Merge branch 'develop' into feat/short

This commit is contained in:
Matthias
2022-02-23 06:27:56 +01:00
29 changed files with 252 additions and 202 deletions

View File

@@ -174,7 +174,7 @@ def test_stoploss_adjust_ftx(mocker, default_conf, sl1, sl2, sl3, side):
assert not exchange.stoploss_adjust(sl3, order, side=side)
def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order, limit_buy_order):
def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order, limit_buy_order):
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123
@@ -196,9 +196,15 @@ def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order, limit_buy_
with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"):
exchange.fetch_stoploss_order('X', 'TKN/BTC')['status']
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': 'closed'}])
# stoploss Limit order
api_mock.fetch_orders = MagicMock(return_value=[
{'id': 'X', 'status': 'closed',
'info': {
'orderId': 'mocked_limit_sell',
}}])
api_mock.fetch_order = MagicMock(return_value=limit_sell_order)
# No orderId field - no call to fetch_order
resp = exchange.fetch_stoploss_order('X', 'TKN/BTC')
assert resp
assert api_mock.fetch_order.call_count == 1
@@ -207,15 +213,16 @@ def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order, limit_buy_
assert resp['type'] == 'stop'
assert resp['status_stop'] == 'triggered'
api_mock.fetch_order = MagicMock(return_value=limit_buy_order)
# Stoploss market order
# Contains no new Order, but "average" instead
order = {'id': 'X', 'status': 'closed', 'info': {'orderId': None}, 'average': 0.254}
api_mock.fetch_orders = MagicMock(return_value=[order])
api_mock.fetch_order.reset_mock()
resp = exchange.fetch_stoploss_order('X', 'TKN/BTC')
assert resp
assert api_mock.fetch_order.call_count == 1
assert resp['id_stop'] == 'mocked_limit_buy'
assert resp['id'] == 'X'
assert resp['type'] == 'stop'
assert resp['status_stop'] == 'triggered'
# fetch_order not called (no regular order ID)
assert api_mock.fetch_order.call_count == 0
assert order == order
with pytest.raises(InvalidOrderException):
api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))

View File

