Merge pull request #3597 from freqtrade/fix/3579

consistently use filled before amount from orders
This commit is contained in:
Matthias 2020-08-12 19:56:57 +02:00 committed by GitHub
commit 5d61c56650
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 107 additions and 56 deletions

View File

@ -24,7 +24,7 @@ from freqtrade.exceptions import (DDosProtection, ExchangeError,
InvalidOrderException, OperationalException, InvalidOrderException, OperationalException,
RetryableOrderError, TemporaryError) RetryableOrderError, TemporaryError)
from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async 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 CcxtModuleType = Any
@ -480,6 +480,7 @@ class Exchange:
"id": order_id, "id": order_id,
'pair': pair, 'pair': pair,
'price': rate, 'price': rate,
'average': rate,
'amount': _amount, 'amount': _amount,
'cost': _amount * rate, 'cost': _amount * rate,
'type': ordertype, 'type': ordertype,
@ -1144,7 +1145,7 @@ class Exchange:
if fee_curr in self.get_pair_base_currency(order['symbol']): if fee_curr in self.get_pair_base_currency(order['symbol']):
# Base currency - divide by amount # Base currency - divide by amount
return round( 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']): elif fee_curr in self.get_pair_quote_currency(order['symbol']):
# Quote currency - divide by cost # Quote currency - divide by cost
return round(order['fee']['cost'] / order['cost'], 8) if order['cost'] else None 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']) comb = self.get_valid_pair_combination(fee_curr, self._config['stake_currency'])
tick = self.fetch_ticker(comb) 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) return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8)
except ExchangeError: except ExchangeError:
return None return None
@ -1172,7 +1173,6 @@ class Exchange:
return (order['fee']['cost'], return (order['fee']['cost'],
order['fee']['currency'], order['fee']['currency'],
self.calculate_fee_rate(order)) self.calculate_fee_rate(order))
# calculate rate ? (order['fee']['cost'] / (order['amount'] * order['price']))
def is_exchange_bad(exchange_name: str) -> bool: def is_exchange_bad(exchange_name: str) -> bool:

View File

@ -20,7 +20,7 @@ from freqtrade.edge import Edge
from freqtrade.exceptions import (DependencyException, ExchangeError, from freqtrade.exceptions import (DependencyException, ExchangeError,
InvalidOrderException, PricingError) InvalidOrderException, PricingError)
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date 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.pairlist.pairlistmanager import PairListManager
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import ExchangeResolver, StrategyResolver
@ -523,7 +523,7 @@ class FreqtradeBot:
time_in_force=time_in_force): time_in_force=time_in_force):
logger.info(f"User requested abortion of buying {pair}") logger.info(f"User requested abortion of buying {pair}")
return False return False
amount = self.exchange.amount_to_precision(pair, amount)
order = self.exchange.buy(pair=pair, ordertype=order_type, order = self.exchange.buy(pair=pair, ordertype=order_type,
amount=amount, rate=buy_limit_requested, amount=amount, rate=buy_limit_requested,
time_in_force=time_in_force) time_in_force=time_in_force)
@ -532,6 +532,7 @@ class FreqtradeBot:
# we assume the order is executed at the price requested # we assume the order is executed at the price requested
buy_limit_filled_price = buy_limit_requested buy_limit_filled_price = buy_limit_requested
amount_requested = amount
if order_status == 'expired' or order_status == 'rejected': if order_status == 'expired' or order_status == 'rejected':
order_tif = self.strategy.order_time_in_force['buy'] order_tif = self.strategy.order_time_in_force['buy']
@ -552,15 +553,15 @@ class FreqtradeBot:
order['filled'], order['amount'], order['remaining'] order['filled'], order['amount'], order['remaining']
) )
stake_amount = order['cost'] stake_amount = order['cost']
amount = order['amount'] amount = safe_value_fallback(order, 'filled', 'amount')
buy_limit_filled_price = order['price'] buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
order_id = None order_id = None
# in case of FOK the order may be filled immediately and fully # in case of FOK the order may be filled immediately and fully
elif order_status == 'closed': elif order_status == 'closed':
stake_amount = order['cost'] stake_amount = order['cost']
amount = order['amount'] amount = safe_value_fallback(order, 'filled', 'amount')
buy_limit_filled_price = order['price'] buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
@ -568,6 +569,7 @@ class FreqtradeBot:
pair=pair, pair=pair,
stake_amount=stake_amount, stake_amount=stake_amount,
amount=amount, amount=amount,
amount_requested=amount_requested,
fee_open=fee, fee_open=fee,
fee_close=fee, fee_close=fee,
open_rate=buy_limit_filled_price, open_rate=buy_limit_filled_price,
@ -990,7 +992,7 @@ class FreqtradeBot:
logger.info('Buy order %s for %s.', reason, trade) logger.info('Buy order %s for %s.', reason, trade)
# Using filled to determine the filled amount # 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): if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC):
logger.info('Buy order fully cancelled. Removing %s from database.', trade) logger.info('Buy order fully cancelled. Removing %s from database.', trade)
@ -1255,7 +1257,8 @@ class FreqtradeBot:
# Try update amount (binance-fix) # Try update amount (binance-fix)
try: try:
new_amount = self.get_real_amount(trade, order, order_amount) 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['amount'] = new_amount
order.pop('filled', None) order.pop('filled', None)
trade.recalc_open_trade_price() trade.recalc_open_trade_price()
@ -1301,7 +1304,7 @@ class FreqtradeBot:
""" """
# Init variables # Init variables
if order_amount is None: if order_amount is None:
order_amount = order['amount'] order_amount = safe_value_fallback(order, 'filled', 'amount')
# Only run for closed orders # Only run for closed orders
if trade.fee_updated(order.get('side', '')) or order['status'] == 'open': if trade.fee_updated(order.get('side', '')) or order['status'] == 'open':
return order_amount return order_amount

