Merge pull request #3597 from freqtrade/fix/3579
consistently use filled before amount from orders
This commit is contained in:
commit
5d61c56650
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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:
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user