@@ -562,25 +562,39 @@ tc35 = BTContainer(data=[
)
# Test 36: Custom-entry-price around candle low
# Causes immediate ROI exit. This is currently expected behavior (#6261)
# https://github.com/freqtrade/freqtrade/issues/6261
# But may change at a later point.
# Would cause immediate ROI exit, but since the trade was entered
# below open, we treat this as cheating, and delay the sell by 1 candle.
# details: https://github.com/freqtrade/freqtrade/issues/6261
tc36 = BTContainer(data=[
# D O H L C V B S BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # Enter and immediate ROI
[1, 5000, 5500, 4951, 4999, 6172, 0, 0], # Enter and immediate ROI
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.1,
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
custom_entry_price=4952,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
)
# Test 37: Custom-entry-price around candle low
# Would cause immediate ROI exit below close
# details: https://github.com/freqtrade/freqtrade/issues/6261
tc37 = BTContainer(data=[
# D O H L C V B S BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5400, 5500, 4951, 5100, 6172, 0, 0], # Enter and immediate ROI
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
custom_entry_price=4952,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)]
)
# Test 37: Custom exit price below all candles
# Test 38: Custom exit price below all candles
# Price adjusted to candle Low.
tc37 = BTContainer(data=[
tc38 = BTContainer(data=[
# D O H L C V B S BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0],
@@ -593,9 +607,9 @@ tc37 = BTContainer(data=[
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=3)]
)
# Test 38: Custom exit price above all candles
# Test 39: Custom exit price above all candles
# causes sell signal timeout
tc38 = BTContainer(data=[
tc39 = BTContainer(data=[
# D O H L C V B S BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0],

View File

@@ -776,7 +776,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
assert 'No closed trade' in msg_mock.call_args_list[-1][0][0]
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
mocker.patch('freqtrade.wallets.Wallets.get_starting_balance', return_value=0.01)
assert ('∙ `-0.00000500 BTC (-0.50%) (-0.0 \N{GREEK CAPITAL LETTER SIGMA}%)`'
assert ('∙ `-0.000005 BTC (-0.50%) (-0.0 \N{GREEK CAPITAL LETTER SIGMA}%)`'
in msg_mock.call_args_list[-1][0][0])
msg_mock.reset_mock()
@@ -852,7 +852,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick
assert '*XRP:*' not in result
assert 'Balance:' in result
assert 'Est. BTC:' in result
assert 'BTC: 12.00000000' in result
assert 'BTC: 12' in result
assert "*3 Other Currencies (< 0.0001 BTC):*" in result
assert 'BTC: 0.00000309' in result
@@ -868,7 +868,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
telegram._balance(update=update, context=MagicMock())
result = msg_mock.call_args_list[0][0][0]
assert msg_mock.call_count == 1
assert 'All balances are zero.' in result
assert 'Starting capital: `0 BTC' in result
def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None:
@@ -881,7 +881,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None
result = msg_mock.call_args_list[0][0][0]
assert msg_mock.call_count == 1
assert "*Warning:* Simulated balances in Dry Mode." in result
assert "Starting capital: `1000` BTC" in result
assert "Starting capital: `1000 BTC`" in result
def test_balance_handle_too_large_response(default_conf, update, mocker) -> None:
@@ -1277,7 +1277,8 @@ def test_forceenter_no_pair(default_conf, update, mocker) -> None:
assert msg_mock.call_args_list[0][1]['msg'] == 'Which pair?'
# assert msg_mock.call_args_list[0][1]['callback_query_handler'] == 'forcebuy'
keyboard = msg_mock.call_args_list[0][1]['keyboard']
assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 4
# One additional button - cancel
assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5
update = MagicMock()
update.callback_query = MagicMock()
update.callback_query.data = 'XRP/USDT_||_long'
@@ -1760,7 +1761,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
'leverage': leverage,
'limit': 1.099e-05,
'order_type': 'limit',
'stake_amount': 0.001,
'stake_amount': 0.01465333,
'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
@@ -1780,7 +1781,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
f'{leverage_text}'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.00100000 BTC, 12.345 USD)`'
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
)
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
@@ -1865,7 +1866,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, ente
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': leverage,
'stake_amount': 0.001,
'stake_amount': 0.01465333,
# 'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
@@ -1880,7 +1881,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, ente
'*Amount:* `1333.33333333`\n'
f"{leverage_text}"
'*Open Rate:* `0.00001099`\n'
'*Total:* `(0.00100000 BTC, 12.345 USD)`'
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
)
@@ -2095,7 +2096,7 @@ def test_send_msg_buy_notification_no_fiat(
'leverage': leverage,
'limit': 1.099e-05,
'order_type': 'limit',
'stake_amount': 0.001,
'stake_amount': 0.01465333,
'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
'fiat_currency': None,
@@ -2112,7 +2113,7 @@ def test_send_msg_buy_notification_no_fiat(
f'{leverage_text}'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.00100000 BTC)`'
'*Total:* `(0.01465333 BTC)`'
)

View File

@@ -445,7 +445,8 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
strategy.custom_stoploss = custom_stop
now = arrow.utcnow().datetime
sl_flag = strategy.stop_loss_reached(current_rate=trade.open_rate * (1 + profit), trade=trade,
current_rate = trade.open_rate * (1 + profit)
sl_flag = strategy.stop_loss_reached(current_rate=current_rate, trade=trade,
current_time=now, current_profit=profit,
force_stoploss=0, high=None)
assert isinstance(sl_flag, SellCheckTuple)
@@ -455,8 +456,9 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
else:
assert sl_flag.sell_flag is True
assert round(trade.stop_loss, 2) == adjusted
current_rate2 = trade.open_rate * (1 + profit2)
sl_flag = strategy.stop_loss_reached(current_rate=trade.open_rate * (1 + profit2), trade=trade,
sl_flag = strategy.stop_loss_reached(current_rate=current_rate2, trade=trade,
current_time=now, current_profit=profit2,
force_stoploss=0, high=None)
assert sl_flag.sell_type == expected2

View File

@@ -2730,8 +2730,7 @@ def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_tr
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order,
is_short) -> None:
def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_short) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
l_order = limit_order[enter_side(is_short)]
@@ -2775,6 +2774,9 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order,
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
assert log_has_re(r"Order .* for .* not cancelled.", caplog)
# min_pair_stake empty should not crash
mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=None)
assert not freqtrade.handle_cancel_enter(trade, limit_order[enter_side(is_short)], reason)
@pytest.mark.parametrize("is_short", [False, True])
@@ -4609,23 +4611,17 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_sh
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
create_mock_trades(fee, is_short=is_short)
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
return_value={'status': 'open'})
create_mock_trades(fee, is_short)
trades = Trade.get_trades().all()
freqtrade.reupdate_enter_order_fees(trades[0])
assert log_has_re(
f"Trying to reupdate {enter_side(is_short)} "
r"fees for .*",
caplog
)
freqtrade.handle_insufficient_funds(trades[3])
# 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]
assert mock_uts.call_args_list[0][0][1] == mock_order_1(is_short=is_short)['id']
assert log_has_re(
f"Updating {enter_side(is_short)}-fee on trade "
r".* for order .*\.",
caplog
)
assert mock_uts.call_args_list[0][0][0] == trades[3]
assert mock_uts.call_args_list[0][0][1] == mock_order_4(is_short)['id']
assert log_has_re(r"Trying to refind lost order for .*", caplog)
mock_uts.reset_mock()
caplog.clear()
@@ -4644,55 +4640,14 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_sh
)
Trade.query.session.add(trade)
freqtrade.reupdate_enter_order_fees(trade)
assert log_has_re(f"Trying to reupdate {enter_side(is_short)} fees for "
r".*", caplog)
freqtrade.handle_insufficient_funds(trade)
# assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
assert mock_uts.call_count == 0
assert not log_has_re(f"Updating {enter_side(is_short)}-fee on trade "
r".* for order .*\.", caplog)
@pytest.mark.usefixtures("init_persistence")
def test_handle_insufficient_funds(mocker, default_conf_usdt, fee):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_enter_order_fees')
create_mock_trades(fee, is_short=False)
trades = Trade.get_trades().all()
# Trade 0 has only a open buy order, no closed order
freqtrade.handle_insufficient_funds(trades[0])
assert mock_rlo.call_count == 0
assert mock_bof.call_count == 1
mock_rlo.reset_mock()
mock_bof.reset_mock()
# Trade 1 has closed buy and sell orders
freqtrade.handle_insufficient_funds(trades[1])
assert mock_rlo.call_count == 1
assert mock_bof.call_count == 0
mock_rlo.reset_mock()
mock_bof.reset_mock()
# Trade 2 has closed buy and sell orders
freqtrade.handle_insufficient_funds(trades[2])
assert mock_rlo.call_count == 1
assert mock_bof.call_count == 0
mock_rlo.reset_mock()
mock_bof.reset_mock()
# Trade 3 has an opne buy order
freqtrade.handle_insufficient_funds(trades[3])
assert mock_rlo.call_count == 0
assert mock_bof.call_count == 1
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short):
def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, caplog):
caplog.set_level(logging.DEBUG)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
@@ -4716,7 +4671,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short):
assert trade.open_order_id is None
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
freqtrade.handle_insufficient_funds(trade)
order = mock_order_1(is_short=is_short)
assert log_has_re(r"Order Order(.*order_id=" + order['id'] + ".*) is no longer open.", caplog)
assert mock_fo.call_count == 0
@@ -4734,13 +4689,13 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short):
assert trade.open_order_id is None
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
freqtrade.handle_insufficient_funds(trade)
order = mock_order_4(is_short=is_short)
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 0
assert mock_uts.call_count == 0
# No change to orderid - as update_trade_state is mocked
assert trade.open_order_id is None
assert mock_fo.call_count == 1
assert mock_uts.call_count == 1
# Found open buy order
assert trade.open_order_id is not None
assert trade.stoploss_order_id is None
caplog.clear()
@@ -4752,11 +4707,11 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short):
assert trade.open_order_id is None
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
freqtrade.handle_insufficient_funds(trade)
order = mock_order_5_stoploss(is_short=is_short)
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 1
assert mock_uts.call_count == 1
assert mock_uts.call_count == 2
# stoploss_order_id is "refound" and added to the trade
assert trade.open_order_id is None
assert trade.stoploss_order_id is not None
@@ -4771,7 +4726,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short):
assert trade.open_order_id is None
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
freqtrade.handle_insufficient_funds(trade)
order = mock_order_6_sell(is_short=is_short)
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 1
@@ -4787,7 +4742,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short):
side_effect=ExchangeError())
order = mock_order_5_stoploss(is_short=is_short)
freqtrade.refind_lost_order(trades[4])
freqtrade.handle_insufficient_funds(trades[4])
assert log_has(f"Error updating {order['id']}.", caplog)