View File

@ -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()} 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. 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. Fall back to dict2 - return key2 from dict2 if it's not None.

View File

@ -17,6 +17,7 @@ from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.pool import StaticPool from sqlalchemy.pool import StaticPool
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import safe_value_fallback
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -86,7 +87,7 @@ def check_migrate(engine) -> None:
logger.debug(f'trying {table_back_name}') logger.debug(f'trying {table_back_name}')
# Check for latest column # 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}') logger.info(f'Running database migration - backup available as {table_back_name}')
fee_open = get_column_def(cols, 'fee_open', 'fee') fee_open = get_column_def(cols, 'fee_open', 'fee')
@ -119,6 +120,7 @@ def check_migrate(engine) -> None:
cols, 'close_profit_abs', cols, 'close_profit_abs',
f"(amount * close_rate * (1 - {fee_close})) - {open_trade_price}") f"(amount * close_rate * (1 - {fee_close})) - {open_trade_price}")
sell_order_status = get_column_def(cols, 'sell_order_status', 'null') sell_order_status = get_column_def(cols, 'sell_order_status', 'null')
amount_requested = get_column_def(cols, 'amount_requested', 'amount')
# Schema migration necessary # Schema migration necessary
engine.execute(f"alter table trades rename to {table_back_name}") 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_open, fee_open_cost, fee_open_currency,
fee_close, fee_close_cost, fee_open_currency, open_rate, fee_close, fee_close_cost, fee_open_currency, open_rate,
open_rate_requested, close_rate, close_rate_requested, close_profit, 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, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
stoploss_order_id, stoploss_last_update, stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, sell_order_status, strategy, 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, {fee_close_cost} fee_close_cost, {fee_close_currency} fee_close_currency,
open_rate, {open_rate_requested} open_rate_requested, close_rate, open_rate, {open_rate_requested} open_rate_requested, close_rate,
{close_rate_requested} close_rate_requested, close_profit, {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, {stop_loss} stop_loss, {stop_loss_pct} stop_loss_pct,
{initial_stop_loss} initial_stop_loss, {initial_stop_loss} initial_stop_loss,
{initial_stop_loss_pct} initial_stop_loss_pct, {initial_stop_loss_pct} initial_stop_loss_pct,
@ -215,6 +217,7 @@ class Trade(_DECL_BASE):
close_profit_abs = Column(Float) close_profit_abs = Column(Float)
stake_amount = Column(Float, nullable=False) stake_amount = Column(Float, nullable=False)
amount = Column(Float) amount = Column(Float)
amount_requested = Column(Float)
open_date = Column(DateTime, nullable=False, default=datetime.utcnow) open_date = Column(DateTime, nullable=False, default=datetime.utcnow)
close_date = Column(DateTime) close_date = Column(DateTime)
open_order_id = Column(String) open_order_id = Column(String)
@ -256,6 +259,7 @@ class Trade(_DECL_BASE):
'is_open': self.is_open, 'is_open': self.is_open,
'exchange': self.exchange, 'exchange': self.exchange,
'amount': round(self.amount, 8), '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), 'stake_amount': round(self.stake_amount, 8),
'strategy': self.strategy, 'strategy': self.strategy,
'ticker_interval': self.timeframe, # DEPRECATED 'ticker_interval': self.timeframe, # DEPRECATED
@ -273,7 +277,7 @@ class Trade(_DECL_BASE):
'open_timestamp': int(self.open_date.timestamp() * 1000), 'open_timestamp': int(self.open_date.timestamp() * 1000),
'open_rate': self.open_rate, 'open_rate': self.open_rate,
'open_rate_requested': self.open_rate_requested, '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() 'close_date_hum': (arrow.get(self.close_date).humanize()
if self.close_date else None), if self.close_date else None),
@ -365,20 +369,20 @@ class Trade(_DECL_BASE):
""" """
order_type = order['type'] order_type = order['type']
# Ignore open and cancelled orders # 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 return
logger.info('Updating trade (id=%s) ...', self.id) logger.info('Updating trade (id=%s) ...', self.id)
if order_type in ('market', 'limit') and order['side'] == 'buy': if order_type in ('market', 'limit') and order['side'] == 'buy':
# Update open rate and actual amount # Update open rate and actual amount
self.open_rate = Decimal(order['price']) self.open_rate = Decimal(safe_value_fallback(order, 'average', 'price'))
self.amount = Decimal(order.get('filled', order['amount'])) self.amount = Decimal(safe_value_fallback(order, 'filled', 'amount'))
self.recalc_open_trade_price() self.recalc_open_trade_price()
logger.info('%s_BUY has been fulfilled for %s.', order_type.upper(), self) logger.info('%s_BUY has been fulfilled for %s.', order_type.upper(), self)
self.open_order_id = None self.open_order_id = None
elif order_type in ('market', 'limit') and order['side'] == 'sell': 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) logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self)
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'): elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'):
self.stoploss_order_id = None self.stoploss_order_id = None

