mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-04-28 13:00:13 +00:00
Merge pull request #8779 from Axel-CH/feature/multiple_open_orders
Feature: Multiple open orders
This commit is contained in:
@@ -2601,7 +2601,6 @@ def open_trade():
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='binance',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
@@ -2613,7 +2612,7 @@ def open_trade():
|
||||
Order(
|
||||
ft_order_side='buy',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=False,
|
||||
ft_is_open=True,
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.open_rate,
|
||||
order_id='123456789',
|
||||
@@ -2639,7 +2638,6 @@ def open_trade_usdt():
|
||||
pair='ADA/USDT',
|
||||
open_rate=2.0,
|
||||
exchange='binance',
|
||||
open_order_id='123456789_exit',
|
||||
amount=30.0,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
|
||||
@@ -46,7 +46,6 @@ def mock_trade_1(fee, is_short: bool):
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17),
|
||||
open_rate=0.123,
|
||||
exchange='binance',
|
||||
open_order_id=f'dry_run_buy_{direc(is_short)}_12345',
|
||||
strategy='StrategyTestV3',
|
||||
timeframe=5,
|
||||
is_short=is_short
|
||||
@@ -210,7 +209,6 @@ def mock_trade_4(fee, is_short: bool):
|
||||
is_open=True,
|
||||
open_rate=0.123,
|
||||
exchange='binance',
|
||||
open_order_id=f'prod_buy_{direc(is_short)}_12345',
|
||||
strategy='StrategyTestV3',
|
||||
timeframe=5,
|
||||
is_short=is_short,
|
||||
@@ -327,7 +325,6 @@ def mock_trade_6(fee, is_short: bool):
|
||||
exchange='binance',
|
||||
strategy='SampleStrategy',
|
||||
enter_tag='TEST2',
|
||||
open_order_id=f"prod_sell_{direc(is_short)}_6",
|
||||
timeframe=5,
|
||||
is_short=is_short
|
||||
)
|
||||
@@ -411,7 +408,6 @@ def short_trade(fee):
|
||||
# close_profit_abs=-0.6925113200000013,
|
||||
exchange='binance',
|
||||
is_open=True,
|
||||
open_order_id=None,
|
||||
strategy='DefaultStrategy',
|
||||
timeframe=5,
|
||||
exit_reason='sell_signal',
|
||||
@@ -502,7 +498,6 @@ def leverage_trade(fee):
|
||||
close_profit_abs=2.5983135000000175,
|
||||
exchange='kraken',
|
||||
is_open=False,
|
||||
open_order_id='dry_run_leverage_buy_12368',
|
||||
strategy='DefaultStrategy',
|
||||
timeframe=5,
|
||||
exit_reason='sell_signal',
|
||||
|
||||
@@ -66,7 +66,6 @@ def mock_trade_usdt_1(fee, is_short: bool):
|
||||
close_profit_abs=-4.09,
|
||||
exchange='binance',
|
||||
strategy='SampleStrategy',
|
||||
open_order_id=f'prod_exit_1_{direc(is_short)}',
|
||||
timeframe=5,
|
||||
is_short=is_short,
|
||||
)
|
||||
@@ -123,7 +122,6 @@ def mock_trade_usdt_2(fee, is_short: bool):
|
||||
close_profit_abs=3.9875,
|
||||
exchange='binance',
|
||||
is_open=False,
|
||||
open_order_id=f'12366_{direc(is_short)}',
|
||||
strategy='StrategyTestV2',
|
||||
timeframe=5,
|
||||
enter_tag='TEST1',
|
||||
@@ -231,7 +229,6 @@ def mock_trade_usdt_4(fee, is_short: bool):
|
||||
is_open=True,
|
||||
open_rate=2.0,
|
||||
exchange='binance',
|
||||
open_order_id=f'prod_buy_12345_{direc(is_short)}',
|
||||
strategy='StrategyTestV2',
|
||||
timeframe=5,
|
||||
is_short=is_short,
|
||||
@@ -340,7 +337,6 @@ def mock_trade_usdt_6(fee, is_short: bool):
|
||||
open_rate=10.0,
|
||||
exchange='binance',
|
||||
strategy='SampleStrategy',
|
||||
open_order_id=f'prod_exit_6_{direc(is_short)}',
|
||||
timeframe=5,
|
||||
is_short=is_short,
|
||||
)
|
||||
@@ -378,7 +374,6 @@ def mock_trade_usdt_7(fee, is_short: bool):
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17),
|
||||
open_rate=2.0,
|
||||
exchange='binance',
|
||||
open_order_id=None,
|
||||
strategy='StrategyTestV2',
|
||||
timeframe=5,
|
||||
is_short=is_short,
|
||||
|
||||
@@ -15,6 +15,7 @@ from freqtrade.persistence import Trade, init_db
|
||||
from freqtrade.persistence.base import ModelBase
|
||||
from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids
|
||||
from freqtrade.persistence.models import PairLock
|
||||
from freqtrade.persistence.trade_model import Order
|
||||
from tests.conftest import log_has
|
||||
|
||||
|
||||
@@ -217,6 +218,23 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
),
|
||||
(
|
||||
-- Order without reference trade
|
||||
2,
|
||||
'buy',
|
||||
'ETC/BTC',
|
||||
1,
|
||||
'dry_buy_order55',
|
||||
'canceled',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'buy',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
)
|
||||
"""
|
||||
engine = create_engine('sqlite://')
|
||||
@@ -238,9 +256,10 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
# Run init to test migration
|
||||
init_db(default_conf['db_url'])
|
||||
|
||||
trades = Trade.session.scalars(select(Trade).filter(Trade.id == 1)).all()
|
||||
trades = Trade.session.scalars(select(Trade)).all()
|
||||
assert len(trades) == 1
|
||||
trade = trades[0]
|
||||
assert trade.id == 1
|
||||
assert trade.fee_open == fee.return_value
|
||||
assert trade.fee_close == fee.return_value
|
||||
assert trade.open_rate_requested is None
|
||||
@@ -281,12 +300,18 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
|
||||
assert orders[1].order_id == 'dry_buy_order22'
|
||||
assert orders[1].ft_order_side == 'buy'
|
||||
assert orders[1].ft_is_open is False
|
||||
assert orders[1].ft_is_open is True
|
||||
|
||||
assert orders[2].order_id == 'dry_stop_order_id11X'
|
||||
assert orders[2].ft_order_side == 'stoploss'
|
||||
assert orders[2].ft_is_open is False
|
||||
|
||||
orders1 = Order.session.scalars(select(Order)).all()
|
||||
assert len(orders1) == 5
|
||||
order = orders1[4]
|
||||
assert order.ft_trade_id == 2
|
||||
assert order.ft_is_open is False
|
||||
|
||||
|
||||
def test_migrate_too_old(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
|
||||
@@ -10,7 +10,8 @@ from freqtrade.enums import TradingMode
|
||||
from freqtrade.exceptions import DependencyException
|
||||
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
|
||||
from freqtrade.util import dt_now
|
||||
from tests.conftest import create_mock_trades, create_mock_trades_with_leverage, log_has, log_has_re
|
||||
from tests.conftest import (create_mock_trades, create_mock_trades_usdt,
|
||||
create_mock_trades_with_leverage, log_has, log_has_re)
|
||||
|
||||
|
||||
spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURES
|
||||
@@ -457,15 +458,14 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_
|
||||
leverage=lev,
|
||||
trading_mode=trading_mode
|
||||
)
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
|
||||
trade.open_order_id = enter_order['id']
|
||||
oobj = Order.parse_from_ccxt_object(enter_order, 'ADA/USDT', entry_side)
|
||||
trade.orders.append(oobj)
|
||||
trade.update_trade(oobj)
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.open_rate == open_rate
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
@@ -476,13 +476,12 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_
|
||||
caplog)
|
||||
|
||||
caplog.clear()
|
||||
trade.open_order_id = enter_order['id']
|
||||
time_machine.move_to("2022-03-31 21:45:05 +00:00")
|
||||
oobj = Order.parse_from_ccxt_object(exit_order, 'ADA/USDT', exit_side)
|
||||
trade.orders.append(oobj)
|
||||
trade.update_trade(oobj)
|
||||
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.close_rate == close_rate
|
||||
assert pytest.approx(trade.close_profit) == profit
|
||||
assert trade.close_date is not None
|
||||
@@ -511,11 +510,10 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
||||
leverage=1.0,
|
||||
)
|
||||
|
||||
trade.open_order_id = 'mocked_market_buy'
|
||||
oobj = Order.parse_from_ccxt_object(market_buy_order_usdt, 'ADA/USDT', 'buy')
|
||||
trade.orders.append(oobj)
|
||||
trade.update_trade(oobj)
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.open_rate == 2.0
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
@@ -526,11 +524,10 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
||||
|
||||
caplog.clear()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = 'mocked_market_sell'
|
||||
oobj = Order.parse_from_ccxt_object(market_sell_order_usdt, 'ADA/USDT', 'sell')
|
||||
trade.orders.append(oobj)
|
||||
trade.update_trade(oobj)
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.close_rate == 2.2
|
||||
assert pytest.approx(trade.close_profit) == 0.094513715710723
|
||||
assert trade.close_date is not None
|
||||
@@ -580,7 +577,6 @@ def test_calc_open_close_trade_price(
|
||||
)
|
||||
entry_order = limit_order[trade.entry_side]
|
||||
exit_order = limit_order[trade.exit_side]
|
||||
trade.open_order_id = f'something-{is_short}-{lev}-{exchange}'
|
||||
|
||||
oobj = Order.parse_from_ccxt_object(entry_order, 'ADA/USDT', trade.entry_side)
|
||||
oobj._trade_live = trade
|
||||
@@ -678,7 +674,6 @@ def test_calc_close_trade_price_exception(limit_buy_order_usdt, fee):
|
||||
leverage=1.0,
|
||||
)
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
|
||||
trade.update_trade(oobj)
|
||||
assert trade.calc_close_trade_value(trade.close_rate) == 0.0
|
||||
@@ -697,7 +692,7 @@ def test_update_open_order(limit_buy_order_usdt):
|
||||
trading_mode=margin
|
||||
)
|
||||
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
|
||||
@@ -705,7 +700,7 @@ def test_update_open_order(limit_buy_order_usdt):
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy')
|
||||
trade.update_trade(oobj)
|
||||
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
|
||||
@@ -778,7 +773,6 @@ def test_calc_open_trade_value(
|
||||
is_short=is_short,
|
||||
trading_mode=trading_mode
|
||||
)
|
||||
trade.open_order_id = 'open_trade'
|
||||
oobj = Order.parse_from_ccxt_object(
|
||||
limit_buy_order_usdt, 'ADA/USDT', 'sell' if is_short else 'buy')
|
||||
trade.update_trade(oobj) # Buy @ 2.0
|
||||
@@ -833,7 +827,6 @@ def test_calc_close_trade_price(
|
||||
trading_mode=trading_mode,
|
||||
funding_fees=funding_fees
|
||||
)
|
||||
trade.open_order_id = 'close_trade'
|
||||
assert round(trade.calc_close_trade_value(rate=close_rate), 8) == result
|
||||
|
||||
|
||||
@@ -1156,7 +1149,6 @@ def test_calc_profit(
|
||||
trading_mode=trading_mode,
|
||||
funding_fees=funding_fees
|
||||
)
|
||||
trade.open_order_id = 'something'
|
||||
|
||||
profit_res = trade.calculate_profit(close_rate)
|
||||
assert pytest.approx(profit_res.profit_abs) == round(profit, 8)
|
||||
@@ -1352,6 +1344,24 @@ def test_get_open_lev(fee, use_db):
|
||||
Trade.use_db = True
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_short', [True, False])
|
||||
@pytest.mark.parametrize('use_db', [True, False])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_get_open_orders(fee, is_short, use_db):
|
||||
Trade.use_db = use_db
|
||||
Trade.reset_trades()
|
||||
|
||||
create_mock_trades_usdt(fee, is_short, use_db)
|
||||
# Trade.commit()
|
||||
trade = Trade.get_trades_proxy(pair="XRP/USDT")[0]
|
||||
# assert trade.id == 3
|
||||
assert len(trade.orders) == 2
|
||||
assert len(trade.open_orders) == 0
|
||||
assert not trade.has_open_orders
|
||||
|
||||
Trade.use_db = True
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_to_json(fee):
|
||||
|
||||
@@ -1367,7 +1377,6 @@ def test_to_json(fee):
|
||||
open_rate=0.123,
|
||||
exchange='binance',
|
||||
enter_tag=None,
|
||||
open_order_id='dry_run_buy_12345',
|
||||
precision_mode=1,
|
||||
amount_precision=8.0,
|
||||
price_precision=7.0,
|
||||
@@ -1383,7 +1392,6 @@ def test_to_json(fee):
|
||||
'is_open': None,
|
||||
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
'open_order_id': 'dry_run_buy_12345',
|
||||
'close_date': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 0.123,
|
||||
@@ -1438,6 +1446,7 @@ def test_to_json(fee):
|
||||
'price_precision': 7.0,
|
||||
'precision_mode': 1,
|
||||
'orders': [],
|
||||
'has_open_orders': False,
|
||||
}
|
||||
|
||||
# Simulate dry_run entries
|
||||
@@ -1505,7 +1514,6 @@ def test_to_json(fee):
|
||||
'is_open': None,
|
||||
'max_rate': None,
|
||||
'min_rate': None,
|
||||
'open_order_id': None,
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 12.33075,
|
||||
'exit_reason': None,
|
||||
@@ -1524,6 +1532,7 @@ def test_to_json(fee):
|
||||
'price_precision': 8.0,
|
||||
'precision_mode': 2,
|
||||
'orders': [],
|
||||
'has_open_orders': False,
|
||||
}
|
||||
|
||||
|
||||
@@ -2066,7 +2075,6 @@ def test_Trade_object_idem():
|
||||
'total_open_trades_stakes',
|
||||
'get_closed_trades_without_assigned_fees',
|
||||
'get_open_trades_without_assigned_fees',
|
||||
'get_open_order_trades',
|
||||
'get_trades',
|
||||
'get_trades_query',
|
||||
'get_exit_reason_performance',
|
||||
@@ -2676,7 +2684,7 @@ def test_recalc_trade_from_orders_dca(data) -> None:
|
||||
assert len(trade.orders) == idx + 1
|
||||
if idx < len(data) - 1:
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.amount == result[0]
|
||||
assert trade.open_rate == result[1]
|
||||
assert trade.stake_amount == result[2]
|
||||
@@ -2690,4 +2698,4 @@ def test_recalc_trade_from_orders_dca(data) -> None:
|
||||
assert not trade.is_open
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
|
||||
@@ -42,7 +42,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'strategy': ANY,
|
||||
'enter_tag': ANY,
|
||||
'timeframe': 5,
|
||||
'open_order_id': ANY,
|
||||
'close_date': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 1.098e-05,
|
||||
@@ -75,7 +74,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'stoploss_current_dist_pct': -10.01,
|
||||
'stoploss_entry_dist': -0.00010402,
|
||||
'stoploss_entry_dist_ratio': -0.10376381,
|
||||
'open_order': None,
|
||||
'open_orders': '',
|
||||
'realized_profit': 0.0,
|
||||
'realized_profit_ratio': None,
|
||||
'total_profit_abs': -4.09e-06,
|
||||
@@ -91,6 +90,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'amount_precision': 8.0,
|
||||
'price_precision': 8.0,
|
||||
'precision_mode': 2,
|
||||
'has_open_orders': False,
|
||||
'orders': [{
|
||||
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
|
||||
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
|
||||
@@ -128,7 +128,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'profit_pct': 0.0,
|
||||
'profit_abs': 0.0,
|
||||
'total_profit_abs': 0.0,
|
||||
'open_order': '(limit buy rem=91.07468123)',
|
||||
'open_orders': '(limit buy rem=91.07468123)',
|
||||
'has_open_orders': True,
|
||||
})
|
||||
response_unfilled['orders'][0].update({
|
||||
'is_open': True,
|
||||
@@ -146,7 +147,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
results = rpc._rpc_trade_status()
|
||||
# Reuse above object, only remaining changed.
|
||||
response_unfilled['orders'][0].update({
|
||||
'remaining': None
|
||||
'remaining': None,
|
||||
})
|
||||
assert results[0] == response_unfilled
|
||||
|
||||
@@ -165,6 +166,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
response.update({
|
||||
'max_stake_amount': 0.001,
|
||||
'total_profit_ratio': pytest.approx(-0.00409153),
|
||||
'has_open_orders': False,
|
||||
})
|
||||
assert results[0] == response
|
||||
|
||||
@@ -779,7 +781,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
'amount': amount,
|
||||
'remaining': amount,
|
||||
'filled': 0.0,
|
||||
'id': trade.orders[0].order_id,
|
||||
'id': trade.orders[-1].order_id,
|
||||
}
|
||||
)
|
||||
cancel_order_3 = mocker.patch(
|
||||
@@ -791,7 +793,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
'amount': amount,
|
||||
'remaining': amount,
|
||||
'filled': 0.0,
|
||||
'id': trade.orders[0].order_id,
|
||||
'id': trade.orders[-1].order_id,
|
||||
}
|
||||
)
|
||||
msg = rpc._rpc_force_exit('3')
|
||||
@@ -800,7 +802,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
assert cancel_order_3.call_count == 1
|
||||
assert cancel_order_mock.call_count == 0
|
||||
|
||||
trade = Trade.session.scalars(select(Trade).filter(Trade.id == '2')).first()
|
||||
trade = Trade.session.scalars(select(Trade).filter(Trade.id == '4')).first()
|
||||
amount = trade.amount
|
||||
# make an limit-buy open trade, if there is no 'filled', don't sell it
|
||||
mocker.patch(
|
||||
@@ -829,7 +831,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
assert msg == {'result': 'Created exit order for trade 4.'}
|
||||
assert cancel_order_4.call_count == 1
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert trade.amount == amount
|
||||
assert pytest.approx(trade.amount) == amount
|
||||
|
||||
|
||||
def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None:
|
||||
@@ -1097,7 +1099,8 @@ def test_rpc_force_entry(mocker, default_conf, ticker, fee, limit_buy_order_open
|
||||
trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit', stake_amount=0.05)
|
||||
assert trade.stake_amount == 0.05
|
||||
assert trade.buy_tag == 'force_entry'
|
||||
assert trade.open_order_id == 'mocked_limit_buy'
|
||||
|
||||
assert trade.open_orders_ids[-1] == 'mocked_limit_buy'
|
||||
|
||||
freqtradebot.strategy.position_adjustment_enable = True
|
||||
with pytest.raises(RPCException, match=r'position for LTC/BTC already open.*open order.*'):
|
||||
|
||||
@@ -1026,7 +1026,6 @@ def test_api_performance(botclient, fee):
|
||||
exchange='binance',
|
||||
stake_amount=1,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456",
|
||||
is_open=False,
|
||||
fee_close=fee.return_value,
|
||||
fee_open=fee.return_value,
|
||||
@@ -1043,7 +1042,6 @@ def test_api_performance(botclient, fee):
|
||||
stake_amount=1,
|
||||
exchange='binance',
|
||||
open_rate=0.412,
|
||||
open_order_id="123456",
|
||||
is_open=False,
|
||||
fee_close=fee.return_value,
|
||||
fee_open=fee.return_value,
|
||||
@@ -1066,11 +1064,11 @@ def test_api_performance(botclient, fee):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'is_short,current_rate,open_order_id,open_trade_value',
|
||||
[(True, 1.098e-05, 'dry_run_buy_short_12345', 15.0911775),
|
||||
(False, 1.099e-05, 'dry_run_buy_long_12345', 15.1668225)])
|
||||
'is_short,current_rate,open_trade_value',
|
||||
[(True, 1.098e-05, 15.0911775),
|
||||
(False, 1.099e-05, 15.1668225)])
|
||||
def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
||||
current_rate, open_order_id, open_trade_value):
|
||||
current_rate, open_trade_value):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot)
|
||||
mocker.patch.multiple(
|
||||
@@ -1111,7 +1109,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
||||
'current_rate': current_rate,
|
||||
'open_date': ANY,
|
||||
'open_timestamp': ANY,
|
||||
'open_order': None,
|
||||
'open_rate': 0.123,
|
||||
'pair': 'ETH/BTC',
|
||||
'base_currency': 'ETH',
|
||||
@@ -1144,7 +1141,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
||||
"is_short": is_short,
|
||||
'max_rate': ANY,
|
||||
'min_rate': ANY,
|
||||
'open_order_id': open_order_id,
|
||||
'open_rate_requested': ANY,
|
||||
'open_trade_value': open_trade_value,
|
||||
'exit_reason': None,
|
||||
@@ -1162,6 +1158,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
|
||||
'price_precision': None,
|
||||
'precision_mode': None,
|
||||
'orders': [ANY],
|
||||
'has_open_orders': True,
|
||||
}
|
||||
|
||||
mocker.patch(f'{EXMS}.get_rate',
|
||||
@@ -1291,7 +1288,6 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
||||
exchange='binance',
|
||||
stake_amount=1,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456",
|
||||
open_date=datetime.now(timezone.utc),
|
||||
is_open=False,
|
||||
is_short=False,
|
||||
@@ -1352,7 +1348,6 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
||||
'is_short': False,
|
||||
'max_rate': None,
|
||||
'min_rate': None,
|
||||
'open_order_id': '123456',
|
||||
'open_rate_requested': None,
|
||||
'open_trade_value': 0.24605460,
|
||||
'exit_reason': None,
|
||||
@@ -1369,6 +1364,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
|
||||
'amount_precision': None,
|
||||
'price_precision': None,
|
||||
'precision_mode': None,
|
||||
'has_open_orders': False,
|
||||
'orders': [],
|
||||
}
|
||||
|
||||
|
||||
@@ -872,7 +872,8 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
trade.is_short = is_short
|
||||
assert trade
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id == '22'
|
||||
assert trade.has_open_orders
|
||||
assert '22' in trade.open_orders_ids
|
||||
|
||||
# Test calling with price
|
||||
open_order['id'] = '33'
|
||||
@@ -898,7 +899,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
trade = Trade.session.scalars(select(Trade)).all()[2]
|
||||
trade.is_short = is_short
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.open_rate == 10
|
||||
assert trade.stake_amount == round(order['average'] * order['filled'] / leverage, 8)
|
||||
assert pytest.approx(trade.liquidation_price) == liq_price
|
||||
@@ -916,7 +917,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
trade = Trade.session.scalars(select(Trade)).all()[3]
|
||||
trade.is_short = is_short
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.open_rate == 0.5
|
||||
assert trade.stake_amount == round(order['average'] * order['filled'] / leverage, 8)
|
||||
|
||||
@@ -1118,7 +1119,6 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
|
||||
freqtrade.enter_positions()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = None
|
||||
trade.is_open = True
|
||||
trades = [trade]
|
||||
@@ -1163,7 +1163,6 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = None
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
@@ -1174,7 +1173,6 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
# should do nothing and return false
|
||||
stop_order_dict.update({'id': "102"})
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = "102"
|
||||
trade.orders.append(
|
||||
Order(
|
||||
@@ -1198,7 +1196,6 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
# should set a stoploss immediately and return False
|
||||
caplog.clear()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = "102"
|
||||
|
||||
canceled_stoploss_order = MagicMock(return_value={'id': '103_1', 'status': 'canceled'})
|
||||
@@ -1223,7 +1220,6 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = "104"
|
||||
trade.orders.append(Order(
|
||||
ft_order_side='stoploss',
|
||||
@@ -1351,7 +1347,6 @@ def test_handle_stoploss_on_exchange_partial(
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = None
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
@@ -1410,7 +1405,6 @@ def test_handle_stoploss_on_exchange_partial_cancel_here(
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = None
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
@@ -1468,8 +1462,8 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
|
||||
'last': 1.9
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': enter_order['id']},
|
||||
{'id': exit_order['id']},
|
||||
enter_order,
|
||||
exit_order,
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
@@ -1485,7 +1479,6 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade.is_short == is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = "100"
|
||||
trade.orders.append(
|
||||
Order(
|
||||
@@ -1663,7 +1656,6 @@ def test_handle_stoploss_on_exchange_trailing(
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = '100'
|
||||
trade.stoploss_last_update = dt_now() - timedelta(minutes=20)
|
||||
trade.orders.append(
|
||||
@@ -1793,7 +1785,6 @@ def test_handle_stoploss_on_exchange_trailing_error(
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = "abcd"
|
||||
trade.stop_loss = 0.2
|
||||
trade.stoploss_last_update = (dt_now() - timedelta(minutes=601)).replace(tzinfo=None)
|
||||
@@ -1873,8 +1864,8 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
||||
'last': 1.9
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': enter_order['id']},
|
||||
{'id': exit_order['id']},
|
||||
enter_order,
|
||||
exit_order,
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
@@ -1907,7 +1898,6 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_short = is_short
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = '100'
|
||||
trade.stoploss_last_update = dt_now() - timedelta(minutes=601)
|
||||
trade.orders.append(
|
||||
@@ -2045,7 +2035,6 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
|
||||
freqtrade.enter_positions()
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = '100'
|
||||
trade.stoploss_last_update = dt_now()
|
||||
trade.orders.append(
|
||||
@@ -2152,7 +2141,6 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog
|
||||
|
||||
order_id = '123'
|
||||
trade = Trade(
|
||||
open_order_id=order_id,
|
||||
pair='ETH/USDT',
|
||||
fee_open=0.001,
|
||||
fee_close=0.001,
|
||||
@@ -2198,7 +2186,6 @@ def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog
|
||||
|
||||
order_id = '123'
|
||||
trade = Trade(
|
||||
open_order_id=order_id,
|
||||
pair='ETH/USDT',
|
||||
fee_open=0.001,
|
||||
fee_close=0.001,
|
||||
@@ -2217,9 +2204,9 @@ def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog
|
||||
ft_amount=trade.amount,
|
||||
ft_price=trade.open_rate,
|
||||
order_id=order_id,
|
||||
ft_is_open=False,
|
||||
|
||||
))
|
||||
trade.open_order_id = None
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
freqtrade.wallets.update()
|
||||
@@ -2248,7 +2235,6 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca
|
||||
order_id = order['id']
|
||||
|
||||
trade = Trade(
|
||||
open_order_id=order_id,
|
||||
fee_open=0.001,
|
||||
fee_close=0.001,
|
||||
open_rate=0.01,
|
||||
@@ -2272,19 +2258,17 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca
|
||||
# Test amount not modified by fee-logic
|
||||
assert not log_has_re(r'Applying fee to .*', caplog)
|
||||
caplog.clear()
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.amount == order['amount']
|
||||
|
||||
trade.open_order_id = order_id
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=0.01)
|
||||
assert trade.amount == 30.0
|
||||
# test amount modified by fee-logic
|
||||
freqtrade.update_trade_state(trade, order_id)
|
||||
assert trade.amount == 29.99
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
# Assert we call handle_trade() if trade is feasible for execution
|
||||
freqtrade.update_trade_state(trade, order_id)
|
||||
|
||||
@@ -2328,7 +2312,6 @@ def test_update_trade_state_withorderdict(
|
||||
open_date=dt_now(),
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_order_id=order_id,
|
||||
is_open=True,
|
||||
leverage=1,
|
||||
is_short=is_short,
|
||||
@@ -2361,15 +2344,15 @@ def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit
|
||||
|
||||
# TODO: should not be magicmock
|
||||
trade = MagicMock()
|
||||
trade.open_order_id = '123'
|
||||
trade.amount = 123
|
||||
open_order_id = '123'
|
||||
|
||||
# Test raise of OperationalException exception
|
||||
mocker.patch(
|
||||
'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||
side_effect=DependencyException()
|
||||
)
|
||||
freqtrade.update_trade_state(trade, trade.open_order_id)
|
||||
freqtrade.update_trade_state(trade, open_order_id)
|
||||
assert log_has('Could not update trade amount: ', caplog)
|
||||
|
||||
|
||||
@@ -2379,13 +2362,13 @@ def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) ->
|
||||
|
||||
# TODO: should not be magicmock
|
||||
trade = MagicMock()
|
||||
trade.open_order_id = '123'
|
||||
open_order_id = '123'
|
||||
|
||||
# Test raise of OperationalException exception
|
||||
grm_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", MagicMock())
|
||||
freqtrade.update_trade_state(trade, trade.open_order_id)
|
||||
freqtrade.update_trade_state(trade, open_order_id)
|
||||
assert grm_mock.call_count == 0
|
||||
assert log_has(f'Unable to fetch order {trade.open_order_id}: ', caplog)
|
||||
assert log_has(f'Unable to fetch order {open_order_id}: ', caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -2413,7 +2396,6 @@ def test_update_trade_state_sell(
|
||||
fee_open=0.0025,
|
||||
fee_close=0.0025,
|
||||
open_date=dt_now(),
|
||||
open_order_id=open_order['id'],
|
||||
is_open=True,
|
||||
interest_rate=0.0005,
|
||||
leverage=1,
|
||||
@@ -2425,7 +2407,7 @@ def test_update_trade_state_sell(
|
||||
order = Order.parse_from_ccxt_object(open_order, 'LTC/ETH', exit_side(is_short))
|
||||
trade.orders.append(order)
|
||||
assert order.status == 'open'
|
||||
freqtrade.update_trade_state(trade, trade.open_order_id, l_order)
|
||||
freqtrade.update_trade_state(trade, trade.open_orders_ids[-1], l_order)
|
||||
assert trade.amount == l_order['amount']
|
||||
# Wallet needs to be updated after closing a limit-sell order to reenable buying
|
||||
assert wallet_mock.call_count == 1
|
||||
@@ -2475,7 +2457,7 @@ def test_handle_trade(
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_short=is_short,
|
||||
exit_long=not is_short, exit_tag='sell_signal1')
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.open_order_id == exit_order['id']
|
||||
assert trade.open_orders_ids[-1] == exit_order['id']
|
||||
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
trade.orders[-1].ft_is_open = False
|
||||
@@ -2706,7 +2688,7 @@ def test_manage_open_orders_entry_usercustom(
|
||||
) -> None:
|
||||
|
||||
old_order = limit_sell_order_old if is_short else limit_buy_order_old
|
||||
old_order['id'] = open_trade.open_order_id
|
||||
old_order['id'] = open_trade.open_orders_ids[0]
|
||||
|
||||
default_conf_usdt["unfilledtimeout"] = {"entry": 1400, "exit": 30}
|
||||
|
||||
@@ -2741,7 +2723,11 @@ def test_manage_open_orders_entry_usercustom(
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_is_open.is_(True))
|
||||
.where(Order.ft_order_side != "stoploss")
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 1
|
||||
assert freqtrade.strategy.check_entry_timeout.call_count == 1
|
||||
@@ -2750,7 +2736,11 @@ def test_manage_open_orders_entry_usercustom(
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_is_open.is_(True))
|
||||
.where(Order.ft_order_side != "stoploss")
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 1
|
||||
assert freqtrade.strategy.check_entry_timeout.call_count == 1
|
||||
@@ -2761,7 +2751,11 @@ def test_manage_open_orders_entry_usercustom(
|
||||
assert cancel_order_wr_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 2
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_is_open.is_(True))
|
||||
.where(Order.ft_order_side != "stoploss")
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 0
|
||||
assert freqtrade.strategy.check_entry_timeout.call_count == 1
|
||||
@@ -2774,7 +2768,7 @@ def test_manage_open_orders_entry(
|
||||
) -> None:
|
||||
old_order = limit_sell_order_old if is_short else limit_buy_order_old
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
open_trade.open_order_id = old_order['id']
|
||||
|
||||
order = Order.parse_from_ccxt_object(old_order, 'mocked', 'buy')
|
||||
open_trade.orders[0] = order
|
||||
limit_buy_cancel = deepcopy(old_order)
|
||||
@@ -2801,7 +2795,11 @@ def test_manage_open_orders_entry(
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 2
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_is_open.is_(True))
|
||||
.where(Order.ft_order_side != "stoploss")
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 0
|
||||
# Custom user entry-timeout is never called
|
||||
@@ -2817,7 +2815,7 @@ def test_adjust_entry_cancel(
|
||||
) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
old_order = limit_sell_order_old if is_short else limit_buy_order_old
|
||||
old_order['id'] = open_trade.open_order_id
|
||||
old_order['id'] = open_trade.open_orders[0].order_id
|
||||
limit_buy_cancel = deepcopy(old_order)
|
||||
limit_buy_cancel['status'] = 'canceled'
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
|
||||
@@ -2840,7 +2838,10 @@ def test_adjust_entry_cancel(
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=None)
|
||||
freqtrade.manage_open_orders()
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
|
||||
assert len(trades) == 0
|
||||
assert len(Order.session.scalars(select(Order)).all()) == 0
|
||||
assert log_has_re(
|
||||
@@ -2859,7 +2860,7 @@ def test_adjust_entry_maintain_replace(
|
||||
) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
old_order = limit_sell_order_old if is_short else limit_buy_order_old
|
||||
old_order['id'] = open_trade.open_order_id
|
||||
old_order['id'] = open_trade.open_orders_ids[0]
|
||||
limit_buy_cancel = deepcopy(old_order)
|
||||
limit_buy_cancel['status'] = 'canceled'
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
|
||||
@@ -2868,7 +2869,8 @@ def test_adjust_entry_maintain_replace(
|
||||
fetch_ticker=ticker_usdt,
|
||||
fetch_order=MagicMock(return_value=old_order),
|
||||
cancel_order_with_result=cancel_order_mock,
|
||||
get_fee=fee
|
||||
get_fee=fee,
|
||||
_dry_is_price_crossed=MagicMock(return_value=False),
|
||||
)
|
||||
|
||||
open_trade.is_short = is_short
|
||||
@@ -2882,7 +2884,10 @@ def test_adjust_entry_maintain_replace(
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=old_order['price'])
|
||||
freqtrade.manage_open_orders()
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_is_open.is_(True))
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
assert len(trades) == 1
|
||||
assert len(Order.get_open_orders()) == 1
|
||||
# Entry adjustment is called
|
||||
@@ -2891,9 +2896,16 @@ def test_adjust_entry_maintain_replace(
|
||||
# Check that order is replaced
|
||||
freqtrade.get_valid_enter_price_and_stake = MagicMock(return_value={100, 10, 1})
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234)
|
||||
|
||||
freqtrade.manage_open_orders()
|
||||
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 1
|
||||
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_is_open.is_(True))
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
assert len(trades) == 1
|
||||
nb_all_orders = len(Order.session.scalars(select(Order)).all())
|
||||
assert nb_all_orders == 2
|
||||
@@ -2917,6 +2929,8 @@ def test_check_handle_cancelled_buy(
|
||||
cancel_order_mock = MagicMock()
|
||||
patch_exchange(mocker)
|
||||
old_order.update({"status": "canceled", 'filled': 0.0})
|
||||
old_order['side'] = 'buy' if is_short else 'sell'
|
||||
old_order['id'] = open_trade.open_orders[0].order_id
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
fetch_ticker=ticker_usdt,
|
||||
@@ -2925,7 +2939,6 @@ def test_check_handle_cancelled_buy(
|
||||
get_fee=fee
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
open_trade.orders = []
|
||||
open_trade.is_short = is_short
|
||||
Trade.session.add(open_trade)
|
||||
Trade.commit()
|
||||
@@ -2935,10 +2948,13 @@ def test_check_handle_cancelled_buy(
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 2
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_is_open.is_(True))
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
assert len(trades) == 0
|
||||
assert log_has_re(
|
||||
f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog)
|
||||
exit_name = 'Buy' if is_short else 'Sell'
|
||||
assert log_has_re(f"{exit_name} order cancelled on exchange for Trade.*", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -2966,10 +2982,7 @@ def test_manage_open_orders_buy_exception(
|
||||
freqtrade.manage_open_orders()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 1
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 1
|
||||
assert len(open_trade.open_orders) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -2978,7 +2991,7 @@ def test_manage_open_orders_exit_usercustom(
|
||||
is_short, open_trade_usdt, caplog
|
||||
) -> None:
|
||||
default_conf_usdt["unfilledtimeout"] = {"entry": 1440, "exit": 1440, "exit_timeout_count": 1}
|
||||
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
||||
|
||||
if is_short:
|
||||
limit_sell_order_old['side'] = 'buy'
|
||||
open_trade_usdt.is_short = is_short
|
||||
@@ -3035,13 +3048,10 @@ def test_manage_open_orders_exit_usercustom(
|
||||
assert rpc_mock.call_count == 2
|
||||
assert freqtrade.strategy.check_exit_timeout.call_count == 1
|
||||
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
# cancelling didn't succeed - order-id remains open.
|
||||
assert trade.open_order_id is not None
|
||||
|
||||
# 2nd canceled trade - Fail execute exit
|
||||
caplog.clear()
|
||||
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
||||
|
||||
mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit',
|
||||
side_effect=DependencyException)
|
||||
@@ -3051,7 +3061,6 @@ def test_manage_open_orders_exit_usercustom(
|
||||
et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit')
|
||||
caplog.clear()
|
||||
# 2nd canceled trade ...
|
||||
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
||||
|
||||
# If cancelling fails - no emergency exit!
|
||||
with patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit', return_value=False):
|
||||
@@ -3069,7 +3078,7 @@ def test_manage_open_orders_exit(
|
||||
) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
limit_sell_order_old['id'] = open_trade_usdt.open_order_id
|
||||
limit_sell_order_old['id'] = '123456789_exit'
|
||||
limit_sell_order_old['side'] = 'buy' if is_short else 'sell'
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
@@ -3111,7 +3120,7 @@ def test_check_handle_cancelled_exit(
|
||||
cancel_order_mock = MagicMock()
|
||||
limit_sell_order_old.update({"status": "canceled", 'filled': 0.0})
|
||||
limit_sell_order_old['side'] = 'buy' if is_short else 'sell'
|
||||
limit_sell_order_old['id'] = open_trade_usdt.open_order_id
|
||||
limit_sell_order_old['id'] = open_trade_usdt.open_orders[0].order_id
|
||||
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
@@ -3148,7 +3157,8 @@ def test_manage_open_orders_partial(
|
||||
open_trade.is_short = is_short
|
||||
open_trade.leverage = leverage
|
||||
open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy'
|
||||
limit_buy_order_old_partial['id'] = open_trade.open_order_id
|
||||
|
||||
limit_buy_order_old_partial['id'] = open_trade.orders[0].order_id
|
||||
limit_buy_order_old_partial['side'] = 'sell' if is_short else 'buy'
|
||||
limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
|
||||
limit_buy_canceled['status'] = 'canceled'
|
||||
@@ -3172,11 +3182,13 @@ def test_manage_open_orders_partial(
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 3
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
).all()
|
||||
assert len(trades) == 1
|
||||
assert trades[0].amount == 23.0
|
||||
assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount / leverage
|
||||
assert trades[0].stake_amount != prior_stake
|
||||
assert not trades[0].has_open_orders
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -3188,8 +3200,8 @@ def test_manage_open_orders_partial_fee(
|
||||
open_trade.is_short = is_short
|
||||
open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy'
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
limit_buy_order_old_partial['id'] = open_trade.open_order_id
|
||||
limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id
|
||||
limit_buy_order_old_partial['id'] = open_trade.orders[0].order_id
|
||||
limit_buy_order_old_partial_canceled['id'] = open_trade.open_orders_ids[0]
|
||||
limit_buy_order_old_partial['side'] = 'sell' if is_short else 'buy'
|
||||
limit_buy_order_old_partial_canceled['side'] = 'sell' if is_short else 'buy'
|
||||
|
||||
@@ -3220,12 +3232,14 @@ def test_manage_open_orders_partial_fee(
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 3
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
assert len(trades) == 1
|
||||
# Verify that trade has been updated
|
||||
assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
|
||||
limit_buy_order_old_partial['remaining']) - 0.023
|
||||
assert trades[0].open_order_id is None
|
||||
assert not trades[0].has_open_orders
|
||||
assert trades[0].fee_updated(open_trade.entry_side)
|
||||
assert pytest.approx(trades[0].fee_open) == 0.001
|
||||
|
||||
@@ -3239,8 +3253,8 @@ def test_manage_open_orders_partial_except(
|
||||
open_trade.is_short = is_short
|
||||
open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy'
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id
|
||||
limit_buy_order_old_partial['id'] = open_trade.open_order_id
|
||||
limit_buy_order_old_partial_canceled['id'] = open_trade.open_orders_ids[0]
|
||||
limit_buy_order_old_partial['id'] = open_trade.open_orders_ids[0]
|
||||
if is_short:
|
||||
limit_buy_order_old_partial['side'] = 'sell'
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
|
||||
@@ -3271,13 +3285,14 @@ def test_manage_open_orders_partial_except(
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 3
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
|
||||
select(Trade)
|
||||
).all()
|
||||
assert len(trades) == 1
|
||||
# Verify that trade has been updated
|
||||
|
||||
assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
|
||||
limit_buy_order_old_partial['remaining'])
|
||||
assert trades[0].open_order_id is None
|
||||
assert not trades[0].has_open_orders
|
||||
assert trades[0].fee_open == fee()
|
||||
|
||||
|
||||
@@ -3335,32 +3350,34 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
|
||||
l_order['filled'] = 0.0
|
||||
l_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_enter(trade, l_order, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
caplog.clear()
|
||||
l_order['filled'] = 0.01
|
||||
assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unexitable.*", caplog)
|
||||
|
||||
caplog.clear()
|
||||
cancel_order_mock.reset_mock()
|
||||
l_order['filled'] = 2
|
||||
assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], 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)
|
||||
trade.open_order_id = 'some_open_order'
|
||||
|
||||
mocker.patch(f'{EXMS}.cancel_order_with_result', cancel_order_mock)
|
||||
assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
|
||||
assert log_has_re(r"Order .* for .* not cancelled.", caplog)
|
||||
# min_pair_stake empty should not crash
|
||||
mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=None)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_order[entry_side(is_short)], reason)
|
||||
assert not freqtrade.handle_cancel_enter(
|
||||
trade, limit_order[entry_side(is_short)], trade.open_orders_ids[0], reason
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -3381,7 +3398,9 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho
|
||||
trade = mock_trade_usdt_4(fee, is_short)
|
||||
Trade.session.add(trade)
|
||||
Trade.commit()
|
||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
|
||||
assert freqtrade.handle_cancel_enter(
|
||||
trade, limit_buy_order_canceled_empty, trade.open_orders_ids[0], reason
|
||||
)
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert log_has_re(
|
||||
f'{trade.entry_side.capitalize()} order fully cancelled. '
|
||||
@@ -3418,7 +3437,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order
|
||||
l_order['filled'] = 0.0
|
||||
l_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_enter(trade, l_order, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
@@ -3426,7 +3445,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order
|
||||
order = deepcopy(l_order)
|
||||
order['status'] = 'canceled'
|
||||
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
|
||||
assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
@@ -3456,7 +3475,6 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee, is_short,
|
||||
amount=amount * leverage,
|
||||
exchange='binance',
|
||||
open_rate=entry_price,
|
||||
open_order_id="sell_123456",
|
||||
open_date=dt_now() - timedelta(days=2),
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
@@ -3509,26 +3527,26 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee, is_short,
|
||||
'status': "open"}
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
send_msg_mock.reset_mock()
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
assert freqtrade.handle_cancel_exit(trade, order, order['id'], reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert send_msg_mock.call_count == 1
|
||||
assert trade.close_rate is None
|
||||
assert trade.exit_reason is None
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
|
||||
send_msg_mock.reset_mock()
|
||||
|
||||
# Partial exit - below exit threshold
|
||||
order['amount'] = amount * leverage
|
||||
order['filled'] = amount * 0.99 * leverage
|
||||
assert not freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
assert not freqtrade.handle_cancel_exit(trade, order, order['id'], reason)
|
||||
# Assert cancel_order was not called (callcount remains unchanged)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert send_msg_mock.call_count == 1
|
||||
assert (send_msg_mock.call_args_list[0][0][0]['reason']
|
||||
== CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'])
|
||||
|
||||
assert not freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
assert not freqtrade.handle_cancel_exit(trade, order, order['id'], reason)
|
||||
|
||||
assert (send_msg_mock.call_args_list[0][0][0]['reason']
|
||||
== CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'])
|
||||
@@ -3540,7 +3558,7 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee, is_short,
|
||||
send_msg_mock.reset_mock()
|
||||
|
||||
order['filled'] = amount * 0.5 * leverage
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
assert freqtrade.handle_cancel_exit(trade, order, order['id'], reason)
|
||||
assert send_msg_mock.call_count == 1
|
||||
assert (send_msg_mock.call_args_list[0][0][0]['reason']
|
||||
== CANCEL_REASON['PARTIALLY_FILLED'])
|
||||
@@ -3556,17 +3574,16 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
|
||||
|
||||
# TODO: should not be magicmock
|
||||
trade = MagicMock()
|
||||
trade.open_order_id = '125'
|
||||
order_id = '125'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
order = {'remaining': 1,
|
||||
'id': '125',
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
assert not freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
assert not freqtrade.handle_cancel_exit(trade, order, order_id, reason)
|
||||
|
||||
# mocker.patch(f'{EXMS}.cancel_order_with_result', return_value=order)
|
||||
# assert not freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
# assert trade.open_order_id == '125'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short, open_rate, amt", [
|
||||
@@ -4013,7 +4030,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
|
||||
freqtrade.exit_positions(trades)
|
||||
assert trade
|
||||
assert trade.stoploss_order_id == '123'
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
|
||||
# Assuming stoploss on exchange is hit
|
||||
# stoploss_order_id should become None
|
||||
@@ -4308,7 +4325,6 @@ def test__safe_exit_amount(default_conf_usdt, fee, caplog, mocker, amount_wallet
|
||||
amount=amount,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456",
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
)
|
||||
@@ -4651,7 +4667,6 @@ def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fe
|
||||
open_rate=0.245441,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
@@ -4679,7 +4694,6 @@ def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_ord
|
||||
open_rate=0.245441,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
@@ -4703,7 +4717,6 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
|
||||
open_rate=0.245441,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
@@ -4756,7 +4769,6 @@ def test_get_real_amount(
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
@@ -4803,8 +4815,7 @@ def test_get_real_amount_multi(
|
||||
exchange='binance',
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
open_rate=0.245441
|
||||
)
|
||||
|
||||
# Fake markets entry to enable fee parsing
|
||||
@@ -4849,7 +4860,6 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
@@ -4871,7 +4881,6 @@ def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_dou
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
@@ -4898,7 +4907,6 @@ def test_get_real_amount_wrong_amount(default_conf_usdt, trades_for_order, buy_o
|
||||
open_rate=0.245441,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
@@ -4923,7 +4931,6 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
@@ -4942,7 +4949,6 @@ def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
|
||||
open_rate=0.245441,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
)
|
||||
order = {
|
||||
'id': 'mocked_order',
|
||||
@@ -5002,8 +5008,7 @@ def test_get_real_amount_in_point(default_conf_usdt, buy_order_fee, fee, mocker,
|
||||
exchange='binance',
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
open_rate=0.245441
|
||||
)
|
||||
limit_buy_order_usdt['amount'] = amount
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
@@ -5045,7 +5050,6 @@ def test_apply_fee_conditional(default_conf_usdt, fee, mocker, caplog,
|
||||
open_rate=0.245441,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
)
|
||||
order = Order(
|
||||
ft_order_side='buy',
|
||||
@@ -5083,8 +5087,7 @@ def test_apply_fee_conditional_multibuy(default_conf_usdt, fee, mocker, caplog,
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
fee_close=fee.return_value
|
||||
)
|
||||
# One closed order
|
||||
order = Order(
|
||||
@@ -5558,7 +5561,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
|
||||
return_value={'status': 'open'})
|
||||
|
||||
def reset_open_orders(trade):
|
||||
trade.open_order_id = None
|
||||
|
||||
trade.stoploss_order_id = None
|
||||
trade.is_short = is_short
|
||||
|
||||
@@ -5570,7 +5573,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
|
||||
# No open order
|
||||
trade = trades[1]
|
||||
reset_open_orders(trade)
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.stoploss_order_id is None
|
||||
|
||||
freqtrade.handle_insufficient_funds(trade)
|
||||
@@ -5580,7 +5583,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
|
||||
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 not trade.has_open_orders
|
||||
assert trade.stoploss_order_id is None
|
||||
|
||||
caplog.clear()
|
||||
@@ -5589,7 +5592,9 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
|
||||
# Open buy order
|
||||
trade = trades[3]
|
||||
reset_open_orders(trade)
|
||||
assert trade.open_order_id is None
|
||||
|
||||
# This part in not relevant anymore
|
||||
# assert not trade.has_open_orders
|
||||
assert trade.stoploss_order_id is None
|
||||
|
||||
freqtrade.handle_insufficient_funds(trade)
|
||||
@@ -5598,7 +5603,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
|
||||
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.has_open_orders
|
||||
assert trade.stoploss_order_id is None
|
||||
|
||||
caplog.clear()
|
||||
@@ -5607,7 +5612,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
|
||||
# Open stoploss order
|
||||
trade = trades[4]
|
||||
reset_open_orders(trade)
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.stoploss_order_id is None
|
||||
|
||||
freqtrade.handle_insufficient_funds(trade)
|
||||
@@ -5616,7 +5621,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
|
||||
assert mock_fo.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 not trade.has_open_orders
|
||||
assert trade.stoploss_order_id is not None
|
||||
|
||||
caplog.clear()
|
||||
@@ -5626,7 +5631,8 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
|
||||
# Open sell order
|
||||
trade = trades[5]
|
||||
reset_open_orders(trade)
|
||||
assert trade.open_order_id is None
|
||||
# This part in not relevant anymore
|
||||
# assert not trade.has_open_orders
|
||||
assert trade.stoploss_order_id is None
|
||||
|
||||
freqtrade.handle_insufficient_funds(trade)
|
||||
@@ -5635,7 +5641,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap
|
||||
assert mock_fo.call_count == 1
|
||||
assert mock_uts.call_count == 1
|
||||
# sell-orderid is "refound" and added to the trade
|
||||
assert trade.open_order_id == order['id']
|
||||
assert trade.open_orders_ids[0] == order['id']
|
||||
assert trade.stoploss_order_id is None
|
||||
|
||||
caplog.clear()
|
||||
@@ -5662,10 +5668,7 @@ def test_handle_onexchange_order(mocker, default_conf_usdt, limit_order, is_shor
|
||||
exit_order,
|
||||
])
|
||||
|
||||
order_id = entry_order['id']
|
||||
|
||||
trade = Trade(
|
||||
open_order_id=order_id,
|
||||
pair='ETH/USDT',
|
||||
fee_open=0.001,
|
||||
fee_close=0.001,
|
||||
@@ -5989,7 +5992,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.open_rate == 11
|
||||
assert trade.stake_amount == 110
|
||||
|
||||
@@ -5999,7 +6002,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.open_rate == 11
|
||||
assert trade.stake_amount == 110
|
||||
assert not trade.fee_updated('buy')
|
||||
@@ -6009,7 +6012,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.open_rate == 11
|
||||
assert trade.stake_amount == 110
|
||||
assert not trade.fee_updated('buy')
|
||||
@@ -6037,7 +6040,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
assert len(orders) == 2
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.open_order_id == '651'
|
||||
assert '651' in trade.open_orders_ids
|
||||
assert trade.open_rate == 11
|
||||
assert trade.amount == 10
|
||||
assert trade.stake_amount == 110
|
||||
@@ -6074,7 +6077,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
# Assert trade is as expected
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.open_order_id == '651'
|
||||
assert '651' in trade.open_orders_ids
|
||||
assert trade.open_rate == 11
|
||||
assert trade.amount == 10
|
||||
assert trade.stake_amount == 110
|
||||
@@ -6111,7 +6114,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
# Assert trade is as expected (averaged dca)
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert pytest.approx(trade.open_rate) == 9.90909090909
|
||||
assert trade.amount == 22
|
||||
assert pytest.approx(trade.stake_amount) == 218
|
||||
@@ -6153,7 +6156,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
# Assert trade is as expected (averaged dca)
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert pytest.approx(trade.open_rate) == 8.729729729729
|
||||
assert trade.amount == 37
|
||||
assert trade.stake_amount == 323
|
||||
@@ -6191,7 +6194,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
# Assert trade is as expected (averaged dca)
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.is_open
|
||||
assert trade.amount == 22
|
||||
assert trade.stake_amount == 192.05405405405406
|
||||
@@ -6268,7 +6271,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.open_rate == bid
|
||||
assert trade.stake_amount == bid * amount
|
||||
|
||||
@@ -6278,7 +6281,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.open_rate == bid
|
||||
assert trade.stake_amount == bid * amount
|
||||
assert not trade.fee_updated(trade.entry_side)
|
||||
@@ -6288,7 +6291,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.open_rate == bid
|
||||
assert trade.stake_amount == bid * amount
|
||||
assert not trade.fee_updated(trade.entry_side)
|
||||
@@ -6323,7 +6326,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
|
||||
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.amount == 50
|
||||
assert trade.open_rate == 11
|
||||
assert trade.stake_amount == 550
|
||||
@@ -6365,7 +6368,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
|
||||
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.amount == 50
|
||||
assert trade.open_rate == 11
|
||||
assert trade.stake_amount == 550
|
||||
@@ -6465,7 +6468,7 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None:
|
||||
assert trade
|
||||
if idx < len(data) - 1:
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.amount == result[0]
|
||||
assert trade.open_rate == result[1]
|
||||
assert trade.stake_amount == result[2]
|
||||
@@ -6478,7 +6481,7 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None:
|
||||
|
||||
trade = Trade.session.scalars(select(Trade)).first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert trade.is_open is False
|
||||
|
||||
|
||||
|
||||
@@ -103,7 +103,6 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
|
||||
trade.orders.append(oobj)
|
||||
trade.stoploss_order_id = f"stop{idx}"
|
||||
trade.open_order_id = None
|
||||
|
||||
n = freqtrade.exit_positions(trades)
|
||||
assert n == 2
|
||||
@@ -194,8 +193,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
|
||||
|
||||
for trade in trades:
|
||||
assert pytest.approx(trade.stake_amount) == result1
|
||||
# Reset trade open order id's
|
||||
trade.open_order_id = None
|
||||
|
||||
trades = Trade.get_open_trades()
|
||||
assert len(trades) == 5
|
||||
bals = freqtrade.wallets.get_all_balances()
|
||||
@@ -386,7 +384,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert len(Trade.get_trades().all()) == 1
|
||||
trade: Trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert trade.open_order_id is not None
|
||||
assert trade.has_open_orders
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.open_rate == 1.96
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
@@ -399,7 +397,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert trade.open_order_id is not None
|
||||
assert trade.has_open_orders
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
|
||||
# Cancel order and place new one
|
||||
@@ -407,7 +405,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
assert trade.open_order_id is not None
|
||||
assert trade.has_open_orders
|
||||
# Open rate is not adjusted yet
|
||||
assert trade.open_rate == 1.96
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
@@ -421,7 +419,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
# Open rate is not adjusted yet
|
||||
assert trade.open_rate == 1.99
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
@@ -438,7 +436,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 3
|
||||
assert trade.open_order_id is not None
|
||||
assert trade.has_open_orders
|
||||
assert trade.open_rate == 1.99
|
||||
assert trade.orders[-1].price == 1.96
|
||||
assert trade.orders[-1].cost == 120 * leverage
|
||||
@@ -449,7 +447,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 4
|
||||
assert trade.open_order_id is not None
|
||||
assert trade.has_open_orders
|
||||
assert trade.open_rate == 1.99
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.orders[-1].price == 1.95
|
||||
@@ -463,7 +461,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 4
|
||||
assert trade.open_order_id is None
|
||||
assert not trade.has_open_orders
|
||||
assert pytest.approx(trade.open_rate) == 1.963153456
|
||||
assert trade.orders[-1].price == 1.95
|
||||
assert pytest.approx(trade.orders[-1].cost) == 120 * leverage
|
||||
@@ -522,7 +520,11 @@ def test_dca_order_adjust_entry_replace_fails(
|
||||
freqtrade.enter_positions()
|
||||
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_not(None))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_is_open.is_(True))
|
||||
.where(Order.ft_order_side != "stoploss")
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
assert len(trades) == 1
|
||||
|
||||
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False)
|
||||
@@ -539,14 +541,22 @@ def test_dca_order_adjust_entry_replace_fails(
|
||||
|
||||
assert freqtrade.strategy.adjust_trade_position.call_count == 1
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_not(None))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_is_open.is_(True))
|
||||
.where(Order.ft_order_side != "stoploss")
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
assert len(trades) == 2
|
||||
|
||||
# We now have 2 orders open
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=2.05)
|
||||
freqtrade.manage_open_orders()
|
||||
trades = Trade.session.scalars(
|
||||
select(Trade).filter(Trade.open_order_id.is_not(None))).all()
|
||||
select(Trade)
|
||||
.where(Order.ft_is_open.is_(True))
|
||||
.where(Order.ft_order_side != "stoploss")
|
||||
.where(Order.ft_trade_id == Trade.id)
|
||||
).all()
|
||||
assert len(trades) == 2
|
||||
assert len(Order.get_open_orders()) == 2
|
||||
# Entry adjustment is called
|
||||
|
||||
Reference in New Issue
Block a user