View File

@@ -21,16 +21,19 @@ def test_decimals_per_coin():
def test_round_coin_value():
assert round_coin_value(222.222222, 'USDT') == '222.222 USDT'
assert round_coin_value(222.2, 'USDT') == '222.200 USDT'
assert round_coin_value(222.2, 'USDT', keep_trailing_zeros=True) == '222.200 USDT'
assert round_coin_value(222.2, 'USDT') == '222.2 USDT'
assert round_coin_value(222.12745, 'EUR') == '222.127 EUR'
assert round_coin_value(0.1274512123, 'BTC') == '0.12745121 BTC'
assert round_coin_value(0.1274512123, 'ETH') == '0.12745 ETH'
assert round_coin_value(222.222222, 'USDT', False) == '222.222'
assert round_coin_value(222.2, 'USDT', False) == '222.200'
assert round_coin_value(222.2, 'USDT', False) == '222.2'
assert round_coin_value(222.00, 'USDT', False) == '222'
assert round_coin_value(222.12745, 'EUR', False) == '222.127'
assert round_coin_value(0.1274512123, 'BTC', False) == '0.12745121'
assert round_coin_value(0.1274512123, 'ETH', False) == '0.12745'
assert round_coin_value(222.2, 'USDT', False, True) == '222.200'
def test_shorten_date() -> None:

