commit
fb376153a2
@ -99,7 +99,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
|
||||
|
||||
# Protect sell-logic from forcesell and vice versa
|
||||
self._sell_lock = Lock()
|
||||
self._exit_lock = Lock()
|
||||
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
||||
|
||||
def notify_status(self, msg: str) -> None:
|
||||
@ -166,14 +166,14 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
self.strategy.analyze(self.active_pair_whitelist)
|
||||
|
||||
with self._sell_lock:
|
||||
with self._exit_lock:
|
||||
# Check and handle any timed out open orders
|
||||
self.check_handle_timedout()
|
||||
|
||||
# Protect from collisions with forcesell.
|
||||
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
||||
# while selling is in process, since telegram messages arrive in an different thread.
|
||||
with self._sell_lock:
|
||||
with self._exit_lock:
|
||||
trades = Trade.get_open_trades()
|
||||
# First process current opened trades (positions)
|
||||
self.exit_positions(trades)
|
||||
@ -296,9 +296,9 @@ class FreqtradeBot(LoggingMixin):
|
||||
if sell_order:
|
||||
self.refind_lost_order(trade)
|
||||
else:
|
||||
self.reupdate_buy_order_fees(trade)
|
||||
self.reupdate_enter_order_fees(trade)
|
||||
|
||||
def reupdate_buy_order_fees(self, trade: Trade):
|
||||
def reupdate_enter_order_fees(self, trade: Trade):
|
||||
"""
|
||||
Get buy order from database, and try to reupdate.
|
||||
Handles trades where the initial fee-update did not work.
|
||||
@ -476,21 +476,21 @@ class FreqtradeBot(LoggingMixin):
|
||||
time_in_force = self.strategy.order_time_in_force['buy']
|
||||
|
||||
if price:
|
||||
buy_limit_requested = price
|
||||
enter_limit_requested = price
|
||||
else:
|
||||
# Calculate price
|
||||
proposed_buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy")
|
||||
proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side="buy")
|
||||
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
|
||||
default_retval=proposed_buy_rate)(
|
||||
default_retval=proposed_enter_rate)(
|
||||
pair=pair, current_time=datetime.now(timezone.utc),
|
||||
proposed_rate=proposed_buy_rate)
|
||||
proposed_rate=proposed_enter_rate)
|
||||
|
||||
buy_limit_requested = self.get_valid_price(custom_entry_price, proposed_buy_rate)
|
||||
enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate)
|
||||
|
||||
if not buy_limit_requested:
|
||||
if not enter_limit_requested:
|
||||
raise PricingError('Could not determine buy price.')
|
||||
|
||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, buy_limit_requested,
|
||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, enter_limit_requested,
|
||||
self.strategy.stoploss)
|
||||
|
||||
if not self.edge:
|
||||
@ -498,7 +498,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
|
||||
default_retval=stake_amount)(
|
||||
pair=pair, current_time=datetime.now(timezone.utc),
|
||||
current_rate=buy_limit_requested, proposed_stake=stake_amount,
|
||||
current_rate=enter_limit_requested, proposed_stake=stake_amount,
|
||||
min_stake=min_stake_amount, max_stake=max_stake_amount)
|
||||
stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount)
|
||||
|
||||
@ -508,27 +508,27 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: "
|
||||
f"{stake_amount} ...")
|
||||
|
||||
amount = stake_amount / buy_limit_requested
|
||||
amount = stake_amount / enter_limit_requested
|
||||
order_type = self.strategy.order_types['buy']
|
||||
if forcebuy:
|
||||
# Forcebuy can define a different ordertype
|
||||
order_type = self.strategy.order_types.get('forcebuy', order_type)
|
||||
|
||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
||||
pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested,
|
||||
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
|
||||
time_in_force=time_in_force, current_time=datetime.now(timezone.utc)):
|
||||
logger.info(f"User requested abortion of buying {pair}")
|
||||
return False
|
||||
amount = self.exchange.amount_to_precision(pair, amount)
|
||||
order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy",
|
||||
amount=amount, rate=buy_limit_requested,
|
||||
amount=amount, rate=enter_limit_requested,
|
||||
time_in_force=time_in_force)
|
||||
order_obj = Order.parse_from_ccxt_object(order, pair, 'buy')
|
||||
order_id = order['id']
|
||||
order_status = order.get('status', None)
|
||||
|
||||
# we assume the order is executed at the price requested
|
||||
buy_limit_filled_price = buy_limit_requested
|
||||
enter_limit_filled_price = enter_limit_requested
|
||||
amount_requested = amount
|
||||
|
||||
if order_status == 'expired' or order_status == 'rejected':
|
||||
@ -551,13 +551,13 @@ class FreqtradeBot(LoggingMixin):
|
||||
)
|
||||
stake_amount = order['cost']
|
||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
||||
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
|
||||
# in case of FOK the order may be filled immediately and fully
|
||||
elif order_status == 'closed':
|
||||
stake_amount = order['cost']
|
||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
||||
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
enter_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')
|
||||
@ -569,8 +569,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
amount_requested=amount_requested,
|
||||
fee_open=fee,
|
||||
fee_close=fee,
|
||||
open_rate=buy_limit_filled_price,
|
||||
open_rate_requested=buy_limit_requested,
|
||||
open_rate=enter_limit_filled_price,
|
||||
open_rate_requested=enter_limit_requested,
|
||||
open_date=datetime.utcnow(),
|
||||
exchange=self.exchange.id,
|
||||
open_order_id=order_id,
|
||||
@ -590,11 +590,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Updating wallets
|
||||
self.wallets.update()
|
||||
|
||||
self._notify_buy(trade, order_type)
|
||||
self._notify_enter(trade, order_type)
|
||||
|
||||
return True
|
||||
|
||||
def _notify_buy(self, trade: Trade, order_type: str) -> None:
|
||||
def _notify_enter(self, trade: Trade, order_type: str) -> None:
|
||||
"""
|
||||
Sends rpc notification when a buy occurred.
|
||||
"""
|
||||
@ -617,7 +617,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Send the message
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def _notify_buy_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
def _notify_enter_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
"""
|
||||
Sends rpc notification when a buy cancel occurred.
|
||||
"""
|
||||
@ -643,7 +643,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Send the message
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def _notify_buy_fill(self, trade: Trade) -> None:
|
||||
def _notify_enter_fill(self, trade: Trade) -> None:
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY_FILL,
|
||||
@ -713,8 +713,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
)
|
||||
|
||||
logger.debug('checking sell')
|
||||
sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
|
||||
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
|
||||
exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
|
||||
if self._check_and_execute_exit(trade, exit_rate, buy, sell):
|
||||
return True
|
||||
|
||||
logger.debug('Found no sell signal for %s.', trade)
|
||||
@ -744,7 +744,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
except InvalidOrderException as e:
|
||||
trade.stoploss_order_id = None
|
||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||
logger.warning('Selling the trade forcefully')
|
||||
logger.warning('Exiting the trade forcefully')
|
||||
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
||||
sell_type=SellType.EMERGENCY_SELL))
|
||||
|
||||
@ -782,7 +782,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Lock pair for one candle to prevent immediate rebuys
|
||||
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
||||
reason='Auto lock')
|
||||
self._notify_sell(trade, "stoploss")
|
||||
self._notify_exit(trade, "stoploss")
|
||||
return True
|
||||
|
||||
if trade.open_order_id or not trade.is_open:
|
||||
@ -851,19 +851,19 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.warning(f"Could not create trailing stoploss order "
|
||||
f"for pair {trade.pair}.")
|
||||
|
||||
def _check_and_execute_sell(self, trade: Trade, sell_rate: float,
|
||||
def _check_and_execute_exit(self, trade: Trade, exit_rate: float,
|
||||
buy: bool, sell: bool) -> bool:
|
||||
"""
|
||||
Check and execute sell
|
||||
Check and execute exit
|
||||
"""
|
||||
should_sell = self.strategy.should_sell(
|
||||
trade, sell_rate, datetime.now(timezone.utc), buy, sell,
|
||||
trade, exit_rate, datetime.now(timezone.utc), buy, sell,
|
||||
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
||||
)
|
||||
|
||||
if should_sell.sell_flag:
|
||||
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
|
||||
self.execute_trade_exit(trade, sell_rate, should_sell)
|
||||
self.execute_trade_exit(trade, exit_rate, should_sell)
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -906,7 +906,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
default_retval=False)(pair=trade.pair,
|
||||
trade=trade,
|
||||
order=order))):
|
||||
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
|
||||
elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and (
|
||||
fully_cancelled
|
||||
@ -915,7 +915,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
default_retval=False)(pair=trade.pair,
|
||||
trade=trade,
|
||||
order=order))):
|
||||
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
|
||||
def cancel_all_open_orders(self) -> None:
|
||||
"""
|
||||
@ -931,13 +931,13 @@ class FreqtradeBot(LoggingMixin):
|
||||
continue
|
||||
|
||||
if order['side'] == 'buy':
|
||||
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
|
||||
elif order['side'] == 'sell':
|
||||
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
Trade.commit()
|
||||
|
||||
def handle_cancel_buy(self, trade: Trade, order: Dict, reason: str) -> bool:
|
||||
def handle_cancel_enter(self, trade: Trade, order: Dict, reason: str) -> bool:
|
||||
"""
|
||||
Buy cancel - cancel order
|
||||
:return: True if order was fully cancelled
|
||||
@ -994,11 +994,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
|
||||
|
||||
self.wallets.update()
|
||||
self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'],
|
||||
reason=reason)
|
||||
self._notify_enter_cancel(trade, order_type=self.strategy.order_types['buy'],
|
||||
reason=reason)
|
||||
return was_trade_fully_canceled
|
||||
|
||||
def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str:
|
||||
def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> str:
|
||||
"""
|
||||
Sell cancel - cancel order and update trade
|
||||
:return: Reason for cancel
|
||||
@ -1032,14 +1032,14 @@ class FreqtradeBot(LoggingMixin):
|
||||
reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
|
||||
self.wallets.update()
|
||||
self._notify_sell_cancel(
|
||||
self._notify_exit_cancel(
|
||||
trade,
|
||||
order_type=self.strategy.order_types['sell'],
|
||||
reason=reason
|
||||
)
|
||||
return reason
|
||||
|
||||
def _safe_sell_amount(self, pair: str, amount: float) -> float:
|
||||
def _safe_exit_amount(self, pair: str, amount: float) -> float:
|
||||
"""
|
||||
Get sellable amount.
|
||||
Should be trade.amount - but will fall back to the available amount if necessary.
|
||||
@ -1111,7 +1111,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# but we allow this value to be changed)
|
||||
order_type = self.strategy.order_types.get("forcesell", order_type)
|
||||
|
||||
amount = self._safe_sell_amount(trade.pair, trade.amount)
|
||||
amount = self._safe_exit_amount(trade.pair, trade.amount)
|
||||
time_in_force = self.strategy.order_time_in_force['sell']
|
||||
|
||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
|
||||
@ -1150,11 +1150,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
||||
reason='Auto lock')
|
||||
|
||||
self._notify_sell(trade, order_type)
|
||||
self._notify_exit(trade, order_type)
|
||||
|
||||
return True
|
||||
|
||||
def _notify_sell(self, trade: Trade, order_type: str, fill: bool = False) -> None:
|
||||
def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False) -> None:
|
||||
"""
|
||||
Sends rpc notification when a sell occurred.
|
||||
"""
|
||||
@ -1196,7 +1196,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Send the message
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def _notify_sell_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
def _notify_exit_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
"""
|
||||
Sends rpc notification when a sell cancel occurred.
|
||||
"""
|
||||
@ -1291,13 +1291,13 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Updating wallets when order is closed
|
||||
if not trade.is_open:
|
||||
if not stoploss_order and not trade.open_order_id:
|
||||
self._notify_sell(trade, '', True)
|
||||
self._notify_exit(trade, '', True)
|
||||
self.protections.stop_per_pair(trade.pair)
|
||||
self.protections.global_stop()
|
||||
self.wallets.update()
|
||||
elif not trade.open_order_id:
|
||||
# Buy fill
|
||||
self._notify_buy_fill(trade)
|
||||
self._notify_enter_fill(trade)
|
||||
|
||||
return False
|
||||
|
||||
|
@ -548,12 +548,12 @@ class RPC:
|
||||
order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair)
|
||||
|
||||
if order['side'] == 'buy':
|
||||
fully_canceled = self._freqtrade.handle_cancel_buy(
|
||||
fully_canceled = self._freqtrade.handle_cancel_enter(
|
||||
trade, order, CANCEL_REASON['FORCE_SELL'])
|
||||
|
||||
if order['side'] == 'sell':
|
||||
# Cancel order - so it is placed anew with a fresh price.
|
||||
self._freqtrade.handle_cancel_sell(trade, order, CANCEL_REASON['FORCE_SELL'])
|
||||
self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_SELL'])
|
||||
|
||||
if not fully_canceled:
|
||||
# Get current rate and execute sell
|
||||
@ -566,7 +566,7 @@ class RPC:
|
||||
if self._freqtrade.state != State.RUNNING:
|
||||
raise RPCException('trader is not running')
|
||||
|
||||
with self._freqtrade._sell_lock:
|
||||
with self._freqtrade._exit_lock:
|
||||
if trade_id == 'all':
|
||||
# Execute sell for all open orders
|
||||
for trade in Trade.get_open_trades():
|
||||
@ -628,7 +628,7 @@ class RPC:
|
||||
Handler for delete <id>.
|
||||
Delete the given trade and close eventually existing open orders.
|
||||
"""
|
||||
with self._freqtrade._sell_lock:
|
||||
with self._freqtrade._exit_lock:
|
||||
c_count = 0
|
||||
trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first()
|
||||
if not trade:
|
||||
|
@ -1190,7 +1190,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.sell_reason == SellType.EMERGENCY_SELL.value
|
||||
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
|
||||
assert log_has("Selling the trade forcefully", caplog)
|
||||
assert log_has("Exiting the trade forcefully", caplog)
|
||||
|
||||
# Should call a market sell
|
||||
assert create_order_mock.call_count == 2
|
||||
@ -2453,8 +2453,8 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
handle_cancel_buy=MagicMock(),
|
||||
handle_cancel_sell=MagicMock(),
|
||||
handle_cancel_enter=MagicMock(),
|
||||
handle_cancel_exit=MagicMock(),
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@ -2474,7 +2474,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
||||
caplog)
|
||||
|
||||
|
||||
def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> None:
|
||||
def test_handle_cancel_enter(mocker, caplog, default_conf, limit_buy_order) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_buy_order = deepcopy(limit_buy_order)
|
||||
@ -2485,7 +2485,7 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_buy_cancel = MagicMock()
|
||||
freqtrade._notify_enter_cancel = MagicMock()
|
||||
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/USDT'
|
||||
@ -2493,46 +2493,46 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
caplog.clear()
|
||||
limit_buy_order['filled'] = 0.01
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog)
|
||||
|
||||
caplog.clear()
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['filled'] = 2
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
# Order remained open for some reason (cancel failed)
|
||||
cancel_buy_order['status'] = 'open'
|
||||
cancel_order_mock = MagicMock(return_value=cancel_buy_order)
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert log_has_re(r"Order .* for .* not cancelled.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
|
||||
indirect=['limit_buy_order_canceled_empty'])
|
||||
def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf,
|
||||
limit_buy_order_canceled_empty) -> None:
|
||||
def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf,
|
||||
limit_buy_order_canceled_empty) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = mocker.patch(
|
||||
'freqtrade.exchange.Exchange.cancel_order_with_result',
|
||||
return_value=limit_buy_order_canceled_empty)
|
||||
nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_buy_cancel')
|
||||
nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter_cancel')
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order_canceled_empty, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog)
|
||||
assert nofiy_mock.call_count == 1
|
||||
@ -2544,8 +2544,8 @@ def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf,
|
||||
'String Return value',
|
||||
123
|
||||
])
|
||||
def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
cancelorder) -> None:
|
||||
def test_handle_cancel_enter_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
cancelorder) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock(return_value=cancelorder)
|
||||
@ -2555,7 +2555,7 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_buy_cancel = MagicMock()
|
||||
freqtrade._notify_enter_cancel = MagicMock()
|
||||
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/USDT'
|
||||
@ -2563,16 +2563,16 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['filled'] = 1.0
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
||||
def test_handle_cancel_exit_limit(mocker, default_conf, fee) -> None:
|
||||
send_msg_mock = patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
@ -2598,26 +2598,26 @@ def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason)
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert send_msg_mock.call_count == 1
|
||||
|
||||
send_msg_mock.reset_mock()
|
||||
|
||||
order['amount'] = 2
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason
|
||||
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
# Assert cancel_order was not called (callcount remains unchanged)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert send_msg_mock.call_count == 1
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason
|
||||
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
# Message should not be iterated again
|
||||
assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
assert send_msg_mock.call_count == 1
|
||||
|
||||
|
||||
def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
||||
def test_handle_cancel_exit_cancel_exception(mocker, default_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(
|
||||
@ -2630,7 +2630,7 @@ def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
||||
order = {'remaining': 1,
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order'
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order'
|
||||
|
||||
|
||||
def test_execute_trade_exit_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||
@ -3302,7 +3302,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_
|
||||
assert trade.amount != amnt
|
||||
|
||||
|
||||
def test__safe_sell_amount(default_conf, fee, caplog, mocker):
|
||||
def test__safe_exit_amount(default_conf, fee, caplog, mocker):
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
amount = 95.33
|
||||
@ -3322,17 +3322,17 @@ def test__safe_sell_amount(default_conf, fee, caplog, mocker):
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
wallet_update.reset_mock()
|
||||
assert freqtrade._safe_sell_amount(trade.pair, trade.amount) == amount_wallet
|
||||
assert freqtrade._safe_exit_amount(trade.pair, trade.amount) == amount_wallet
|
||||
assert log_has_re(r'.*Falling back to wallet-amount.', caplog)
|
||||
assert wallet_update.call_count == 1
|
||||
caplog.clear()
|
||||
wallet_update.reset_mock()
|
||||
assert freqtrade._safe_sell_amount(trade.pair, amount_wallet) == amount_wallet
|
||||
assert freqtrade._safe_exit_amount(trade.pair, amount_wallet) == amount_wallet
|
||||
assert not log_has_re(r'.*Falling back to wallet-amount.', caplog)
|
||||
assert wallet_update.call_count == 1
|
||||
|
||||
|
||||
def test__safe_sell_amount_error(default_conf, fee, caplog, mocker):
|
||||
def test__safe_exit_amount_error(default_conf, fee, caplog, mocker):
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
amount = 95.33
|
||||
@ -3350,7 +3350,7 @@ def test__safe_sell_amount_error(default_conf, fee, caplog, mocker):
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
with pytest.raises(DependencyException, match=r"Not enough amount to sell."):
|
||||
assert freqtrade._safe_sell_amount(trade.pair, trade.amount)
|
||||
assert freqtrade._safe_exit_amount(trade.pair, trade.amount)
|
||||
|
||||
|
||||
def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplog) -> None:
|
||||
@ -4304,8 +4304,8 @@ def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limi
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||
side_effect=[
|
||||
ExchangeError(), limit_sell_order, limit_buy_order, limit_sell_order])
|
||||
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_buy')
|
||||
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell')
|
||||
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter')
|
||||
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit')
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
create_mock_trades(fee)
|
||||
@ -4420,14 +4420,14 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf, fee):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
||||
def test_reupdate_enter_order_fees(mocker, default_conf, fee, caplog):
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
|
||||
|
||||
create_mock_trades(fee)
|
||||
trades = Trade.get_trades().all()
|
||||
|
||||
freqtrade.reupdate_buy_order_fees(trades[0])
|
||||
freqtrade.reupdate_enter_order_fees(trades[0])
|
||||
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
||||
assert mock_uts.call_count == 1
|
||||
assert mock_uts.call_args_list[0][0][0] == trades[0]
|
||||
@ -4450,7 +4450,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
||||
)
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
freqtrade.reupdate_buy_order_fees(trade)
|
||||
freqtrade.reupdate_enter_order_fees(trade)
|
||||
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
||||
assert mock_uts.call_count == 0
|
||||
assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog)
|
||||
@ -4460,7 +4460,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
||||
def test_handle_insufficient_funds(mocker, default_conf, fee):
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
|
||||
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_buy_order_fees')
|
||||
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_enter_order_fees')
|
||||
create_mock_trades(fee)
|
||||
trades = Trade.get_trades().all()
|
||||
|
||||
|
@ -70,7 +70,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
create_stoploss_order=MagicMock(return_value=True),
|
||||
_notify_sell=MagicMock(),
|
||||
_notify_exit=MagicMock(),
|
||||
)
|
||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
|
||||
wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
|
||||
@ -154,7 +154,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
create_stoploss_order=MagicMock(return_value=True),
|
||||
_notify_sell=MagicMock(),
|
||||
_notify_exit=MagicMock(),
|
||||
)
|
||||
should_sell_mock = MagicMock(side_effect=[
|
||||
SellCheckTuple(sell_type=SellType.NONE),
|
||||
|
Loading…
Reference in New Issue
Block a user