View File

@ -176,6 +176,7 @@ def create_mock_trades(fee):
pair='ETH/BTC', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=123.0, amount=123.0,
amount_requested=123.0,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
open_rate=0.123, open_rate=0.123,
@ -188,6 +189,7 @@ def create_mock_trades(fee):
pair='ETC/BTC', pair='ETC/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=123.0, amount=123.0,
amount_requested=123.0,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
open_rate=0.123, open_rate=0.123,
@ -218,6 +220,7 @@ def create_mock_trades(fee):
pair='ETC/BTC', pair='ETC/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=123.0, amount=123.0,
amount_requested=124.0,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
open_rate=0.123, open_rate=0.123,

View File

@ -79,7 +79,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'close_rate': None, 'close_rate': None,
'current_rate': 1.099e-05, 'current_rate': 1.099e-05,
'amount': 91.07468124, 'amount': 91.07468123,
'amount_requested': 91.07468123,
'stake_amount': 0.001, 'stake_amount': 0.001,
'close_profit': None, 'close_profit': None,
'close_profit_pct': 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, 'open_rate': 1.098e-05,
'close_rate': None, 'close_rate': None,
'current_rate': ANY, 'current_rate': ANY,
'amount': 91.07468124, 'amount': 91.07468123,
'amount_requested': 91.07468123,
'stake_amount': 0.001, 'stake_amount': 0.001,
'close_profit': None, 'close_profit': None,
'close_profit_pct': None, 'close_profit_pct': None,