View File

@@ -38,13 +38,17 @@ def test_init_custom_db_url(default_conf, tmpdir):
init_db(default_conf['db_url'], default_conf['dry_run'])
assert Path(filename).is_file()
r = Trade._session.execute(text("PRAGMA journal_mode"))
assert r.first() == ('wal',)
def test_init_invalid_db_url(default_conf):
def test_init_invalid_db_url():
# Update path to a value other than default, but still in-memory
default_conf.update({'db_url': 'unknown:///some.url'})
with pytest.raises(OperationalException, match=r'.*no valid database URL*'):
init_db(default_conf['db_url'], default_conf['dry_run'])
init_db('unknown:///some.url', True)
with pytest.raises(OperationalException, match=r'Bad db-url.*For in-memory database, pl.*'):
init_db('sqlite:///', True)
def test_init_prod_db(default_conf, mocker):
@@ -2080,11 +2084,14 @@ def test_select_order(fee, is_short):
order = trades[4].select_order(trades[4].enter_side, False)
assert order is not None
trades[4].orders[1].ft_order_side = trades[4].exit_side
order = trades[4].select_order(trades[4].exit_side, True)
assert order is not None
trades[4].orders[1].ft_order_side = 'stoploss'
order = trades[4].select_order('stoploss', None)
assert order is not None
assert order.ft_order_side == 'stoploss'
order = trades[4].select_order(trades[4].exit_side, False)
assert order is None
def test_Trade_object_idem():