Merge pull request #3597 from freqtrade/fix/3579
consistently use filled before amount from orders
This commit is contained in:
		| @@ -24,7 +24,7 @@ from freqtrade.exceptions import (DDosProtection, ExchangeError, | ||||
|                                   InvalidOrderException, OperationalException, | ||||
|                                   RetryableOrderError, TemporaryError) | ||||
| from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async | ||||
| from freqtrade.misc import deep_merge_dicts, safe_value_fallback | ||||
| from freqtrade.misc import deep_merge_dicts, safe_value_fallback2 | ||||
|  | ||||
| CcxtModuleType = Any | ||||
|  | ||||
| @@ -480,6 +480,7 @@ class Exchange: | ||||
|             "id": order_id, | ||||
|             'pair': pair, | ||||
|             'price': rate, | ||||
|             'average': rate, | ||||
|             'amount': _amount, | ||||
|             'cost': _amount * rate, | ||||
|             'type': ordertype, | ||||
| @@ -1144,7 +1145,7 @@ class Exchange: | ||||
|         if fee_curr in self.get_pair_base_currency(order['symbol']): | ||||
|             # Base currency - divide by amount | ||||
|             return round( | ||||
|                 order['fee']['cost'] / safe_value_fallback(order, order, 'filled', 'amount'), 8) | ||||
|                 order['fee']['cost'] / safe_value_fallback2(order, order, 'filled', 'amount'), 8) | ||||
|         elif fee_curr in self.get_pair_quote_currency(order['symbol']): | ||||
|             # Quote currency - divide by cost | ||||
|             return round(order['fee']['cost'] / order['cost'], 8) if order['cost'] else None | ||||
| @@ -1157,7 +1158,7 @@ class Exchange: | ||||
|                 comb = self.get_valid_pair_combination(fee_curr, self._config['stake_currency']) | ||||
|                 tick = self.fetch_ticker(comb) | ||||
|  | ||||
|                 fee_to_quote_rate = safe_value_fallback(tick, tick, 'last', 'ask') | ||||
|                 fee_to_quote_rate = safe_value_fallback2(tick, tick, 'last', 'ask') | ||||
|                 return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8) | ||||
|             except ExchangeError: | ||||
|                 return None | ||||
| @@ -1172,7 +1173,6 @@ class Exchange: | ||||
|         return (order['fee']['cost'], | ||||
|                 order['fee']['currency'], | ||||
|                 self.calculate_fee_rate(order)) | ||||
|         # calculate rate ? (order['fee']['cost'] / (order['amount'] * order['price'])) | ||||
|  | ||||
|  | ||||
| def is_exchange_bad(exchange_name: str) -> bool: | ||||
|   | ||||
| @@ -20,7 +20,7 @@ from freqtrade.edge import Edge | ||||
| from freqtrade.exceptions import (DependencyException, ExchangeError, | ||||
|                                   InvalidOrderException, PricingError) | ||||
| from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date | ||||
| from freqtrade.misc import safe_value_fallback | ||||
| from freqtrade.misc import safe_value_fallback, safe_value_fallback2 | ||||
| from freqtrade.pairlist.pairlistmanager import PairListManager | ||||
| from freqtrade.persistence import Trade | ||||
| from freqtrade.resolvers import ExchangeResolver, StrategyResolver | ||||
| @@ -523,7 +523,7 @@ class FreqtradeBot: | ||||
|                 time_in_force=time_in_force): | ||||
|             logger.info(f"User requested abortion of buying {pair}") | ||||
|             return False | ||||
|  | ||||
|         amount = self.exchange.amount_to_precision(pair, amount) | ||||
|         order = self.exchange.buy(pair=pair, ordertype=order_type, | ||||
|                                   amount=amount, rate=buy_limit_requested, | ||||
|                                   time_in_force=time_in_force) | ||||
| @@ -532,6 +532,7 @@ class FreqtradeBot: | ||||
|  | ||||
|         # we assume the order is executed at the price requested | ||||
|         buy_limit_filled_price = buy_limit_requested | ||||
|         amount_requested = amount | ||||
|  | ||||
|         if order_status == 'expired' or order_status == 'rejected': | ||||
|             order_tif = self.strategy.order_time_in_force['buy'] | ||||
| @@ -552,15 +553,15 @@ class FreqtradeBot: | ||||
|                                order['filled'], order['amount'], order['remaining'] | ||||
|                                ) | ||||
|                 stake_amount = order['cost'] | ||||
|                 amount = order['amount'] | ||||
|                 buy_limit_filled_price = order['price'] | ||||
|                 amount = safe_value_fallback(order, 'filled', 'amount') | ||||
|                 buy_limit_filled_price = safe_value_fallback(order, 'average', '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'] | ||||
|             amount = safe_value_fallback(order, 'filled', 'amount') | ||||
|             buy_limit_filled_price = safe_value_fallback(order, 'average', 'price') | ||||
|  | ||||
|         # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL | ||||
|         fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') | ||||
| @@ -568,6 +569,7 @@ class FreqtradeBot: | ||||
|             pair=pair, | ||||
|             stake_amount=stake_amount, | ||||
|             amount=amount, | ||||
|             amount_requested=amount_requested, | ||||
|             fee_open=fee, | ||||
|             fee_close=fee, | ||||
|             open_rate=buy_limit_filled_price, | ||||
| @@ -990,7 +992,7 @@ class FreqtradeBot: | ||||
|         logger.info('Buy order %s for %s.', reason, trade) | ||||
|  | ||||
|         # Using filled to determine the filled amount | ||||
|         filled_amount = safe_value_fallback(corder, order, 'filled', 'filled') | ||||
|         filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled') | ||||
|  | ||||
|         if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC): | ||||
|             logger.info('Buy order fully cancelled. Removing %s from database.', trade) | ||||
| @@ -1255,7 +1257,8 @@ class FreqtradeBot: | ||||
|         # Try update amount (binance-fix) | ||||
|         try: | ||||
|             new_amount = self.get_real_amount(trade, order, order_amount) | ||||
|             if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC): | ||||
|             if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount, | ||||
|                            abs_tol=constants.MATH_CLOSE_PREC): | ||||
|                 order['amount'] = new_amount | ||||
|                 order.pop('filled', None) | ||||
|                 trade.recalc_open_trade_price() | ||||
| @@ -1301,7 +1304,7 @@ class FreqtradeBot: | ||||
|         """ | ||||
|         # Init variables | ||||
|         if order_amount is None: | ||||
|             order_amount = order['amount'] | ||||
|             order_amount = safe_value_fallback(order, 'filled', 'amount') | ||||
|         # Only run for closed orders | ||||
|         if trade.fee_updated(order.get('side', '')) or order['status'] == 'open': | ||||
|             return order_amount | ||||
|   | ||||
| @@ -134,7 +134,21 @@ def round_dict(d, n): | ||||
|     return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()} | ||||
|  | ||||
|  | ||||
| def safe_value_fallback(dict1: dict, dict2: dict, key1: str, key2: str, default_value=None): | ||||
| def safe_value_fallback(obj: dict, key1: str, key2: str, default_value=None): | ||||
|     """ | ||||
|     Search a value in obj, return this if it's not None. | ||||
|     Then search key2 in obj - return that if it's not none - then use default_value. | ||||
|     Else falls back to None. | ||||
|     """ | ||||
|     if key1 in obj and obj[key1] is not None: | ||||
|         return obj[key1] | ||||
|     else: | ||||
|         if key2 in obj and obj[key2] is not None: | ||||
|             return obj[key2] | ||||
|     return default_value | ||||
|  | ||||
|  | ||||
| def safe_value_fallback2(dict1: dict, dict2: dict, key1: str, key2: str, default_value=None): | ||||
|     """ | ||||
|     Search a value in dict1, return this if it's not None. | ||||
|     Fall back to dict2 - return key2 from dict2 if it's not None. | ||||
|   | ||||
| @@ -17,6 +17,7 @@ from sqlalchemy.orm.session import sessionmaker | ||||
| from sqlalchemy.pool import StaticPool | ||||
|  | ||||
| from freqtrade.exceptions import OperationalException | ||||
| from freqtrade.misc import safe_value_fallback | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -86,7 +87,7 @@ def check_migrate(engine) -> None: | ||||
|         logger.debug(f'trying {table_back_name}') | ||||
|  | ||||
|     # Check for latest column | ||||
|     if not has_column(cols, 'timeframe'): | ||||
|     if not has_column(cols, 'amount_requested'): | ||||
|         logger.info(f'Running database migration - backup available as {table_back_name}') | ||||
|  | ||||
|         fee_open = get_column_def(cols, 'fee_open', 'fee') | ||||
| @@ -119,6 +120,7 @@ def check_migrate(engine) -> None: | ||||
|             cols, 'close_profit_abs', | ||||
|             f"(amount * close_rate * (1 - {fee_close})) - {open_trade_price}") | ||||
|         sell_order_status = get_column_def(cols, 'sell_order_status', 'null') | ||||
|         amount_requested = get_column_def(cols, 'amount_requested', 'amount') | ||||
|  | ||||
|         # Schema migration necessary | ||||
|         engine.execute(f"alter table trades rename to {table_back_name}") | ||||
| @@ -134,7 +136,7 @@ def check_migrate(engine) -> None: | ||||
|                 fee_open, fee_open_cost, fee_open_currency, | ||||
|                 fee_close, fee_close_cost, fee_open_currency, open_rate, | ||||
|                 open_rate_requested, close_rate, close_rate_requested, close_profit, | ||||
|                 stake_amount, amount, open_date, close_date, open_order_id, | ||||
|                 stake_amount, amount, amount_requested, open_date, close_date, open_order_id, | ||||
|                 stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, | ||||
|                 stoploss_order_id, stoploss_last_update, | ||||
|                 max_rate, min_rate, sell_reason, sell_order_status, strategy, | ||||
| @@ -153,7 +155,7 @@ def check_migrate(engine) -> None: | ||||
|                 {fee_close_cost} fee_close_cost, {fee_close_currency} fee_close_currency, | ||||
|                 open_rate, {open_rate_requested} open_rate_requested, close_rate, | ||||
|                 {close_rate_requested} close_rate_requested, close_profit, | ||||
|                 stake_amount, amount, open_date, close_date, open_order_id, | ||||
|                 stake_amount, amount, {amount_requested}, open_date, close_date, open_order_id, | ||||
|                 {stop_loss} stop_loss, {stop_loss_pct} stop_loss_pct, | ||||
|                 {initial_stop_loss} initial_stop_loss, | ||||
|                 {initial_stop_loss_pct} initial_stop_loss_pct, | ||||
| @@ -215,6 +217,7 @@ class Trade(_DECL_BASE): | ||||
|     close_profit_abs = Column(Float) | ||||
|     stake_amount = Column(Float, nullable=False) | ||||
|     amount = Column(Float) | ||||
|     amount_requested = Column(Float) | ||||
|     open_date = Column(DateTime, nullable=False, default=datetime.utcnow) | ||||
|     close_date = Column(DateTime) | ||||
|     open_order_id = Column(String) | ||||
| @@ -256,6 +259,7 @@ class Trade(_DECL_BASE): | ||||
|             'is_open': self.is_open, | ||||
|             'exchange': self.exchange, | ||||
|             'amount': round(self.amount, 8), | ||||
|             'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None, | ||||
|             'stake_amount': round(self.stake_amount, 8), | ||||
|             'strategy': self.strategy, | ||||
|             'ticker_interval': self.timeframe,  # DEPRECATED | ||||
| @@ -273,7 +277,7 @@ class Trade(_DECL_BASE): | ||||
|             'open_timestamp': int(self.open_date.timestamp() * 1000), | ||||
|             'open_rate': self.open_rate, | ||||
|             'open_rate_requested': self.open_rate_requested, | ||||
|             'open_trade_price': self.open_trade_price, | ||||
|             'open_trade_price': round(self.open_trade_price, 8), | ||||
|  | ||||
|             'close_date_hum': (arrow.get(self.close_date).humanize() | ||||
|                                if self.close_date else None), | ||||
| @@ -365,20 +369,20 @@ class Trade(_DECL_BASE): | ||||
|         """ | ||||
|         order_type = order['type'] | ||||
|         # Ignore open and cancelled orders | ||||
|         if order['status'] == 'open' or order['price'] is None: | ||||
|         if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None: | ||||
|             return | ||||
|  | ||||
|         logger.info('Updating trade (id=%s) ...', self.id) | ||||
|  | ||||
|         if order_type in ('market', 'limit') and order['side'] == 'buy': | ||||
|             # Update open rate and actual amount | ||||
|             self.open_rate = Decimal(order['price']) | ||||
|             self.amount = Decimal(order.get('filled', order['amount'])) | ||||
|             self.open_rate = Decimal(safe_value_fallback(order, 'average', 'price')) | ||||
|             self.amount = Decimal(safe_value_fallback(order, 'filled', 'amount')) | ||||
|             self.recalc_open_trade_price() | ||||
|             logger.info('%s_BUY has been fulfilled for %s.', order_type.upper(), self) | ||||
|             self.open_order_id = None | ||||
|         elif order_type in ('market', 'limit') and order['side'] == 'sell': | ||||
|             self.close(order['price']) | ||||
|             self.close(safe_value_fallback(order, 'average', 'price')) | ||||
|             logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self) | ||||
|         elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'): | ||||
|             self.stoploss_order_id = None | ||||
|   | ||||
| @@ -176,6 +176,7 @@ def create_mock_trades(fee): | ||||
|         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_rate=0.123, | ||||
| @@ -188,6 +189,7 @@ def create_mock_trades(fee): | ||||
|         pair='ETC/BTC', | ||||
|         stake_amount=0.001, | ||||
|         amount=123.0, | ||||
|         amount_requested=123.0, | ||||
|         fee_open=fee.return_value, | ||||
|         fee_close=fee.return_value, | ||||
|         open_rate=0.123, | ||||
| @@ -218,6 +220,7 @@ def create_mock_trades(fee): | ||||
|         pair='ETC/BTC', | ||||
|         stake_amount=0.001, | ||||
|         amount=123.0, | ||||
|         amount_requested=124.0, | ||||
|         fee_open=fee.return_value, | ||||
|         fee_close=fee.return_value, | ||||
|         open_rate=0.123, | ||||
|   | ||||
| @@ -79,7 +79,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: | ||||
|         'open_rate': 1.098e-05, | ||||
|         'close_rate': None, | ||||
|         'current_rate': 1.099e-05, | ||||
|         'amount': 91.07468124, | ||||
|         'amount': 91.07468123, | ||||
|         'amount_requested': 91.07468123, | ||||
|         'stake_amount': 0.001, | ||||
|         'close_profit': None, | ||||
|         'close_profit_pct': None, | ||||
| @@ -142,7 +143,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: | ||||
|         'open_rate': 1.098e-05, | ||||
|         'close_rate': None, | ||||
|         'current_rate': ANY, | ||||
|         'amount': 91.07468124, | ||||
|         'amount': 91.07468123, | ||||
|         'amount_requested': 91.07468123, | ||||
|         'stake_amount': 0.001, | ||||
|         'close_profit': None, | ||||
|         'close_profit_pct': None, | ||||
|   | ||||
| @@ -566,7 +566,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets): | ||||
|     rc = client_get(client, f"{BASE_URI}/status") | ||||
|     assert_response(rc) | ||||
|     assert len(rc.json) == 1 | ||||
|     assert rc.json == [{'amount': 91.07468124, | ||||
|     assert rc.json == [{'amount': 91.07468123, | ||||
|                         'amount_requested': 91.07468123, | ||||
|                         'base_currency': 'BTC', | ||||
|                         'close_date': None, | ||||
|                         'close_date_hum': None, | ||||
| @@ -688,6 +689,7 @@ def test_api_forcebuy(botclient, mocker, fee): | ||||
|     fbuy_mock = MagicMock(return_value=Trade( | ||||
|         pair='ETH/ETH', | ||||
|         amount=1, | ||||
|         amount_requested=1, | ||||
|         exchange='bittrex', | ||||
|         stake_amount=1, | ||||
|         open_rate=0.245441, | ||||
| @@ -704,6 +706,7 @@ def test_api_forcebuy(botclient, mocker, fee): | ||||
|                      data='{"pair": "ETH/BTC"}') | ||||
|     assert_response(rc) | ||||
|     assert rc.json == {'amount': 1, | ||||
|                        'amount_requested': 1, | ||||
|                        'trade_id': None, | ||||
|                        'close_date': None, | ||||
|                        'close_date_hum': None, | ||||
| @@ -740,7 +743,7 @@ def test_api_forcebuy(botclient, mocker, fee): | ||||
|                        'min_rate': None, | ||||
|                        'open_order_id': '123456', | ||||
|                        'open_rate_requested': None, | ||||
|                        'open_trade_price': 0.2460546025, | ||||
|                        'open_trade_price': 0.24605460, | ||||
|                        'sell_reason': None, | ||||
|                        'sell_order_status': None, | ||||
|                        'strategy': None, | ||||
|   | ||||
| @@ -691,8 +691,8 @@ def test_reload_config_handle(default_conf, update, mocker) -> None: | ||||
|     assert 'reloading config' in msg_mock.call_args_list[0][0][0] | ||||
|  | ||||
|  | ||||
| def test_forcesell_handle(default_conf, update, ticker, fee, | ||||
|                           ticker_sell_up, mocker) -> None: | ||||
| def test_telegram_forcesell_handle(default_conf, update, ticker, fee, | ||||
|                                    ticker_sell_up, mocker) -> None: | ||||
|     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) | ||||
|     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) | ||||
|     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) | ||||
| @@ -731,7 +731,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee, | ||||
|         'pair': 'ETH/BTC', | ||||
|         'gain': 'profit', | ||||
|         'limit': 1.173e-05, | ||||
|         'amount': 91.07468123861567, | ||||
|         'amount': 91.07468123, | ||||
|         'order_type': 'limit', | ||||
|         'open_rate': 1.098e-05, | ||||
|         'current_rate': 1.173e-05, | ||||
| @@ -745,8 +745,8 @@ def test_forcesell_handle(default_conf, update, ticker, fee, | ||||
|     } == last_msg | ||||
|  | ||||
|  | ||||
| def test_forcesell_down_handle(default_conf, update, ticker, fee, | ||||
|                                ticker_sell_down, mocker) -> None: | ||||
| def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, | ||||
|                                         ticker_sell_down, mocker) -> None: | ||||
|     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', | ||||
|                  return_value=15000.0) | ||||
|     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) | ||||
| @@ -791,7 +791,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, | ||||
|         'pair': 'ETH/BTC', | ||||
|         'gain': 'loss', | ||||
|         'limit': 1.043e-05, | ||||
|         'amount': 91.07468123861567, | ||||
|         'amount': 91.07468123, | ||||
|         'order_type': 'limit', | ||||
|         'open_rate': 1.098e-05, | ||||
|         'current_rate': 1.043e-05, | ||||
| @@ -840,7 +840,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None | ||||
|         'pair': 'ETH/BTC', | ||||
|         'gain': 'loss', | ||||
|         'limit': 1.099e-05, | ||||
|         'amount': 91.07468123861567, | ||||
|         'amount': 91.07468123, | ||||
|         'order_type': 'limit', | ||||
|         'open_rate': 1.098e-05, | ||||
|         'current_rate': 1.099e-05, | ||||
|   | ||||
| @@ -595,7 +595,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, | ||||
|  | ||||
|     freqtrade.create_trade('ETH/BTC') | ||||
|     rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount'] | ||||
|     assert rate * amount >= default_conf['stake_amount'] | ||||
|     assert rate * amount <= default_conf['stake_amount'] | ||||
|  | ||||
|  | ||||
| def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_order, | ||||
| @@ -782,7 +782,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, | ||||
|     assert trade.open_date is not None | ||||
|     assert trade.exchange == 'bittrex' | ||||
|     assert trade.open_rate == 0.00001098 | ||||
|     assert trade.amount == 91.07468123861567 | ||||
|     assert trade.amount == 91.07468123 | ||||
|  | ||||
|     assert log_has( | ||||
|         'Buy signal found: about create a new trade with stake_amount: 0.001 ...', caplog | ||||
| @@ -1009,7 +1009,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None: | ||||
|     call_args = buy_mm.call_args_list[0][1] | ||||
|     assert call_args['pair'] == pair | ||||
|     assert call_args['rate'] == bid | ||||
|     assert call_args['amount'] == stake_amount / bid | ||||
|     assert call_args['amount'] == round(stake_amount / bid, 8) | ||||
|     buy_rate_mock.reset_mock() | ||||
|  | ||||
|     # Should create an open trade with an open order id | ||||
| @@ -1029,7 +1029,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None: | ||||
|     call_args = buy_mm.call_args_list[1][1] | ||||
|     assert call_args['pair'] == pair | ||||
|     assert call_args['rate'] == fix_price | ||||
|     assert call_args['amount'] == stake_amount / fix_price | ||||
|     assert call_args['amount'] == round(stake_amount / fix_price, 8) | ||||
|  | ||||
|     # In case of closed order | ||||
|     limit_buy_order['status'] = 'closed' | ||||
| @@ -1407,7 +1407,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, | ||||
|     assert freqtrade.handle_stoploss_on_exchange(trade) is False | ||||
|  | ||||
|     cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') | ||||
|     stoploss_order_mock.assert_called_once_with(amount=85.32423208191126, | ||||
|     stoploss_order_mock.assert_called_once_with(amount=85.32423208, | ||||
|                                                 pair='ETH/BTC', | ||||
|                                                 order_types=freqtrade.strategy.order_types, | ||||
|                                                 stop_price=0.00002346 * 0.95) | ||||
| @@ -1595,7 +1595,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, | ||||
|     # stoploss should be set to 1% as trailing is on | ||||
|     assert trade.stop_loss == 0.00002346 * 0.99 | ||||
|     cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') | ||||
|     stoploss_order_mock.assert_called_once_with(amount=2132892.491467577, | ||||
|     stoploss_order_mock.assert_called_once_with(amount=2132892.49146757, | ||||
|                                                 pair='NEO/BTC', | ||||
|                                                 order_types=freqtrade.strategy.order_types, | ||||
|                                                 stop_price=0.00002346 * 0.99) | ||||
| @@ -2598,7 +2598,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N | ||||
|         'pair': 'ETH/BTC', | ||||
|         'gain': 'profit', | ||||
|         'limit': 1.172e-05, | ||||
|         'amount': 91.07468123861567, | ||||
|         'amount': 91.07468123, | ||||
|         'order_type': 'limit', | ||||
|         'open_rate': 1.098e-05, | ||||
|         'current_rate': 1.173e-05, | ||||
| @@ -2648,7 +2648,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) | ||||
|         'pair': 'ETH/BTC', | ||||
|         'gain': 'loss', | ||||
|         'limit': 1.044e-05, | ||||
|         'amount': 91.07468123861567, | ||||
|         'amount': 91.07468123, | ||||
|         'order_type': 'limit', | ||||
|         'open_rate': 1.098e-05, | ||||
|         'current_rate': 1.043e-05, | ||||
| @@ -2705,7 +2705,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe | ||||
|         'pair': 'ETH/BTC', | ||||
|         'gain': 'loss', | ||||
|         'limit': 1.08801e-05, | ||||
|         'amount': 91.07468123861567, | ||||
|         'amount': 91.07468123, | ||||
|         'order_type': 'limit', | ||||
|         'open_rate': 1.098e-05, | ||||
|         'current_rate': 1.043e-05, | ||||
| @@ -2911,7 +2911,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee, | ||||
|         'pair': 'ETH/BTC', | ||||
|         'gain': 'profit', | ||||
|         'limit': 1.172e-05, | ||||
|         'amount': 91.07468123861567, | ||||
|         'amount': 91.07468123, | ||||
|         'order_type': 'market', | ||||
|         'open_rate': 1.098e-05, | ||||
|         'current_rate': 1.173e-05, | ||||
|   | ||||
| @@ -11,7 +11,7 @@ from freqtrade.misc import (datesarray_to_datetimearray, file_dump_json, | ||||
|                             file_load_json, format_ms_time, pair_to_filename, | ||||
|                             plural, render_template, | ||||
|                             render_template_with_fallback, safe_value_fallback, | ||||
|                             shorten_date) | ||||
|                             safe_value_fallback2, shorten_date) | ||||
|  | ||||
|  | ||||
| def test_shorten_date() -> None: | ||||
| @@ -96,24 +96,40 @@ def test_format_ms_time() -> None: | ||||
|  | ||||
|  | ||||
| def test_safe_value_fallback(): | ||||
|     dict1 = {'keya': None, 'keyb': 2, 'keyc': 5, 'keyd': None} | ||||
|     assert safe_value_fallback(dict1, 'keya', 'keyb') == 2 | ||||
|     assert safe_value_fallback(dict1, 'keyb', 'keya') == 2 | ||||
|  | ||||
|     assert safe_value_fallback(dict1, 'keyb', 'keyc') == 2 | ||||
|     assert safe_value_fallback(dict1, 'keya', 'keyc') == 5 | ||||
|  | ||||
|     assert safe_value_fallback(dict1, 'keyc', 'keyb') == 5 | ||||
|  | ||||
|     assert safe_value_fallback(dict1, 'keya', 'keyd') is None | ||||
|  | ||||
|     assert safe_value_fallback(dict1, 'keyNo', 'keyNo') is None | ||||
|     assert safe_value_fallback(dict1, 'keyNo', 'keyNo', 55) == 55 | ||||
|  | ||||
|  | ||||
| def test_safe_value_fallback2(): | ||||
|     dict1 = {'keya': None, 'keyb': 2, 'keyc': 5, 'keyd': None} | ||||
|     dict2 = {'keya': 20, 'keyb': None, 'keyc': 6, 'keyd': None} | ||||
|     assert safe_value_fallback(dict1, dict2, 'keya', 'keya') == 20 | ||||
|     assert safe_value_fallback(dict2, dict1, 'keya', 'keya') == 20 | ||||
|     assert safe_value_fallback2(dict1, dict2, 'keya', 'keya') == 20 | ||||
|     assert safe_value_fallback2(dict2, dict1, 'keya', 'keya') == 20 | ||||
|  | ||||
|     assert safe_value_fallback(dict1, dict2, 'keyb', 'keyb') == 2 | ||||
|     assert safe_value_fallback(dict2, dict1, 'keyb', 'keyb') == 2 | ||||
|     assert safe_value_fallback2(dict1, dict2, 'keyb', 'keyb') == 2 | ||||
|     assert safe_value_fallback2(dict2, dict1, 'keyb', 'keyb') == 2 | ||||
|  | ||||
|     assert safe_value_fallback(dict1, dict2, 'keyc', 'keyc') == 5 | ||||
|     assert safe_value_fallback(dict2, dict1, 'keyc', 'keyc') == 6 | ||||
|     assert safe_value_fallback2(dict1, dict2, 'keyc', 'keyc') == 5 | ||||
|     assert safe_value_fallback2(dict2, dict1, 'keyc', 'keyc') == 6 | ||||
|  | ||||
|     assert safe_value_fallback(dict1, dict2, 'keyd', 'keyd') is None | ||||
|     assert safe_value_fallback(dict2, dict1, 'keyd', 'keyd') is None | ||||
|     assert safe_value_fallback(dict2, dict1, 'keyd', 'keyd', 1234) == 1234 | ||||
|     assert safe_value_fallback2(dict1, dict2, 'keyd', 'keyd') is None | ||||
|     assert safe_value_fallback2(dict2, dict1, 'keyd', 'keyd') is None | ||||
|     assert safe_value_fallback2(dict2, dict1, 'keyd', 'keyd', 1234) == 1234 | ||||
|  | ||||
|     assert safe_value_fallback(dict1, dict2, 'keyNo', 'keyNo') is None | ||||
|     assert safe_value_fallback(dict2, dict1, 'keyNo', 'keyNo') is None | ||||
|     assert safe_value_fallback(dict2, dict1, 'keyNo', 'keyNo', 1234) == 1234 | ||||
|     assert safe_value_fallback2(dict1, dict2, 'keyNo', 'keyNo') is None | ||||
|     assert safe_value_fallback2(dict2, dict1, 'keyNo', 'keyNo') is None | ||||
|     assert safe_value_fallback2(dict2, dict1, 'keyNo', 'keyNo', 1234) == 1234 | ||||
|  | ||||
|  | ||||
| def test_plural() -> None: | ||||
|   | ||||
| @@ -457,6 +457,7 @@ def test_migrate_old(mocker, default_conf, fee): | ||||
|     assert trade.close_rate_requested is None | ||||
|     assert trade.is_open == 1 | ||||
|     assert trade.amount == amount | ||||
|     assert trade.amount_requested == amount | ||||
|     assert trade.stake_amount == default_conf.get("stake_amount") | ||||
|     assert trade.pair == "ETC/BTC" | ||||
|     assert trade.exchange == "bittrex" | ||||
| @@ -546,6 +547,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): | ||||
|     assert trade.close_rate_requested is None | ||||
|     assert trade.is_open == 1 | ||||
|     assert trade.amount == amount | ||||
|     assert trade.amount_requested == amount | ||||
|     assert trade.stake_amount == default_conf.get("stake_amount") | ||||
|     assert trade.pair == "ETC/BTC" | ||||
|     assert trade.exchange == "binance" | ||||
| @@ -725,6 +727,7 @@ def test_to_json(default_conf, fee): | ||||
|         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, | ||||
| @@ -757,6 +760,7 @@ def test_to_json(default_conf, fee): | ||||
|                       'close_rate': None, | ||||
|                       'close_rate_requested': None, | ||||
|                       'amount': 123.0, | ||||
|                       'amount_requested': 123.0, | ||||
|                       'stake_amount': 0.001, | ||||
|                       'close_profit': None, | ||||
|                       'close_profit_abs': None, | ||||
| @@ -786,6 +790,7 @@ def test_to_json(default_conf, fee): | ||||
|         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, | ||||
| @@ -808,6 +813,7 @@ def test_to_json(default_conf, fee): | ||||
|                       'open_rate': 0.123, | ||||
|                       'close_rate': 0.125, | ||||
|                       'amount': 100.0, | ||||
|                       'amount_requested': 101.0, | ||||
|                       'stake_amount': 0.001, | ||||
|                       'stop_loss': None, | ||||
|                       'stop_loss_abs': None, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user