View File

@ -566,7 +566,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
rc = client_get(client, f"{BASE_URI}/status") rc = client_get(client, f"{BASE_URI}/status")
assert_response(rc) assert_response(rc)
assert len(rc.json) == 1 assert len(rc.json) == 1
assert rc.json == [{'amount': 91.07468124, assert rc.json == [{'amount': 91.07468123,
'amount_requested': 91.07468123,
'base_currency': 'BTC', 'base_currency': 'BTC',
'close_date': None, 'close_date': None,
'close_date_hum': None, 'close_date_hum': None,
@ -688,6 +689,7 @@ def test_api_forcebuy(botclient, mocker, fee):
fbuy_mock = MagicMock(return_value=Trade( fbuy_mock = MagicMock(return_value=Trade(
pair='ETH/ETH', pair='ETH/ETH',
amount=1, amount=1,
amount_requested=1,
exchange='bittrex', exchange='bittrex',
stake_amount=1, stake_amount=1,
open_rate=0.245441, open_rate=0.245441,
@ -704,6 +706,7 @@ def test_api_forcebuy(botclient, mocker, fee):
data='{"pair": "ETH/BTC"}') data='{"pair": "ETH/BTC"}')
assert_response(rc) assert_response(rc)
assert rc.json == {'amount': 1, assert rc.json == {'amount': 1,
'amount_requested': 1,
'trade_id': None, 'trade_id': None,
'close_date': None, 'close_date': None,
'close_date_hum': None, 'close_date_hum': None,
@ -740,7 +743,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'min_rate': None, 'min_rate': None,
'open_order_id': '123456', 'open_order_id': '123456',
'open_rate_requested': None, 'open_rate_requested': None,
'open_trade_price': 0.2460546025, 'open_trade_price': 0.24605460,
'sell_reason': None, 'sell_reason': None,
'sell_order_status': None, 'sell_order_status': None,
'strategy': None, 'strategy': None,

View File

@ -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] assert 'reloading config' in msg_mock.call_args_list[0][0][0]
def test_forcesell_handle(default_conf, update, ticker, fee, def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
ticker_sell_up, mocker) -> None: ticker_sell_up, mocker) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', 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', 'pair': 'ETH/BTC',
'gain': 'profit', 'gain': 'profit',
'limit': 1.173e-05, 'limit': 1.173e-05,
'amount': 91.07468123861567, 'amount': 91.07468123,
'order_type': 'limit', 'order_type': 'limit',
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'current_rate': 1.173e-05, 'current_rate': 1.173e-05,
@ -745,8 +745,8 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
} == last_msg } == last_msg
def test_forcesell_down_handle(default_conf, update, ticker, fee, def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
ticker_sell_down, mocker) -> None: ticker_sell_down, mocker) -> None:
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=15000.0) return_value=15000.0)
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) 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', 'pair': 'ETH/BTC',
'gain': 'loss', 'gain': 'loss',
'limit': 1.043e-05, 'limit': 1.043e-05,
'amount': 91.07468123861567, 'amount': 91.07468123,
'order_type': 'limit', 'order_type': 'limit',
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'current_rate': 1.043e-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', 'pair': 'ETH/BTC',
'gain': 'loss', 'gain': 'loss',
'limit': 1.099e-05, 'limit': 1.099e-05,
'amount': 91.07468123861567, 'amount': 91.07468123,
'order_type': 'limit', 'order_type': 'limit',
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'current_rate': 1.099e-05, 'current_rate': 1.099e-05,

View File

@ -595,7 +595,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order,
freqtrade.create_trade('ETH/BTC') freqtrade.create_trade('ETH/BTC')
rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount'] 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, 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.open_date is not None
assert trade.exchange == 'bittrex' assert trade.exchange == 'bittrex'
assert trade.open_rate == 0.00001098 assert trade.open_rate == 0.00001098
assert trade.amount == 91.07468123861567 assert trade.amount == 91.07468123
assert log_has( assert log_has(
'Buy signal found: about create a new trade with stake_amount: 0.001 ...', caplog '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] call_args = buy_mm.call_args_list[0][1]
assert call_args['pair'] == pair assert call_args['pair'] == pair
assert call_args['rate'] == bid 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() buy_rate_mock.reset_mock()
# Should create an open trade with an open order id # 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] call_args = buy_mm.call_args_list[1][1]
assert call_args['pair'] == pair assert call_args['pair'] == pair
assert call_args['rate'] == fix_price 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 # In case of closed order
limit_buy_order['status'] = 'closed' 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 assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_called_once_with(100, 'ETH/BTC') 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', pair='ETH/BTC',
order_types=freqtrade.strategy.order_types, order_types=freqtrade.strategy.order_types,
stop_price=0.00002346 * 0.95) 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 # stoploss should be set to 1% as trailing is on
assert trade.stop_loss == 0.00002346 * 0.99 assert trade.stop_loss == 0.00002346 * 0.99
cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') 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', pair='NEO/BTC',
order_types=freqtrade.strategy.order_types, order_types=freqtrade.strategy.order_types,
stop_price=0.00002346 * 0.99) 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', 'pair': 'ETH/BTC',
'gain': 'profit', 'gain': 'profit',
'limit': 1.172e-05, 'limit': 1.172e-05,
'amount': 91.07468123861567, 'amount': 91.07468123,
'order_type': 'limit', 'order_type': 'limit',
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'current_rate': 1.173e-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', 'pair': 'ETH/BTC',
'gain': 'loss', 'gain': 'loss',
'limit': 1.044e-05, 'limit': 1.044e-05,
'amount': 91.07468123861567, 'amount': 91.07468123,
'order_type': 'limit', 'order_type': 'limit',
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'current_rate': 1.043e-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', 'pair': 'ETH/BTC',
'gain': 'loss', 'gain': 'loss',
'limit': 1.08801e-05, 'limit': 1.08801e-05,
'amount': 91.07468123861567, 'amount': 91.07468123,
'order_type': 'limit', 'order_type': 'limit',
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'current_rate': 1.043e-05, 'current_rate': 1.043e-05,
@ -2911,7 +2911,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'gain': 'profit', 'gain': 'profit',
'limit': 1.172e-05, 'limit': 1.172e-05,
'amount': 91.07468123861567, 'amount': 91.07468123,
'order_type': 'market', 'order_type': 'market',
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'current_rate': 1.173e-05, 'current_rate': 1.173e-05,

View File

@ -11,7 +11,7 @@ from freqtrade.misc import (datesarray_to_datetimearray, file_dump_json,
file_load_json, format_ms_time, pair_to_filename, file_load_json, format_ms_time, pair_to_filename,
plural, render_template, plural, render_template,
render_template_with_fallback, safe_value_fallback, render_template_with_fallback, safe_value_fallback,
shorten_date) safe_value_fallback2, shorten_date)
def test_shorten_date() -> None: def test_shorten_date() -> None:
@ -96,24 +96,40 @@ def test_format_ms_time() -> None:
def test_safe_value_fallback(): 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} dict1 = {'keya': None, 'keyb': 2, 'keyc': 5, 'keyd': None}
dict2 = {'keya': 20, 'keyb': None, 'keyc': 6, 'keyd': None} dict2 = {'keya': 20, 'keyb': None, 'keyc': 6, 'keyd': None}
assert safe_value_fallback(dict1, dict2, 'keya', 'keya') == 20 assert safe_value_fallback2(dict1, dict2, 'keya', 'keya') == 20
assert safe_value_fallback(dict2, dict1, 'keya', 'keya') == 20 assert safe_value_fallback2(dict2, dict1, 'keya', 'keya') == 20
assert safe_value_fallback(dict1, dict2, 'keyb', 'keyb') == 2 assert safe_value_fallback2(dict1, dict2, 'keyb', 'keyb') == 2
assert safe_value_fallback(dict2, dict1, 'keyb', 'keyb') == 2 assert safe_value_fallback2(dict2, dict1, 'keyb', 'keyb') == 2
assert safe_value_fallback(dict1, dict2, 'keyc', 'keyc') == 5 assert safe_value_fallback2(dict1, dict2, 'keyc', 'keyc') == 5
assert safe_value_fallback(dict2, dict1, 'keyc', 'keyc') == 6 assert safe_value_fallback2(dict2, dict1, 'keyc', 'keyc') == 6
assert safe_value_fallback(dict1, dict2, 'keyd', 'keyd') is None assert safe_value_fallback2(dict1, dict2, 'keyd', 'keyd') is None
assert safe_value_fallback(dict2, dict1, 'keyd', 'keyd') is None assert safe_value_fallback2(dict2, dict1, 'keyd', 'keyd') is None
assert safe_value_fallback(dict2, dict1, 'keyd', 'keyd', 1234) == 1234 assert safe_value_fallback2(dict2, dict1, 'keyd', 'keyd', 1234) == 1234
assert safe_value_fallback(dict1, dict2, 'keyNo', 'keyNo') is None assert safe_value_fallback2(dict1, dict2, 'keyNo', 'keyNo') is None
assert safe_value_fallback(dict2, dict1, 'keyNo', 'keyNo') is None assert safe_value_fallback2(dict2, dict1, 'keyNo', 'keyNo') is None
assert safe_value_fallback(dict2, dict1, 'keyNo', 'keyNo', 1234) == 1234 assert safe_value_fallback2(dict2, dict1, 'keyNo', 'keyNo', 1234) == 1234
def test_plural() -> None: def test_plural() -> None:

View File

@ -457,6 +457,7 @@ def test_migrate_old(mocker, default_conf, fee):
assert trade.close_rate_requested is None assert trade.close_rate_requested is None
assert trade.is_open == 1 assert trade.is_open == 1
assert trade.amount == amount assert trade.amount == amount
assert trade.amount_requested == amount
assert trade.stake_amount == default_conf.get("stake_amount") assert trade.stake_amount == default_conf.get("stake_amount")
assert trade.pair == "ETC/BTC" assert trade.pair == "ETC/BTC"
assert trade.exchange == "bittrex" 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.close_rate_requested is None
assert trade.is_open == 1 assert trade.is_open == 1
assert trade.amount == amount assert trade.amount == amount
assert trade.amount_requested == amount
assert trade.stake_amount == default_conf.get("stake_amount") assert trade.stake_amount == default_conf.get("stake_amount")
assert trade.pair == "ETC/BTC" assert trade.pair == "ETC/BTC"
assert trade.exchange == "binance" assert trade.exchange == "binance"
@ -725,6 +727,7 @@ def test_to_json(default_conf, fee):
pair='ETH/BTC', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=123.0, amount=123.0,
amount_requested=123.0,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=arrow.utcnow().shift(hours=-2).datetime,
@ -757,6 +760,7 @@ def test_to_json(default_conf, fee):
'close_rate': None, 'close_rate': None,
'close_rate_requested': None, 'close_rate_requested': None,
'amount': 123.0, 'amount': 123.0,
'amount_requested': 123.0,
'stake_amount': 0.001, 'stake_amount': 0.001,
'close_profit': None, 'close_profit': None,
'close_profit_abs': None, 'close_profit_abs': None,
@ -786,6 +790,7 @@ def test_to_json(default_conf, fee):
pair='XRP/BTC', pair='XRP/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=100.0, amount=100.0,
amount_requested=101.0,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=arrow.utcnow().shift(hours=-2).datetime,
@ -808,6 +813,7 @@ def test_to_json(default_conf, fee):
'open_rate': 0.123, 'open_rate': 0.123,
'close_rate': 0.125, 'close_rate': 0.125,
'amount': 100.0, 'amount': 100.0,
'amount_requested': 101.0,
'stake_amount': 0.001, 'stake_amount': 0.001,
'stop_loss': None, 'stop_loss': None,
'stop_loss_abs': None, 'stop_loss_abs': None,