Merge branch 'develop' into feature_keyval_storage
Update rpc/telegram to use MAX_MESSAGE_LENGTH.
This commit is contained in:
+61
-47
@@ -78,9 +78,21 @@ def get_args(args):
|
||||
|
||||
|
||||
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
|
||||
def get_mock_coro(return_value):
|
||||
# TODO: This should be replaced with AsyncMock once support for python 3.7 is dropped.
|
||||
def get_mock_coro(return_value=None, side_effect=None):
|
||||
async def mock_coro(*args, **kwargs):
|
||||
return return_value
|
||||
if side_effect:
|
||||
if isinstance(side_effect, list):
|
||||
effect = side_effect.pop(0)
|
||||
else:
|
||||
effect = side_effect
|
||||
if isinstance(effect, Exception):
|
||||
raise effect
|
||||
if callable(effect):
|
||||
return effect(*args, **kwargs)
|
||||
return effect
|
||||
else:
|
||||
return return_value
|
||||
|
||||
return Mock(wraps=mock_coro)
|
||||
|
||||
@@ -100,11 +112,8 @@ def patch_exchange(
|
||||
mock_supported_modes=True
|
||||
) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_config', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id))
|
||||
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title()))
|
||||
mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2))
|
||||
@@ -139,7 +148,7 @@ def get_patched_exchange(mocker, config, api_mock=None, id='binance',
|
||||
patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes)
|
||||
config['exchange']['name'] = id
|
||||
try:
|
||||
exchange = ExchangeResolver.load_exchange(id, config)
|
||||
exchange = ExchangeResolver.load_exchange(id, config, load_leverage_tiers=True)
|
||||
except ImportError:
|
||||
exchange = Exchange(config)
|
||||
return exchange
|
||||
@@ -1618,8 +1627,8 @@ def limit_buy_order_open():
|
||||
'timestamp': arrow.utcnow().int_timestamp * 1000,
|
||||
'datetime': arrow.utcnow().isoformat(),
|
||||
'price': 0.00001099,
|
||||
'average': 0.00001099,
|
||||
'amount': 90.99181073,
|
||||
'average': None,
|
||||
'filled': 0.0,
|
||||
'cost': 0.0009999,
|
||||
'remaining': 90.99181073,
|
||||
@@ -1682,6 +1691,7 @@ def limit_buy_order_old_partial():
|
||||
'price': 0.00001099,
|
||||
'amount': 90.99181073,
|
||||
'filled': 23.0,
|
||||
'cost': 90.99181073 * 23.0,
|
||||
'remaining': 67.99181073,
|
||||
'status': 'open'
|
||||
}
|
||||
@@ -2599,7 +2609,7 @@ def open_trade_usdt():
|
||||
pair='ADA/USDT',
|
||||
open_rate=2.0,
|
||||
exchange='binance',
|
||||
open_order_id='123456789',
|
||||
open_order_id='123456789_exit',
|
||||
amount=30.0,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
@@ -2624,6 +2634,23 @@ def open_trade_usdt():
|
||||
cost=trade.open_rate * trade.amount,
|
||||
order_date=trade.open_date,
|
||||
order_filled_date=trade.open_date,
|
||||
),
|
||||
Order(
|
||||
ft_order_side='exit',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
order_id='123456789_exit',
|
||||
status="open",
|
||||
symbol=trade.pair,
|
||||
order_type="limit",
|
||||
side="sell",
|
||||
price=trade.open_rate,
|
||||
average=trade.open_rate,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
cost=trade.open_rate * trade.amount,
|
||||
order_date=trade.open_date,
|
||||
order_filled_date=trade.open_date,
|
||||
)
|
||||
]
|
||||
return trade
|
||||
@@ -2790,6 +2817,7 @@ def limit_buy_order_usdt_open():
|
||||
'datetime': arrow.utcnow().isoformat(),
|
||||
'timestamp': arrow.utcnow().int_timestamp * 1000,
|
||||
'price': 2.00,
|
||||
'average': 2.00,
|
||||
'amount': 30.0,
|
||||
'filled': 0.0,
|
||||
'cost': 60.0,
|
||||
@@ -3153,60 +3181,46 @@ def leverage_tiers():
|
||||
"AAVE/USDT": [
|
||||
{
|
||||
'min': 0,
|
||||
'max': 50000,
|
||||
'max': 5000,
|
||||
'mmr': 0.01,
|
||||
'lev': 50,
|
||||
'maintAmt': 0.0
|
||||
},
|
||||
{
|
||||
'min': 50000,
|
||||
'max': 250000,
|
||||
'min': 5000,
|
||||
'max': 25000,
|
||||
'mmr': 0.02,
|
||||
'lev': 25,
|
||||
'maintAmt': 500.0
|
||||
'maintAmt': 75.0
|
||||
},
|
||||
{
|
||||
'min': 25000,
|
||||
'max': 100000,
|
||||
'mmr': 0.05,
|
||||
'lev': 10,
|
||||
'maintAmt': 700.0
|
||||
},
|
||||
{
|
||||
'min': 100000,
|
||||
'max': 250000,
|
||||
'mmr': 0.1,
|
||||
'lev': 5,
|
||||
'maintAmt': 5700.0
|
||||
},
|
||||
{
|
||||
'min': 250000,
|
||||
'max': 1000000,
|
||||
'mmr': 0.05,
|
||||
'lev': 10,
|
||||
'maintAmt': 8000.0
|
||||
},
|
||||
{
|
||||
'min': 1000000,
|
||||
'max': 2000000,
|
||||
'mmr': 0.1,
|
||||
'lev': 5,
|
||||
'maintAmt': 58000.0
|
||||
},
|
||||
{
|
||||
'min': 2000000,
|
||||
'max': 5000000,
|
||||
'mmr': 0.125,
|
||||
'lev': 4,
|
||||
'maintAmt': 108000.0
|
||||
},
|
||||
{
|
||||
'min': 5000000,
|
||||
'max': 10000000,
|
||||
'mmr': 0.1665,
|
||||
'lev': 3,
|
||||
'maintAmt': 315500.0
|
||||
'lev': 2,
|
||||
'maintAmt': 11950.0
|
||||
},
|
||||
{
|
||||
'min': 10000000,
|
||||
'max': 20000000,
|
||||
'mmr': 0.25,
|
||||
'lev': 2,
|
||||
'maintAmt': 1150500.0
|
||||
'max': 50000000,
|
||||
'mmr': 0.5,
|
||||
'lev': 1,
|
||||
'maintAmt': 386950.0
|
||||
},
|
||||
{
|
||||
"min": 20000000,
|
||||
"max": 50000000,
|
||||
"mmr": 0.5,
|
||||
"lev": 1,
|
||||
"maintAmt": 6150500.0
|
||||
}
|
||||
],
|
||||
"ADA/BUSD": [
|
||||
{
|
||||
|
||||
@@ -214,7 +214,8 @@ def mock_trade_4(fee, is_short: bool):
|
||||
open_order_id=f'prod_buy_{direc(is_short)}_12345',
|
||||
strategy='StrategyTestV3',
|
||||
timeframe=5,
|
||||
is_short=is_short
|
||||
is_short=is_short,
|
||||
stop_loss_pct=0.10
|
||||
)
|
||||
o = Order.parse_from_ccxt_object(mock_order_4(is_short), 'ETC/BTC', entry_side(is_short))
|
||||
trade.orders.append(o)
|
||||
@@ -270,7 +271,8 @@ def mock_trade_5(fee, is_short: bool):
|
||||
enter_tag='TEST1',
|
||||
stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455',
|
||||
timeframe=5,
|
||||
is_short=is_short
|
||||
is_short=is_short,
|
||||
stop_loss_pct=0.10,
|
||||
)
|
||||
o = Order.parse_from_ccxt_object(mock_order_5(is_short), 'XRP/BTC', entry_side(is_short))
|
||||
trade.orders.append(o)
|
||||
|
||||
@@ -63,7 +63,7 @@ def mock_trade_usdt_1(fee, is_short: bool):
|
||||
open_rate=10.0,
|
||||
close_rate=8.0,
|
||||
close_profit=-0.2,
|
||||
close_profit_abs=-4.0,
|
||||
close_profit_abs=-4.09,
|
||||
exchange='binance',
|
||||
strategy='SampleStrategy',
|
||||
open_order_id=f'prod_exit_1_{direc(is_short)}',
|
||||
@@ -183,7 +183,7 @@ def mock_trade_usdt_3(fee, is_short: bool):
|
||||
open_rate=1.0,
|
||||
close_rate=1.1,
|
||||
close_profit=0.1,
|
||||
close_profit_abs=9.8425,
|
||||
close_profit_abs=2.8425,
|
||||
exchange='binance',
|
||||
is_open=False,
|
||||
strategy='StrategyTestV2',
|
||||
|
||||
@@ -311,3 +311,27 @@ def test_no_exchange_mode(default_conf):
|
||||
|
||||
with pytest.raises(OperationalException, match=message):
|
||||
dp.available_pairs()
|
||||
|
||||
|
||||
def test_dp_send_msg(default_conf):
|
||||
|
||||
default_conf["runmode"] = RunMode.DRY_RUN
|
||||
|
||||
default_conf["timeframe"] = '1h'
|
||||
dp = DataProvider(default_conf, None)
|
||||
msg = 'Test message'
|
||||
dp.send_msg(msg)
|
||||
|
||||
assert msg in dp._msg_queue
|
||||
dp._msg_queue.pop()
|
||||
assert msg not in dp._msg_queue
|
||||
# Message is not resent due to caching
|
||||
dp.send_msg(msg)
|
||||
assert msg not in dp._msg_queue
|
||||
dp.send_msg(msg, always_send=True)
|
||||
assert msg in dp._msg_queue
|
||||
|
||||
default_conf["runmode"] = RunMode.BACKTEST
|
||||
dp = DataProvider(default_conf, None)
|
||||
dp.send_msg(msg, always_send=True)
|
||||
assert msg not in dp._msg_queue
|
||||
|
||||
@@ -136,7 +136,7 @@ def test_adjust(mocker, edge_conf):
|
||||
))
|
||||
|
||||
pairs = ['A/B', 'C/D', 'E/F', 'G/H']
|
||||
assert(edge.adjust(pairs) == ['E/F', 'C/D'])
|
||||
assert (edge.adjust(pairs) == ['E/F', 'C/D'])
|
||||
|
||||
|
||||
def test_stoploss(mocker, edge_conf):
|
||||
|
||||
@@ -137,7 +137,8 @@ def exchange_futures(request, exchange_conf, class_mocker):
|
||||
'freqtrade.exchange.binance.Binance.fill_leverage_tiers')
|
||||
class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees')
|
||||
class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init')
|
||||
exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True)
|
||||
exchange = ExchangeResolver.load_exchange(
|
||||
request.param, exchange_conf, validate=True, load_leverage_tiers=True)
|
||||
|
||||
yield exchange, request.param
|
||||
|
||||
@@ -153,6 +154,25 @@ class TestCCXTExchange():
|
||||
assert isinstance(markets[pair], dict)
|
||||
assert exchange.market_is_spot(markets[pair])
|
||||
|
||||
def test_has_validations(self, exchange):
|
||||
|
||||
exchange, exchangename = exchange
|
||||
|
||||
exchange.validate_ordertypes({
|
||||
'entry': 'limit',
|
||||
'exit': 'limit',
|
||||
'stoploss': 'limit',
|
||||
})
|
||||
|
||||
if exchangename == 'gateio':
|
||||
# gateio doesn't have market orders on spot
|
||||
return
|
||||
exchange.validate_ordertypes({
|
||||
'entry': 'market',
|
||||
'exit': 'market',
|
||||
'stoploss': 'market',
|
||||
})
|
||||
|
||||
def test_load_markets_futures(self, exchange_futures):
|
||||
exchange, exchangename = exchange_futures
|
||||
if not exchange:
|
||||
@@ -199,8 +219,13 @@ class TestCCXTExchange():
|
||||
l2 = exchange.fetch_l2_order_book(pair)
|
||||
assert 'asks' in l2
|
||||
assert 'bids' in l2
|
||||
assert len(l2['asks']) >= 1
|
||||
assert len(l2['bids']) >= 1
|
||||
l2_limit_range = exchange._ft_has['l2_limit_range']
|
||||
l2_limit_range_required = exchange._ft_has['l2_limit_range_required']
|
||||
if exchangename == 'gateio':
|
||||
# TODO: Gateio is unstable here at the moment, ignoring the limit partially.
|
||||
return
|
||||
for val in [1, 2, 5, 25, 100]:
|
||||
l2 = exchange.fetch_l2_order_book(pair, val)
|
||||
if not l2_limit_range or val in l2_limit_range:
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
from ccxt import Precise
|
||||
from freqtrade.util import FtPrecise
|
||||
|
||||
|
||||
ws = Precise('-1.123e-6')
|
||||
ws = Precise('-1.123e-6')
|
||||
xs = Precise('0.00000002')
|
||||
ys = Precise('69696900000')
|
||||
zs = Precise('0')
|
||||
ws = FtPrecise('-1.123e-6')
|
||||
ws = FtPrecise('-1.123e-6')
|
||||
xs = FtPrecise('0.00000002')
|
||||
ys = FtPrecise('69696900000')
|
||||
zs = FtPrecise('0')
|
||||
|
||||
|
||||
def test_precise():
|
||||
def test_FtPrecise():
|
||||
assert ys * xs == '1393.938'
|
||||
assert xs * ys == '1393.938'
|
||||
|
||||
@@ -45,31 +45,36 @@ def test_precise():
|
||||
assert xs + zs == '0.00000002'
|
||||
assert ys + zs == '69696900000'
|
||||
|
||||
assert abs(Precise('-500.1')) == '500.1'
|
||||
assert abs(Precise('213')) == '213'
|
||||
assert abs(FtPrecise('-500.1')) == '500.1'
|
||||
assert abs(FtPrecise('213')) == '213'
|
||||
|
||||
assert abs(Precise('-500.1')) == '500.1'
|
||||
assert -Precise('213') == '-213'
|
||||
assert abs(FtPrecise('-500.1')) == '500.1'
|
||||
assert -FtPrecise('213') == '-213'
|
||||
|
||||
assert Precise('10.1') % Precise('0.5') == '0.1'
|
||||
assert Precise('5550') % Precise('120') == '30'
|
||||
assert FtPrecise('10.1') % FtPrecise('0.5') == '0.1'
|
||||
assert FtPrecise('5550') % FtPrecise('120') == '30'
|
||||
|
||||
assert Precise('-0.0') == Precise('0')
|
||||
assert Precise('5.534000') == Precise('5.5340')
|
||||
assert FtPrecise('-0.0') == FtPrecise('0')
|
||||
assert FtPrecise('5.534000') == FtPrecise('5.5340')
|
||||
|
||||
assert min(Precise('-3.1415'), Precise('-2')) == '-3.1415'
|
||||
assert min(FtPrecise('-3.1415'), FtPrecise('-2')) == '-3.1415'
|
||||
|
||||
assert max(Precise('3.1415'), Precise('-2')) == '3.1415'
|
||||
assert max(FtPrecise('3.1415'), FtPrecise('-2')) == '3.1415'
|
||||
|
||||
assert Precise('2') > Precise('1.2345')
|
||||
assert not Precise('-3.1415') > Precise('-2')
|
||||
assert not Precise('3.1415') > Precise('3.1415')
|
||||
assert Precise.string_gt('3.14150000000000000000001', '3.1415')
|
||||
assert FtPrecise('2') > FtPrecise('1.2345')
|
||||
assert not FtPrecise('-3.1415') > FtPrecise('-2')
|
||||
assert not FtPrecise('3.1415') > FtPrecise('3.1415')
|
||||
assert FtPrecise.string_gt('3.14150000000000000000001', '3.1415')
|
||||
|
||||
assert Precise('3.1415') >= Precise('3.1415')
|
||||
assert Precise('3.14150000000000000000001') >= Precise('3.1415')
|
||||
assert FtPrecise('3.1415') >= FtPrecise('3.1415')
|
||||
assert FtPrecise('3.14150000000000000000001') >= FtPrecise('3.1415')
|
||||
|
||||
assert not Precise('3.1415') < Precise('3.1415')
|
||||
assert not FtPrecise('3.1415') < FtPrecise('3.1415')
|
||||
|
||||
assert Precise('3.1415') <= Precise('3.1415')
|
||||
assert Precise('3.1415') <= Precise('3.14150000000000000000001')
|
||||
assert FtPrecise('3.1415') <= FtPrecise('3.1415')
|
||||
assert FtPrecise('3.1415') <= FtPrecise('3.14150000000000000000001')
|
||||
|
||||
assert FtPrecise(213) == '213'
|
||||
assert FtPrecise(-213) == '-213'
|
||||
assert str(FtPrecise(-213)) == '-213'
|
||||
assert FtPrecise(213.2) == '213.2'
|
||||
|
||||
+204
-77
@@ -27,6 +27,57 @@ from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has
|
||||
# Make sure to always keep one exchange here which is NOT subclassed!!
|
||||
EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx', 'gateio']
|
||||
|
||||
get_entry_rate_data = [
|
||||
('other', 20, 19, 10, 0.0, 20), # Full ask side
|
||||
('ask', 20, 19, 10, 0.0, 20), # Full ask side
|
||||
('ask', 20, 19, 10, 1.0, 10), # Full last side
|
||||
('ask', 20, 19, 10, 0.5, 15), # Between ask and last
|
||||
('ask', 20, 19, 10, 0.7, 13), # Between ask and last
|
||||
('ask', 20, 19, 10, 0.3, 17), # Between ask and last
|
||||
('ask', 5, 6, 10, 1.0, 5), # last bigger than ask
|
||||
('ask', 5, 6, 10, 0.5, 5), # last bigger than ask
|
||||
('ask', 20, 19, 10, None, 20), # price_last_balance missing
|
||||
('ask', 10, 20, None, 0.5, 10), # last not available - uses ask
|
||||
('ask', 4, 5, None, 0.5, 4), # last not available - uses ask
|
||||
('ask', 4, 5, None, 1, 4), # last not available - uses ask
|
||||
('ask', 4, 5, None, 0, 4), # last not available - uses ask
|
||||
('same', 21, 20, 10, 0.0, 20), # Full bid side
|
||||
('bid', 21, 20, 10, 0.0, 20), # Full bid side
|
||||
('bid', 21, 20, 10, 1.0, 10), # Full last side
|
||||
('bid', 21, 20, 10, 0.5, 15), # Between bid and last
|
||||
('bid', 21, 20, 10, 0.7, 13), # Between bid and last
|
||||
('bid', 21, 20, 10, 0.3, 17), # Between bid and last
|
||||
('bid', 6, 5, 10, 1.0, 5), # last bigger than bid
|
||||
('bid', 21, 20, 10, None, 20), # price_last_balance missing
|
||||
('bid', 6, 5, 10, 0.5, 5), # last bigger than bid
|
||||
('bid', 21, 20, None, 0.5, 20), # last not available - uses bid
|
||||
('bid', 6, 5, None, 0.5, 5), # last not available - uses bid
|
||||
('bid', 6, 5, None, 1, 5), # last not available - uses bid
|
||||
('bid', 6, 5, None, 0, 5), # last not available - uses bid
|
||||
]
|
||||
|
||||
get_sell_rate_data = [
|
||||
('bid', 12.0, 11.0, 11.5, 0.0, 11.0), # full bid side
|
||||
('bid', 12.0, 11.0, 11.5, 1.0, 11.5), # full last side
|
||||
('bid', 12.0, 11.0, 11.5, 0.5, 11.25), # between bid and lat
|
||||
('bid', 12.0, 11.2, 10.5, 0.0, 11.2), # Last smaller than bid
|
||||
('bid', 12.0, 11.2, 10.5, 1.0, 11.2), # Last smaller than bid - uses bid
|
||||
('bid', 12.0, 11.2, 10.5, 0.5, 11.2), # Last smaller than bid - uses bid
|
||||
('bid', 0.003, 0.002, 0.005, 0.0, 0.002),
|
||||
('bid', 0.003, 0.002, 0.005, None, 0.002),
|
||||
('ask', 12.0, 11.0, 12.5, 0.0, 12.0), # full ask side
|
||||
('ask', 12.0, 11.0, 12.5, 1.0, 12.5), # full last side
|
||||
('ask', 12.0, 11.0, 12.5, 0.5, 12.25), # between bid and lat
|
||||
('ask', 12.2, 11.2, 10.5, 0.0, 12.2), # Last smaller than ask
|
||||
('ask', 12.0, 11.0, 10.5, 1.0, 12.0), # Last smaller than ask - uses ask
|
||||
('ask', 12.0, 11.2, 10.5, 0.5, 12.0), # Last smaller than ask - uses ask
|
||||
('ask', 10.0, 11.0, 11.0, 0.0, 10.0),
|
||||
('ask', 10.11, 11.2, 11.0, 0.0, 10.11),
|
||||
('ask', 0.001, 0.002, 11.0, 0.0, 0.001),
|
||||
('ask', 0.006, 1.0, 11.0, 0.0, 0.006),
|
||||
('ask', 0.006, 1.0, 11.0, None, 0.006),
|
||||
]
|
||||
|
||||
|
||||
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
||||
fun, mock_ccxt_fun, retries=API_RETRY_COUNT + 1, **kwargs):
|
||||
@@ -1135,7 +1186,58 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverag
|
||||
assert order["symbol"] == "ETH/BTC"
|
||||
assert order["amount"] == 1
|
||||
assert order["leverage"] == leverage
|
||||
assert order["cost"] == 1 * 200 / leverage
|
||||
assert order["cost"] == 1 * 200
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side,is_short,order_reason', [
|
||||
("buy", False, "entry"),
|
||||
("sell", False, "exit"),
|
||||
("buy", True, "exit"),
|
||||
("sell", True, "entry"),
|
||||
])
|
||||
@pytest.mark.parametrize("order_type,price_side,fee", [
|
||||
("limit", "same", 1.0),
|
||||
("limit", "other", 2.0),
|
||||
("market", "same", 2.0),
|
||||
("market", "other", 2.0),
|
||||
])
|
||||
def test_create_dry_run_order_fees(
|
||||
default_conf,
|
||||
mocker,
|
||||
side,
|
||||
order_type,
|
||||
is_short,
|
||||
order_reason,
|
||||
price_side,
|
||||
fee,
|
||||
):
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.get_fee',
|
||||
side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0
|
||||
)
|
||||
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled',
|
||||
return_value=price_side == 'other')
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
order = exchange.create_dry_run_order(
|
||||
pair='LTC/USDT',
|
||||
ordertype=order_type,
|
||||
side=side,
|
||||
amount=10,
|
||||
rate=2.0,
|
||||
leverage=1.0
|
||||
)
|
||||
if price_side == 'other' or order_type == 'market':
|
||||
assert order['fee']['rate'] == fee
|
||||
return
|
||||
else:
|
||||
assert order['fee'] is None
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled',
|
||||
return_value=price_side != 'other')
|
||||
|
||||
order1 = exchange.fetch_dry_run_order(order['id'])
|
||||
assert order1['fee']['rate'] == fee
|
||||
|
||||
|
||||
@pytest.mark.parametrize("side,startprice,endprice", [
|
||||
@@ -2309,34 +2411,7 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
|
||||
exchange.fetch_l2_order_book(pair='ETH/BTC', limit=50)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", [
|
||||
('other', 20, 19, 10, 0.0, 20), # Full ask side
|
||||
('ask', 20, 19, 10, 0.0, 20), # Full ask side
|
||||
('ask', 20, 19, 10, 1.0, 10), # Full last side
|
||||
('ask', 20, 19, 10, 0.5, 15), # Between ask and last
|
||||
('ask', 20, 19, 10, 0.7, 13), # Between ask and last
|
||||
('ask', 20, 19, 10, 0.3, 17), # Between ask and last
|
||||
('ask', 5, 6, 10, 1.0, 5), # last bigger than ask
|
||||
('ask', 5, 6, 10, 0.5, 5), # last bigger than ask
|
||||
('ask', 20, 19, 10, None, 20), # price_last_balance missing
|
||||
('ask', 10, 20, None, 0.5, 10), # last not available - uses ask
|
||||
('ask', 4, 5, None, 0.5, 4), # last not available - uses ask
|
||||
('ask', 4, 5, None, 1, 4), # last not available - uses ask
|
||||
('ask', 4, 5, None, 0, 4), # last not available - uses ask
|
||||
('same', 21, 20, 10, 0.0, 20), # Full bid side
|
||||
('bid', 21, 20, 10, 0.0, 20), # Full bid side
|
||||
('bid', 21, 20, 10, 1.0, 10), # Full last side
|
||||
('bid', 21, 20, 10, 0.5, 15), # Between bid and last
|
||||
('bid', 21, 20, 10, 0.7, 13), # Between bid and last
|
||||
('bid', 21, 20, 10, 0.3, 17), # Between bid and last
|
||||
('bid', 6, 5, 10, 1.0, 5), # last bigger than bid
|
||||
('bid', 21, 20, 10, None, 20), # price_last_balance missing
|
||||
('bid', 6, 5, 10, 0.5, 5), # last bigger than bid
|
||||
('bid', 21, 20, None, 0.5, 20), # last not available - uses bid
|
||||
('bid', 6, 5, None, 0.5, 5), # last not available - uses bid
|
||||
('bid', 6, 5, None, 1, 5), # last not available - uses bid
|
||||
('bid', 6, 5, None, 0, 5), # last not available - uses bid
|
||||
])
|
||||
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_entry_rate_data)
|
||||
def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||
last, last_ab, expected) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
@@ -2360,27 +2435,7 @@ def test_get_entry_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||
assert not log_has("Using cached entry rate for ETH/BTC.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', [
|
||||
('bid', 12.0, 11.0, 11.5, 0.0, 11.0), # full bid side
|
||||
('bid', 12.0, 11.0, 11.5, 1.0, 11.5), # full last side
|
||||
('bid', 12.0, 11.0, 11.5, 0.5, 11.25), # between bid and lat
|
||||
('bid', 12.0, 11.2, 10.5, 0.0, 11.2), # Last smaller than bid
|
||||
('bid', 12.0, 11.2, 10.5, 1.0, 11.2), # Last smaller than bid - uses bid
|
||||
('bid', 12.0, 11.2, 10.5, 0.5, 11.2), # Last smaller than bid - uses bid
|
||||
('bid', 0.003, 0.002, 0.005, 0.0, 0.002),
|
||||
('bid', 0.003, 0.002, 0.005, None, 0.002),
|
||||
('ask', 12.0, 11.0, 12.5, 0.0, 12.0), # full ask side
|
||||
('ask', 12.0, 11.0, 12.5, 1.0, 12.5), # full last side
|
||||
('ask', 12.0, 11.0, 12.5, 0.5, 12.25), # between bid and lat
|
||||
('ask', 12.2, 11.2, 10.5, 0.0, 12.2), # Last smaller than ask
|
||||
('ask', 12.0, 11.0, 10.5, 1.0, 12.0), # Last smaller than ask - uses ask
|
||||
('ask', 12.0, 11.2, 10.5, 0.5, 12.0), # Last smaller than ask - uses ask
|
||||
('ask', 10.0, 11.0, 11.0, 0.0, 10.0),
|
||||
('ask', 10.11, 11.2, 11.0, 0.0, 10.11),
|
||||
('ask', 0.001, 0.002, 11.0, 0.0, 0.001),
|
||||
('ask', 0.006, 1.0, 11.0, 0.0, 0.006),
|
||||
('ask', 0.006, 1.0, 11.0, None, 0.006),
|
||||
])
|
||||
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_sell_rate_data)
|
||||
def test_get_exit_rate(default_conf, mocker, caplog, side, bid, ask,
|
||||
last, last_ab, expected) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
@@ -2430,14 +2485,14 @@ def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, is_sho
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_short,side,expected', [
|
||||
(False, 'bid', 0.043936), # Value from order_book_l2 fitxure - bids side
|
||||
(False, 'ask', 0.043949), # Value from order_book_l2 fitxure - asks side
|
||||
(False, 'other', 0.043936), # Value from order_book_l2 fitxure - bids side
|
||||
(False, 'same', 0.043949), # Value from order_book_l2 fitxure - asks side
|
||||
(True, 'bid', 0.043936), # Value from order_book_l2 fitxure - bids side
|
||||
(True, 'ask', 0.043949), # Value from order_book_l2 fitxure - asks side
|
||||
(True, 'other', 0.043949), # Value from order_book_l2 fitxure - asks side
|
||||
(True, 'same', 0.043936), # Value from order_book_l2 fitxure - bids side
|
||||
(False, 'bid', 0.043936), # Value from order_book_l2 fixture - bids side
|
||||
(False, 'ask', 0.043949), # Value from order_book_l2 fixture - asks side
|
||||
(False, 'other', 0.043936), # Value from order_book_l2 fixture - bids side
|
||||
(False, 'same', 0.043949), # Value from order_book_l2 fixture - asks side
|
||||
(True, 'bid', 0.043936), # Value from order_book_l2 fixture - bids side
|
||||
(True, 'ask', 0.043949), # Value from order_book_l2 fixture - asks side
|
||||
(True, 'other', 0.043949), # Value from order_book_l2 fixture - asks side
|
||||
(True, 'same', 0.043936), # Value from order_book_l2 fixture - bids side
|
||||
])
|
||||
def test_get_exit_rate_orderbook(
|
||||
default_conf, mocker, caplog, is_short, side, expected, order_book_l2):
|
||||
@@ -2470,7 +2525,8 @@ def test_get_exit_rate_orderbook_exception(default_conf, mocker, caplog):
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
with pytest.raises(PricingError):
|
||||
exchange.get_rate(pair, refresh=True, side="exit", is_short=False)
|
||||
assert log_has_re(r"Exit Price at location 1 from orderbook could not be determined\..*",
|
||||
assert log_has_re(rf"{pair} - Exit Price at location 1 from orderbook "
|
||||
rf"could not be determined\..*",
|
||||
caplog)
|
||||
|
||||
|
||||
@@ -2497,6 +2553,84 @@ def test_get_exit_rate_exception(default_conf, mocker, is_short):
|
||||
assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.13
|
||||
|
||||
|
||||
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_entry_rate_data)
|
||||
@pytest.mark.parametrize("side2", ['bid', 'ask'])
|
||||
@pytest.mark.parametrize("use_order_book", [True, False])
|
||||
def test_get_rates_testing_buy(mocker, default_conf, caplog, side, ask, bid,
|
||||
last, last_ab, expected,
|
||||
side2, use_order_book, order_book_l2) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
if last_ab is None:
|
||||
del default_conf['entry_pricing']['price_last_balance']
|
||||
else:
|
||||
default_conf['entry_pricing']['price_last_balance'] = last_ab
|
||||
default_conf['entry_pricing']['price_side'] = side
|
||||
default_conf['exit_pricing']['price_side'] = side2
|
||||
default_conf['exit_pricing']['use_order_book'] = use_order_book
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_l2_order_book = order_book_l2
|
||||
api_mock.fetch_ticker = MagicMock(
|
||||
return_value={'ask': ask, 'last': last, 'bid': bid})
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
|
||||
assert exchange.get_rates('ETH/BTC', refresh=True, is_short=False)[0] == expected
|
||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
|
||||
api_mock.fetch_l2_order_book.reset_mock()
|
||||
api_mock.fetch_ticker.reset_mock()
|
||||
assert exchange.get_rates('ETH/BTC', refresh=False, is_short=False)[0] == expected
|
||||
assert log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
assert api_mock.fetch_l2_order_book.call_count == 0
|
||||
assert api_mock.fetch_ticker.call_count == 0
|
||||
# Running a 2nd time with Refresh on!
|
||||
caplog.clear()
|
||||
|
||||
assert exchange.get_rates('ETH/BTC', refresh=True, is_short=False)[0] == expected
|
||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
|
||||
assert api_mock.fetch_l2_order_book.call_count == int(use_order_book)
|
||||
assert api_mock.fetch_ticker.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side,ask,bid,last,last_ab,expected', get_sell_rate_data)
|
||||
@pytest.mark.parametrize("side2", ['bid', 'ask'])
|
||||
@pytest.mark.parametrize("use_order_book", [True, False])
|
||||
def test_get_rates_testing_sell(default_conf, mocker, caplog, side, bid, ask,
|
||||
last, last_ab, expected,
|
||||
side2, use_order_book, order_book_l2) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
default_conf['exit_pricing']['price_side'] = side
|
||||
if last_ab is not None:
|
||||
default_conf['exit_pricing']['price_last_balance'] = last_ab
|
||||
|
||||
default_conf['entry_pricing']['price_side'] = side2
|
||||
default_conf['entry_pricing']['use_order_book'] = use_order_book
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_l2_order_book = order_book_l2
|
||||
api_mock.fetch_ticker = MagicMock(
|
||||
return_value={'ask': ask, 'last': last, 'bid': bid})
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
|
||||
pair = "ETH/BTC"
|
||||
|
||||
# Test regular mode
|
||||
rate = exchange.get_rates(pair, refresh=True, is_short=False)[1]
|
||||
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
assert isinstance(rate, float)
|
||||
assert rate == expected
|
||||
# Use caching
|
||||
api_mock.fetch_l2_order_book.reset_mock()
|
||||
api_mock.fetch_ticker.reset_mock()
|
||||
|
||||
rate = exchange.get_rates(pair, refresh=False, is_short=False)[1]
|
||||
assert rate == expected
|
||||
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
|
||||
assert api_mock.fetch_l2_order_book.call_count == 0
|
||||
assert api_mock.fetch_ticker.call_count == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
@pytest.mark.asyncio
|
||||
async def test___async_get_candle_history_sort(default_conf, mocker, exchange_name):
|
||||
@@ -2859,6 +2993,9 @@ def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order,
|
||||
({'amount': 10.0, 'fee': {}}, False),
|
||||
({'result': 'testest123'}, False),
|
||||
('hello_world', False),
|
||||
({'status': 'canceled', 'amount': None, 'fee': None}, False),
|
||||
({'status': 'canceled', 'filled': None, 'amount': None, 'fee': None}, False),
|
||||
|
||||
])
|
||||
def test_is_cancel_order_result_suitable(mocker, default_conf, exchange_name, order, result):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
@@ -3544,7 +3681,7 @@ def test_order_has_fee(order, expected) -> None:
|
||||
def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01))
|
||||
ex = get_patched_exchange(mocker, default_conf)
|
||||
assert ex.extract_cost_curr_rate(order) == expected
|
||||
assert ex.extract_cost_curr_rate(order['fee'], order['symbol'], cost=20, amount=1) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("order,unknown_fee_rate,expected", [
|
||||
@@ -3582,6 +3719,9 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
|
||||
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 1, 4.0),
|
||||
({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5,
|
||||
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 2, 8.0),
|
||||
# Missing currency
|
||||
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
|
||||
'fee': {'currency': None, 'cost': 0.005}}, None, None),
|
||||
])
|
||||
def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081})
|
||||
@@ -3590,7 +3730,8 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_r
|
||||
|
||||
ex = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
assert ex.calculate_fee_rate(order) == expected
|
||||
assert ex.calculate_fee_rate(order['fee'], order['symbol'],
|
||||
cost=order['cost'], amount=order['amount']) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('retrycount,max_retries,expected', [
|
||||
@@ -3669,8 +3810,8 @@ def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
|
||||
since=unix_time
|
||||
)
|
||||
|
||||
assert(isclose(expected_fees, fees_from_datetime))
|
||||
assert(isclose(expected_fees, fees_from_unix_time))
|
||||
assert (isclose(expected_fees, fees_from_datetime))
|
||||
assert (isclose(expected_fees, fees_from_unix_time))
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
@@ -4041,20 +4182,6 @@ def test_get_or_calculate_liquidation_price(mocker, default_conf):
|
||||
)
|
||||
assert liq_price == 17.540699999999998
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
default_conf,
|
||||
api_mock,
|
||||
"binance",
|
||||
"get_or_calculate_liquidation_price",
|
||||
"fetch_positions",
|
||||
pair="XRP/USDT",
|
||||
open_rate=0.0,
|
||||
is_short=False,
|
||||
position=0.0,
|
||||
wallet_balance=0.0,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,rate_start,rate_end,d1,d2,amount,expected_fees', [
|
||||
('binance', 0, 2, "2021-09-01 01:00:00", "2021-09-01 04:00:00", 30.0, 0.0),
|
||||
|
||||
@@ -203,7 +203,7 @@ def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order, limit_
|
||||
'info': {
|
||||
'orderId': 'mocked_limit_sell',
|
||||
}}])
|
||||
api_mock.fetch_order = MagicMock(return_value=limit_sell_order)
|
||||
api_mock.fetch_order = MagicMock(return_value=limit_sell_order.copy())
|
||||
|
||||
# No orderId field - no call to fetch_order
|
||||
resp = exchange.fetch_stoploss_order('X', 'TKN/BTC')
|
||||
@@ -219,11 +219,23 @@ def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order, limit_
|
||||
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()
|
||||
api_mock.privateGetConditionalOrdersConditionalOrderIdTriggers = MagicMock(
|
||||
return_value={'result': [
|
||||
{'orderId': 'mocked_market_sell', 'type': 'market', 'side': 'sell', 'price': 0.254}
|
||||
]})
|
||||
resp = exchange.fetch_stoploss_order('X', 'TKN/BTC')
|
||||
assert resp
|
||||
# fetch_order not called (no regular order ID)
|
||||
assert api_mock.fetch_order.call_count == 0
|
||||
assert order == order
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
api_mock.privateGetConditionalOrdersConditionalOrderIdTriggers.call_count == 1
|
||||
expected_resp = limit_sell_order.copy()
|
||||
expected_resp.update({
|
||||
'id_stop': 'X',
|
||||
'id': 'X',
|
||||
'type': 'stop',
|
||||
'status_stop': 'triggered',
|
||||
})
|
||||
assert expected_resp == resp
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||
|
||||
@@ -53,6 +53,25 @@ def test_fetch_stoploss_order_gateio(default_conf, mocker):
|
||||
assert fetch_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC'
|
||||
assert fetch_order_mock.call_args_list[0][1]['params'] == {'stop': True}
|
||||
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
default_conf['margin_mode'] = 'isolated'
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='gateio')
|
||||
|
||||
exchange.fetch_order = MagicMock(return_value={
|
||||
'status': 'closed',
|
||||
'id': '1234',
|
||||
'stopPrice': 5.62,
|
||||
'info': {
|
||||
'trade_id': '222555'
|
||||
}
|
||||
})
|
||||
|
||||
exchange.fetch_stoploss_order('1234', 'ETH/BTC')
|
||||
assert exchange.fetch_order.call_count == 2
|
||||
assert exchange.fetch_order.call_args_list[0][1]['order_id'] == '1234'
|
||||
assert exchange.fetch_order.call_args_list[1][1]['order_id'] == '222555'
|
||||
|
||||
|
||||
def test_cancel_stoploss_order_gateio(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='gateio')
|
||||
|
||||
@@ -123,5 +123,5 @@ def test_stoploss_adjust_kucoin(mocker, default_conf):
|
||||
assert exchange.stoploss_adjust(1501, order, 'sell')
|
||||
assert not exchange.stoploss_adjust(1499, order, 'sell')
|
||||
# Test with invalid order case
|
||||
order['info']['stop'] = None
|
||||
assert not exchange.stoploss_adjust(1501, order, 'sell')
|
||||
order['stopPrice'] = None
|
||||
assert exchange.stoploss_adjust(1501, order, 'sell')
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
from freqtrade.enums.candletype import CandleType
|
||||
from freqtrade.exchange.exchange import timeframe_to_minutes
|
||||
from tests.conftest import get_patched_exchange
|
||||
from tests.conftest import get_mock_coro, get_patched_exchange
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets):
|
||||
'fetchLeverageTiers': False,
|
||||
'fetchMarketLeverageTiers': True,
|
||||
})
|
||||
api_mock.fetch_market_leverage_tiers = MagicMock(side_effect=[
|
||||
api_mock.fetch_market_leverage_tiers = get_mock_coro(side_effect=[
|
||||
[
|
||||
{
|
||||
'tier': 1,
|
||||
|
||||
@@ -18,11 +18,11 @@ def hyperopt_conf(default_conf):
|
||||
'runmode': RunMode.HYPEROPT,
|
||||
'strategy': 'HyperoptableStrategy',
|
||||
'hyperopt_loss': 'ShortTradeDurHyperOptLoss',
|
||||
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
|
||||
'epochs': 1,
|
||||
'timerange': None,
|
||||
'spaces': ['default'],
|
||||
'hyperopt_jobs': 1,
|
||||
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
|
||||
'epochs': 1,
|
||||
'timerange': None,
|
||||
'spaces': ['default'],
|
||||
'hyperopt_jobs': 1,
|
||||
'hyperopt_min_trades': 1,
|
||||
})
|
||||
return hyperconf
|
||||
|
||||
@@ -90,28 +90,6 @@ def load_data_test(what, testdatadir):
|
||||
fill_missing=True)}
|
||||
|
||||
|
||||
def simple_backtest(config, contour, mocker, testdatadir) -> None:
|
||||
patch_exchange(mocker)
|
||||
config['timeframe'] = '1m'
|
||||
backtesting = Backtesting(config)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
|
||||
data = load_data_test(contour, testdatadir)
|
||||
processed = backtesting.strategy.advise_all_indicators(data)
|
||||
min_date, max_date = get_timerange(processed)
|
||||
assert isinstance(processed, dict)
|
||||
results = backtesting.backtest(
|
||||
processed=processed,
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=1,
|
||||
position_stacking=False,
|
||||
enable_protections=config.get('enable_protections', False),
|
||||
)
|
||||
# results :: <class 'pandas.core.frame.DataFrame'>
|
||||
return results
|
||||
|
||||
|
||||
# FIX: fixturize this?
|
||||
def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
|
||||
data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair])
|
||||
@@ -942,6 +920,7 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
|
||||
def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatadir) -> None:
|
||||
# While this test IS a copy of test_backtest_pricecontours, it's needed to ensure
|
||||
# results do not carry-over to the next run, which is not given by using parametrize.
|
||||
patch_exchange(mocker)
|
||||
default_conf['protections'] = [
|
||||
{
|
||||
"method": "CooldownPeriod",
|
||||
@@ -949,6 +928,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
|
||||
}]
|
||||
|
||||
default_conf['enable_protections'] = True
|
||||
default_conf['timeframe'] = '1m'
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
|
||||
@@ -959,12 +939,27 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
|
||||
['sine', 9],
|
||||
['raise', 10],
|
||||
]
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
|
||||
# While entry-signals are unrealistic, running backtesting
|
||||
# over and over again should not cause different results
|
||||
for [contour, numres] in tests:
|
||||
# Debug output for random test failure
|
||||
print(f"{contour}, {numres}")
|
||||
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == numres
|
||||
data = load_data_test(contour, testdatadir)
|
||||
processed = backtesting.strategy.advise_all_indicators(data)
|
||||
min_date, max_date = get_timerange(processed)
|
||||
assert isinstance(processed, dict)
|
||||
results = backtesting.backtest(
|
||||
processed=processed,
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=1,
|
||||
position_stacking=False,
|
||||
enable_protections=default_conf.get('enable_protections', False),
|
||||
)
|
||||
assert len(results['results']) == numres
|
||||
|
||||
|
||||
@pytest.mark.parametrize('protections,contour,expected', [
|
||||
@@ -990,7 +985,25 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
# While entry-signals are unrealistic, running backtesting
|
||||
# over and over again should not cause different results
|
||||
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == expected
|
||||
|
||||
patch_exchange(mocker)
|
||||
default_conf['timeframe'] = '1m'
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
|
||||
data = load_data_test(contour, testdatadir)
|
||||
processed = backtesting.strategy.advise_all_indicators(data)
|
||||
min_date, max_date = get_timerange(processed)
|
||||
assert isinstance(processed, dict)
|
||||
results = backtesting.backtest(
|
||||
processed=processed,
|
||||
start_date=min_date,
|
||||
end_date=max_date,
|
||||
max_open_trades=1,
|
||||
position_stacking=False,
|
||||
enable_protections=default_conf.get('enable_protections', False),
|
||||
)
|
||||
assert len(results['results']) == expected
|
||||
|
||||
|
||||
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from arrow import Arrow
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
@@ -87,3 +89,87 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
|
||||
assert (round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6) or
|
||||
round(ln.iloc[0]["low"], 6) < round(
|
||||
t["close_rate"], 6) < round(ln.iloc[0]["high"], 6))
|
||||
|
||||
|
||||
def test_backtest_position_adjustment_detailed(default_conf, fee, mocker) -> None:
|
||||
default_conf['use_exit_signal'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=10)
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
|
||||
patch_exchange(mocker)
|
||||
default_conf.update({
|
||||
"stake_amount": 100.0,
|
||||
"dry_run_wallet": 1000.0,
|
||||
"strategy": "StrategyTestV3"
|
||||
})
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
pair = 'XRP/USDT'
|
||||
row = [
|
||||
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
|
||||
2.1, # Open
|
||||
2.2, # High
|
||||
1.9, # Low
|
||||
2.1, # Close
|
||||
1, # enter_long
|
||||
0, # exit_long
|
||||
0, # enter_short
|
||||
0, # exit_short
|
||||
'', # enter_tag
|
||||
'', # exit_tag
|
||||
]
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
trade.orders[0].close_bt_order(row[0], trade)
|
||||
assert trade
|
||||
assert pytest.approx(trade.stake_amount) == 100.0
|
||||
assert pytest.approx(trade.amount) == 47.61904762
|
||||
assert len(trade.orders) == 1
|
||||
backtesting.strategy.adjust_trade_position = MagicMock(return_value=None)
|
||||
|
||||
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row)
|
||||
assert trade
|
||||
assert pytest.approx(trade.stake_amount) == 100.0
|
||||
assert pytest.approx(trade.amount) == 47.61904762
|
||||
assert len(trade.orders) == 1
|
||||
# Increase position by 100
|
||||
backtesting.strategy.adjust_trade_position = MagicMock(return_value=100)
|
||||
|
||||
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row)
|
||||
|
||||
assert trade
|
||||
assert pytest.approx(trade.stake_amount) == 200.0
|
||||
assert pytest.approx(trade.amount) == 95.23809524
|
||||
assert len(trade.orders) == 2
|
||||
|
||||
# Reduce by more than amount - no change to trade.
|
||||
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-500)
|
||||
|
||||
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row)
|
||||
|
||||
assert trade
|
||||
assert pytest.approx(trade.stake_amount) == 200.0
|
||||
assert pytest.approx(trade.amount) == 95.23809524
|
||||
assert len(trade.orders) == 2
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
|
||||
# Reduce position by 50
|
||||
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-100)
|
||||
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row)
|
||||
|
||||
assert trade
|
||||
assert pytest.approx(trade.stake_amount) == 100.0
|
||||
assert pytest.approx(trade.amount) == 47.61904762
|
||||
assert len(trade.orders) == 3
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
assert trade.nr_of_successful_exits == 1
|
||||
|
||||
# Adjust below minimum
|
||||
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-99)
|
||||
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row)
|
||||
|
||||
assert trade
|
||||
assert pytest.approx(trade.stake_amount) == 100.0
|
||||
assert pytest.approx(trade.amount) == 47.61904762
|
||||
assert len(trade.orders) == 3
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
assert trade.nr_of_successful_exits == 1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring,W0212,C0103
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from unittest.mock import ANY, MagicMock
|
||||
from unittest.mock import ANY, MagicMock, PropertyMock
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
@@ -18,8 +18,8 @@ from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
||||
from freqtrade.optimize.optimize_reports import generate_strategy_stats
|
||||
from freqtrade.optimize.space import SKDecimal
|
||||
from freqtrade.strategy import IntParameter
|
||||
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, get_markets, log_has, log_has_re,
|
||||
patch_exchange, patched_configuration_load_config_file)
|
||||
|
||||
|
||||
def generate_result_metrics():
|
||||
@@ -855,12 +855,13 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
||||
'strategy': 'HyperoptableStrategy',
|
||||
'user_data_dir': Path(tmpdir),
|
||||
'hyperopt_random_state': 42,
|
||||
'spaces': ['all']
|
||||
'spaces': ['all'],
|
||||
})
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
@@ -882,6 +883,45 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
||||
hyperopt.get_optimizer([], 2)
|
||||
|
||||
|
||||
def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_config', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_markets')
|
||||
mocker.patch('freqtrade.exchange.Exchange.markets',
|
||||
PropertyMock(return_value=get_markets()))
|
||||
(Path(tmpdir) / 'hyperopt_results').mkdir(parents=True)
|
||||
# No hyperopt needed
|
||||
hyperopt_conf.update({
|
||||
'strategy': 'HyperoptableStrategy',
|
||||
'user_data_dir': Path(tmpdir),
|
||||
'hyperopt_random_state': 42,
|
||||
'spaces': ['all'],
|
||||
# Enforce parallelity
|
||||
'epochs': 2,
|
||||
'hyperopt_jobs': 2,
|
||||
'fee': fee.return_value,
|
||||
})
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.exchange.get_max_leverage = lambda *x, **xx: 1.0
|
||||
hyperopt.backtesting.exchange.get_min_pair_stake_amount = lambda *x, **xx: 1.0
|
||||
hyperopt.backtesting.exchange.get_max_pair_stake_amount = lambda *x, **xx: 100.0
|
||||
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
assert hyperopt.backtesting.strategy.sell_rsi.value == 74
|
||||
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30
|
||||
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range
|
||||
assert isinstance(buy_rsi_range, range)
|
||||
# Range from 0 - 50 (inclusive)
|
||||
assert len(list(buy_rsi_range)) == 51
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
|
||||
def test_SKDecimal():
|
||||
space = SKDecimal(1, 2, decimals=2)
|
||||
assert 1.5 in space
|
||||
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
from freqtrade import constants
|
||||
from freqtrade.enums import ExitType
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.persistence.trade_model import Order
|
||||
from freqtrade.plugins.protectionmanager import ProtectionManager
|
||||
from tests.conftest import get_patched_freqtradebot, log_has_re
|
||||
|
||||
@@ -30,7 +31,37 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
|
||||
amount=0.01 / open_rate,
|
||||
exchange='binance',
|
||||
is_short=is_short,
|
||||
leverage=1,
|
||||
)
|
||||
|
||||
trade.orders.append(Order(
|
||||
ft_order_side=trade.entry_side,
|
||||
order_id=f'{pair}-{trade.entry_side}-{trade.open_date}',
|
||||
ft_pair=pair,
|
||||
amount=trade.amount,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
price=open_rate,
|
||||
average=open_rate,
|
||||
status="closed",
|
||||
order_type="market",
|
||||
side=trade.entry_side,
|
||||
))
|
||||
if not is_open:
|
||||
trade.orders.append(Order(
|
||||
ft_order_side=trade.exit_side,
|
||||
order_id=f'{pair}-{trade.exit_side}-{trade.close_date}',
|
||||
ft_pair=pair,
|
||||
amount=trade.amount,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
price=open_rate * (2 - profit_rate if is_short else profit_rate),
|
||||
average=open_rate * (2 - profit_rate if is_short else profit_rate),
|
||||
status="closed",
|
||||
order_type="market",
|
||||
side=trade.exit_side,
|
||||
))
|
||||
|
||||
trade.recalc_open_trade_value()
|
||||
if not is_open:
|
||||
trade.close(open_rate * (2 - profit_rate if is_short else profit_rate))
|
||||
@@ -393,7 +424,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
@pytest.mark.parametrize("protectionconf,desc_expected,exception_expected", [
|
||||
({"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2, "stop_duration": 60},
|
||||
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
||||
"2 stoplosses within 60 minutes.'}]",
|
||||
"2 stoplosses with profit < 0.00% within 60 minutes.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "CooldownPeriod", "stop_duration": 60},
|
||||
@@ -411,9 +442,9 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
||||
None
|
||||
),
|
||||
({"method": "StoplossGuard", "lookback_period_candles": 12, "trade_limit": 2,
|
||||
"stop_duration": 60},
|
||||
"required_profit": -0.05, "stop_duration": 60},
|
||||
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
||||
"2 stoplosses within 12 candles.'}]",
|
||||
"2 stoplosses with profit < -5.00% within 12 candles.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "CooldownPeriod", "stop_duration_candles": 5},
|
||||
|
||||
+22
-17
@@ -111,6 +111,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'stoploss_entry_dist': -0.00010475,
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'open_order': None,
|
||||
'realized_profit': 0.0,
|
||||
'exchange': 'binance',
|
||||
'leverage': 1.0,
|
||||
'interest_rate': 0.0,
|
||||
@@ -196,6 +197,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'open_order': None,
|
||||
'exchange': 'binance',
|
||||
'realized_profit': 0.0,
|
||||
'leverage': 1.0,
|
||||
'interest_rate': 0.0,
|
||||
'liquidation_price': None,
|
||||
@@ -312,10 +314,10 @@ def test__rpc_timeunit_profit(default_conf_usdt, ticker, fee,
|
||||
# {'date': datetime.date(2022, 6, 11), 'abs_profit': 13.8299999,
|
||||
# 'starting_balance': 1055.37, 'rel_profit': 0.0131044,
|
||||
# 'fiat_value': 0.0, 'trade_count': 2}
|
||||
assert day['abs_profit'] in (0.0, pytest.approx(13.8299999), pytest.approx(-4.0))
|
||||
assert day['rel_profit'] in (0.0, pytest.approx(0.01310441), pytest.approx(-0.00377583))
|
||||
assert day['abs_profit'] in (0.0, pytest.approx(6.83), pytest.approx(-4.09))
|
||||
assert day['rel_profit'] in (0.0, pytest.approx(0.00642902), pytest.approx(-0.00383512))
|
||||
assert day['trade_count'] in (0, 1, 2)
|
||||
assert day['starting_balance'] in (pytest.approx(1059.37), pytest.approx(1055.37))
|
||||
assert day['starting_balance'] in (pytest.approx(1062.37), pytest.approx(1066.46))
|
||||
assert day['fiat_value'] in (0.0, )
|
||||
# ensure first day is current date
|
||||
assert str(days['data'][0]['date']) == str(datetime.utcnow().date())
|
||||
@@ -433,9 +435,9 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
|
||||
create_mock_trades_usdt(fee)
|
||||
|
||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
assert pytest.approx(stats['profit_closed_coin']) == 9.83
|
||||
assert pytest.approx(stats['profit_closed_coin']) == 2.74
|
||||
assert pytest.approx(stats['profit_closed_percent_mean']) == -1.67
|
||||
assert pytest.approx(stats['profit_closed_fiat']) == 10.813
|
||||
assert pytest.approx(stats['profit_closed_fiat']) == 3.014
|
||||
assert pytest.approx(stats['profit_all_coin']) == -77.45964918
|
||||
assert pytest.approx(stats['profit_all_percent_mean']) == -57.86
|
||||
assert pytest.approx(stats['profit_all_fiat']) == -85.205614098
|
||||
@@ -830,6 +832,8 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
assert cancel_order_mock.call_count == 2
|
||||
assert trade.amount == amount
|
||||
|
||||
trade = Trade.query.filter(Trade.id == '3').first()
|
||||
|
||||
# make an limit-sell open trade
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.fetch_order',
|
||||
@@ -839,7 +843,8 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
'side': 'sell',
|
||||
'amount': amount,
|
||||
'remaining': amount,
|
||||
'filled': 0.0
|
||||
'filled': 0.0,
|
||||
'id': trade.orders[0].order_id,
|
||||
}
|
||||
)
|
||||
msg = rpc._rpc_force_exit('3')
|
||||
@@ -865,9 +870,9 @@ def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None:
|
||||
|
||||
res = rpc._rpc_performance()
|
||||
assert len(res) == 3
|
||||
assert res[0]['pair'] == 'XRP/USDT'
|
||||
assert res[0]['pair'] == 'ETC/USDT'
|
||||
assert res[0]['count'] == 1
|
||||
assert res[0]['profit_pct'] == 10.0
|
||||
assert res[0]['profit_pct'] == 5.0
|
||||
|
||||
|
||||
def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None:
|
||||
@@ -891,16 +896,16 @@ def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None
|
||||
res = rpc._rpc_enter_tag_performance(None)
|
||||
|
||||
assert len(res) == 3
|
||||
assert res[0]['enter_tag'] == 'TEST3'
|
||||
assert res[0]['enter_tag'] == 'TEST1'
|
||||
assert res[0]['count'] == 1
|
||||
assert res[0]['profit_pct'] == 10.0
|
||||
assert res[0]['profit_pct'] == 5.0
|
||||
|
||||
res = rpc._rpc_enter_tag_performance(None)
|
||||
|
||||
assert len(res) == 3
|
||||
assert res[0]['enter_tag'] == 'TEST3'
|
||||
assert res[0]['enter_tag'] == 'TEST1'
|
||||
assert res[0]['count'] == 1
|
||||
assert res[0]['profit_pct'] == 10.0
|
||||
assert res[0]['profit_pct'] == 5.0
|
||||
|
||||
|
||||
def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
|
||||
@@ -951,11 +956,11 @@ def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker)
|
||||
res = rpc._rpc_exit_reason_performance(None)
|
||||
|
||||
assert len(res) == 3
|
||||
assert res[0]['exit_reason'] == 'roi'
|
||||
assert res[0]['exit_reason'] == 'exit_signal'
|
||||
assert res[0]['count'] == 1
|
||||
assert res[0]['profit_pct'] == 10.0
|
||||
assert res[0]['profit_pct'] == 5.0
|
||||
|
||||
assert res[1]['exit_reason'] == 'exit_signal'
|
||||
assert res[1]['exit_reason'] == 'roi'
|
||||
assert res[2]['exit_reason'] == 'Other'
|
||||
|
||||
|
||||
@@ -1007,9 +1012,9 @@ def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None:
|
||||
res = rpc._rpc_mix_tag_performance(None)
|
||||
|
||||
assert len(res) == 3
|
||||
assert res[0]['mix_tag'] == 'TEST3 roi'
|
||||
assert res[0]['mix_tag'] == 'TEST1 exit_signal'
|
||||
assert res[0]['count'] == 1
|
||||
assert res[0]['profit_pct'] == 10.0
|
||||
assert res[0]['profit_pct'] == 5.0
|
||||
|
||||
|
||||
def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee):
|
||||
|
||||
@@ -109,6 +109,9 @@ def test_api_ui_fallback(botclient, mocker):
|
||||
rc = client_get(client, "/something")
|
||||
assert rc.status_code == 200
|
||||
|
||||
rc = client_get(client, "/something.js")
|
||||
assert rc.status_code == 200
|
||||
|
||||
# Test directory traversal without mock
|
||||
rc = client_get(client, '%2F%2F%2Fetc/passwd')
|
||||
assert rc.status_code == 200
|
||||
@@ -578,9 +581,10 @@ def test_api_trades(botclient, mocker, fee, markets, is_short):
|
||||
)
|
||||
rc = client_get(client, f"{BASE_URI}/trades")
|
||||
assert_response(rc)
|
||||
assert len(rc.json()) == 3
|
||||
assert len(rc.json()) == 4
|
||||
assert rc.json()['trades_count'] == 0
|
||||
assert rc.json()['total_trades'] == 0
|
||||
assert rc.json()['offset'] == 0
|
||||
|
||||
create_mock_trades(fee, is_short=is_short)
|
||||
Trade.query.session.flush()
|
||||
@@ -716,11 +720,11 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
(
|
||||
True,
|
||||
{'best_pair': 'ETC/BTC', 'best_rate': -0.5, 'best_pair_profit_ratio': -0.005,
|
||||
'profit_all_coin': 43.61269123,
|
||||
'profit_all_fiat': 538398.67323435, 'profit_all_percent_mean': 66.41,
|
||||
'profit_all_coin': 45.561959,
|
||||
'profit_all_fiat': 562462.39126200, 'profit_all_percent_mean': 66.41,
|
||||
'profit_all_ratio_mean': 0.664109545, 'profit_all_percent_sum': 398.47,
|
||||
'profit_all_ratio_sum': 3.98465727, 'profit_all_percent': 4.36,
|
||||
'profit_all_ratio': 0.043612222872799825, 'profit_closed_coin': -0.00673913,
|
||||
'profit_all_ratio_sum': 3.98465727, 'profit_all_percent': 4.56,
|
||||
'profit_all_ratio': 0.04556147, 'profit_closed_coin': -0.00673913,
|
||||
'profit_closed_fiat': -83.19455985, 'profit_closed_ratio_mean': -0.0075,
|
||||
'profit_closed_percent_mean': -0.75, 'profit_closed_ratio_sum': -0.015,
|
||||
'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06,
|
||||
@@ -731,11 +735,11 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
(
|
||||
False,
|
||||
{'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'best_pair_profit_ratio': 0.01,
|
||||
'profit_all_coin': -44.0631579,
|
||||
'profit_all_fiat': -543959.6842755, 'profit_all_percent_mean': -66.41,
|
||||
'profit_all_coin': -45.79641127,
|
||||
'profit_all_fiat': -565356.69712815, 'profit_all_percent_mean': -66.41,
|
||||
'profit_all_ratio_mean': -0.6641100666666667, 'profit_all_percent_sum': -398.47,
|
||||
'profit_all_ratio_sum': -3.9846604, 'profit_all_percent': -4.41,
|
||||
'profit_all_ratio': -0.044063014216106644, 'profit_closed_coin': 0.00073913,
|
||||
'profit_all_ratio_sum': -3.9846604, 'profit_all_percent': -4.58,
|
||||
'profit_all_ratio': -0.045796261934205953, 'profit_closed_coin': 0.00073913,
|
||||
'profit_closed_fiat': 9.124559849999999, 'profit_closed_ratio_mean': 0.0075,
|
||||
'profit_closed_percent_mean': 0.75, 'profit_closed_ratio_sum': 0.015,
|
||||
'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07,
|
||||
@@ -746,11 +750,11 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
(
|
||||
None,
|
||||
{'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'best_pair_profit_ratio': 0.01,
|
||||
'profit_all_coin': -14.43790415,
|
||||
'profit_all_fiat': -178235.92673175, 'profit_all_percent_mean': 0.08,
|
||||
'profit_all_coin': -14.94732578,
|
||||
'profit_all_fiat': -184524.7367541, 'profit_all_percent_mean': 0.08,
|
||||
'profit_all_ratio_mean': 0.000835751666666662, 'profit_all_percent_sum': 0.5,
|
||||
'profit_all_ratio_sum': 0.005014509999999972, 'profit_all_percent': -1.44,
|
||||
'profit_all_ratio': -0.014437768014451796, 'profit_closed_coin': -0.00542913,
|
||||
'profit_all_ratio_sum': 0.005014509999999972, 'profit_all_percent': -1.49,
|
||||
'profit_all_ratio': -0.014947184841095841, 'profit_closed_coin': -0.00542913,
|
||||
'profit_closed_fiat': -67.02260985, 'profit_closed_ratio_mean': 0.0025,
|
||||
'profit_closed_percent_mean': 0.25, 'profit_closed_ratio_sum': 0.005,
|
||||
'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06,
|
||||
@@ -789,22 +793,22 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
|
||||
'first_trade_timestamp': ANY,
|
||||
'latest_trade_date': '5 minutes ago',
|
||||
'latest_trade_timestamp': ANY,
|
||||
'profit_all_coin': expected['profit_all_coin'],
|
||||
'profit_all_fiat': expected['profit_all_fiat'],
|
||||
'profit_all_percent_mean': expected['profit_all_percent_mean'],
|
||||
'profit_all_ratio_mean': expected['profit_all_ratio_mean'],
|
||||
'profit_all_percent_sum': expected['profit_all_percent_sum'],
|
||||
'profit_all_ratio_sum': expected['profit_all_ratio_sum'],
|
||||
'profit_all_percent': expected['profit_all_percent'],
|
||||
'profit_all_ratio': expected['profit_all_ratio'],
|
||||
'profit_closed_coin': expected['profit_closed_coin'],
|
||||
'profit_closed_fiat': expected['profit_closed_fiat'],
|
||||
'profit_closed_ratio_mean': expected['profit_closed_ratio_mean'],
|
||||
'profit_closed_percent_mean': expected['profit_closed_percent_mean'],
|
||||
'profit_closed_ratio_sum': expected['profit_closed_ratio_sum'],
|
||||
'profit_closed_percent_sum': expected['profit_closed_percent_sum'],
|
||||
'profit_closed_ratio': expected['profit_closed_ratio'],
|
||||
'profit_closed_percent': expected['profit_closed_percent'],
|
||||
'profit_all_coin': pytest.approx(expected['profit_all_coin']),
|
||||
'profit_all_fiat': pytest.approx(expected['profit_all_fiat']),
|
||||
'profit_all_percent_mean': pytest.approx(expected['profit_all_percent_mean']),
|
||||
'profit_all_ratio_mean': pytest.approx(expected['profit_all_ratio_mean']),
|
||||
'profit_all_percent_sum': pytest.approx(expected['profit_all_percent_sum']),
|
||||
'profit_all_ratio_sum': pytest.approx(expected['profit_all_ratio_sum']),
|
||||
'profit_all_percent': pytest.approx(expected['profit_all_percent']),
|
||||
'profit_all_ratio': pytest.approx(expected['profit_all_ratio']),
|
||||
'profit_closed_coin': pytest.approx(expected['profit_closed_coin']),
|
||||
'profit_closed_fiat': pytest.approx(expected['profit_closed_fiat']),
|
||||
'profit_closed_ratio_mean': pytest.approx(expected['profit_closed_ratio_mean']),
|
||||
'profit_closed_percent_mean': pytest.approx(expected['profit_closed_percent_mean']),
|
||||
'profit_closed_ratio_sum': pytest.approx(expected['profit_closed_ratio_sum']),
|
||||
'profit_closed_percent_sum': pytest.approx(expected['profit_closed_percent_sum']),
|
||||
'profit_closed_ratio': pytest.approx(expected['profit_closed_ratio']),
|
||||
'profit_closed_percent': pytest.approx(expected['profit_closed_percent']),
|
||||
'trade_count': 6,
|
||||
'closed_trade_count': 2,
|
||||
'winning_trades': expected['winning_trades'],
|
||||
@@ -1201,7 +1205,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets):
|
||||
fetch_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
_is_dry_limit_order_filled=MagicMock(return_value=False),
|
||||
_is_dry_limit_order_filled=MagicMock(return_value=True),
|
||||
)
|
||||
patch_get_signal(ftbot)
|
||||
|
||||
@@ -1211,12 +1215,27 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets):
|
||||
assert rc.json() == {"error": "Error querying /api/v1/forceexit: invalid argument"}
|
||||
Trade.query.session.rollback()
|
||||
|
||||
ftbot.enter_positions()
|
||||
create_mock_trades(fee)
|
||||
trade = Trade.get_trades([Trade.id == 5]).first()
|
||||
assert pytest.approx(trade.amount) == 123
|
||||
rc = client_post(client, f"{BASE_URI}/forceexit",
|
||||
data='{"tradeid": "5", "ordertype": "market", "amount": 23}')
|
||||
assert_response(rc)
|
||||
assert rc.json() == {'result': 'Created sell order for trade 5.'}
|
||||
Trade.query.session.rollback()
|
||||
|
||||
trade = Trade.get_trades([Trade.id == 5]).first()
|
||||
assert pytest.approx(trade.amount) == 100
|
||||
assert trade.is_open is True
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/forceexit",
|
||||
data='{"tradeid": "1"}')
|
||||
data='{"tradeid": "5"}')
|
||||
assert_response(rc)
|
||||
assert rc.json() == {'result': 'Created sell order for trade 1.'}
|
||||
assert rc.json() == {'result': 'Created sell order for trade 5.'}
|
||||
Trade.query.session.rollback()
|
||||
|
||||
trade = Trade.get_trades([Trade.id == 5]).first()
|
||||
assert trade.is_open is False
|
||||
|
||||
|
||||
def test_api_pair_candles(botclient, ohlcv_history):
|
||||
@@ -1397,10 +1416,10 @@ def test_api_strategies(botclient):
|
||||
|
||||
assert rc.json() == {'strategies': [
|
||||
'HyperoptableStrategy',
|
||||
'HyperoptableStrategyV2',
|
||||
'InformativeDecoratorTest',
|
||||
'StrategyTestV2',
|
||||
'StrategyTestV3',
|
||||
'StrategyTestV3Analysis',
|
||||
'StrategyTestV3Futures'
|
||||
]}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import logging
|
||||
import time
|
||||
from collections import deque
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freqtrade.enums import RPCMessageType
|
||||
@@ -81,9 +82,25 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||
assert telegram_mock.call_count == 0
|
||||
|
||||
|
||||
def test_process_msg_queue(mocker, default_conf, caplog) -> None:
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
queue = deque()
|
||||
queue.append('Test message')
|
||||
queue.append('Test message 2')
|
||||
rpc_manager.process_msg_queue(queue)
|
||||
|
||||
assert log_has("Sending rpc message: {'type': strategy_msg, 'msg': 'Test message'}", caplog)
|
||||
assert log_has("Sending rpc message: {'type': strategy_msg, 'msg': 'Test message 2'}", caplog)
|
||||
assert telegram_mock.call_count == 2
|
||||
|
||||
|
||||
def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
|
||||
+155
-41
@@ -12,6 +12,7 @@ from unittest.mock import ANY, MagicMock
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
from pandas import DataFrame
|
||||
from telegram import Chat, Message, ReplyKeyboardMarkup, Update
|
||||
from telegram.error import BadRequest, NetworkError, TelegramError
|
||||
|
||||
@@ -271,7 +272,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None:
|
||||
msg = msg_mock.call_args_list[0][0][0]
|
||||
assert re.search(r'Number of Entries.*2', msg)
|
||||
assert re.search(r'Average Entry Price', msg)
|
||||
assert re.search(r'Order filled at', msg)
|
||||
assert re.search(r'Order filled', msg)
|
||||
assert re.search(r'Close Date:', msg) is None
|
||||
assert re.search(r'Close Profit:', msg) is None
|
||||
|
||||
@@ -341,7 +342,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
# close_rate should not be included in the message as the trade is not closed
|
||||
# and no line should be empty
|
||||
lines = msg_mock.call_args_list[0][0][0].split('\n')
|
||||
assert '' not in lines
|
||||
assert '' not in lines[:-1]
|
||||
assert 'Close Rate' not in ''.join(lines)
|
||||
assert 'Close Profit' not in ''.join(lines)
|
||||
|
||||
@@ -356,13 +357,29 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
telegram._status(update=update, context=context)
|
||||
|
||||
lines = msg_mock.call_args_list[0][0][0].split('\n')
|
||||
assert '' not in lines
|
||||
assert '' not in lines[:-1]
|
||||
assert 'Close Rate' not in ''.join(lines)
|
||||
assert 'Close Profit' not in ''.join(lines)
|
||||
|
||||
assert msg_mock.call_count == 2
|
||||
assert 'LTC/BTC' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
mocker.patch('freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH', 500)
|
||||
|
||||
msg_mock.reset_mock()
|
||||
context = MagicMock()
|
||||
context.args = ["2"]
|
||||
telegram._status(update=update, context=context)
|
||||
|
||||
assert msg_mock.call_count == 2
|
||||
|
||||
msg1 = msg_mock.call_args_list[0][0][0]
|
||||
msg2 = msg_mock.call_args_list[1][0][0]
|
||||
|
||||
assert 'Close Rate' not in msg1
|
||||
assert 'Trade ID:* `2`' in msg1
|
||||
assert 'Trade ID:* `2` - continued' in msg2
|
||||
|
||||
|
||||
def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
@@ -432,10 +449,10 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi
|
||||
assert "Daily Profit over the last 2 days</b>:" in msg_mock.call_args_list[0][0][0]
|
||||
assert 'Day ' in msg_mock.call_args_list[0][0][0]
|
||||
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 13.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 15.21 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(2)' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(2) 13.83 USDT 15.21 USD 1.31%' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(2) 6.83 USDT 7.51 USD 0.64%' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(0)' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
# Reset msg_mock
|
||||
@@ -446,8 +463,8 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi
|
||||
assert "Daily Profit over the last 7 days</b>:" in msg_mock.call_args_list[0][0][0]
|
||||
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
|
||||
assert str((datetime.utcnow() - timedelta(days=5)).date()) in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 13.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 15.21 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(2)' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(1)' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(0)' in msg_mock.call_args_list[0][0][0]
|
||||
@@ -459,8 +476,8 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi
|
||||
context = MagicMock()
|
||||
context.args = ["1"]
|
||||
telegram._daily(update=update, context=context)
|
||||
assert ' 13.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 15.21 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(2)' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
@@ -522,8 +539,8 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach
|
||||
today = datetime.utcnow().date()
|
||||
first_iso_day_of_current_week = today - timedelta(days=today.weekday())
|
||||
assert str(first_iso_day_of_current_week) in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 9.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 10.81 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 3.01 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(3)' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(0)' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
@@ -535,8 +552,8 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach
|
||||
assert "Weekly Profit over the last 8 weeks (starting from Monday)</b>:" \
|
||||
in msg_mock.call_args_list[0][0][0]
|
||||
assert 'Weekly' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 9.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 10.81 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 3.01 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(3)' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(0)' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
@@ -591,8 +608,8 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
|
||||
today = datetime.utcnow().date()
|
||||
current_month = f"{today.year}-{today.month:02} "
|
||||
assert current_month in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 9.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 10.81 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 3.01 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(3)' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(0)' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
@@ -605,8 +622,8 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
|
||||
assert 'Monthly Profit over the last 6 months</b>:' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'Month ' in msg_mock.call_args_list[0][0][0]
|
||||
assert current_month in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 9.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 10.81 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 3.01 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(3)' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(0)' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
@@ -619,8 +636,8 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
|
||||
telegram._monthly(update=update, context=context)
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'Monthly Profit over the last 12 months</b>:' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 9.83 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 10.81 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0]
|
||||
assert ' 3.01 USD' in msg_mock.call_args_list[0][0][0]
|
||||
assert '(3)' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
# The one-digit months should contain a zero, Eg: September 2021 = "2021-09"
|
||||
@@ -685,6 +702,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
oobj = Order.parse_from_ccxt_object(
|
||||
limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell')
|
||||
trade.orders.append(oobj)
|
||||
trade.update_trade(oobj)
|
||||
|
||||
trade.close_date = datetime.now(timezone.utc)
|
||||
@@ -706,7 +724,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
|
||||
assert '*Best Performing:* `ETH/USDT: 9.45%`' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Max Drawdown:*' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Profit factor:*' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Trading volume:* `60 USDT`' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Trading volume:* `126 USDT`' in msg_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_short', [True, False])
|
||||
@@ -957,6 +975,9 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
'stake_amount': 0.0009999999999054,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -1026,6 +1047,9 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
'stake_amount': 0.0009999999999054,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -1085,6 +1109,9 @@ def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
'stake_amount': 0.0009999999999054,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
} == msg
|
||||
|
||||
|
||||
@@ -1257,7 +1284,7 @@ def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, moc
|
||||
telegram._performance(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'Performance' in msg_mock.call_args_list[0][0][0]
|
||||
assert '<code>XRP/USDT\t9.842 USDT (10.00%) (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||
assert '<code>XRP/USDT\t2.842 USDT (10.00%) (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_telegram_entry_tag_performance_handle(
|
||||
@@ -1307,7 +1334,7 @@ def test_telegram_exit_reason_performance_handle(default_conf_usdt, update, tick
|
||||
telegram._exit_reason_performance(update=update, context=context)
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'Exit Reason Performance' in msg_mock.call_args_list[0][0][0]
|
||||
assert '<code>roi\t9.842 USDT (10.00%) (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||
assert '<code>roi\t2.842 USDT (10.00%) (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||
context.args = ['XRP/USDT']
|
||||
|
||||
telegram._exit_reason_performance(update=update, context=context)
|
||||
@@ -1339,7 +1366,7 @@ def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker,
|
||||
telegram._mix_tag_performance(update=update, context=context)
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0]
|
||||
assert ('<code>TEST3 roi\t9.842 USDT (10.00%) (1)</code>'
|
||||
assert ('<code>TEST3 roi\t2.842 USDT (10.00%) (1)</code>'
|
||||
in msg_mock.call_args_list[0][0][0])
|
||||
|
||||
context.args = ['XRP/USDT']
|
||||
@@ -1435,7 +1462,7 @@ def test_whitelist_static(default_conf, update, mocker) -> None:
|
||||
def test_whitelist_dynamic(default_conf, update, mocker) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
default_conf['pairlists'] = [{'method': 'VolumePairList',
|
||||
'number_assets': 4
|
||||
'number_assets': 4
|
||||
}]
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
@@ -1505,7 +1532,7 @@ def test_telegram_logs(default_conf, update, mocker) -> None:
|
||||
|
||||
msg_mock.reset_mock()
|
||||
# Test with changed MaxMessageLength
|
||||
mocker.patch('freqtrade.rpc.telegram.MAX_TELEGRAM_MESSAGE_LENGTH', 200)
|
||||
mocker.patch('freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH', 200)
|
||||
context = MagicMock()
|
||||
context.args = []
|
||||
telegram._logs(update=update, context=context)
|
||||
@@ -1655,8 +1682,17 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
||||
(RPCMessageType.ENTRY, 'Long', 'long_signal_01', 1.0),
|
||||
(RPCMessageType.ENTRY, 'Long', 'long_signal_01', 5.0),
|
||||
(RPCMessageType.ENTRY, 'Short', 'short_signal_01', 2.0)])
|
||||
def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
|
||||
enter, enter_signal, leverage) -> None:
|
||||
def test_send_msg_enter_notification(default_conf, mocker, caplog, message_type,
|
||||
enter, enter_signal, leverage) -> None:
|
||||
default_conf['telegram']['notification_settings']['show_candle'] = 'ohlc'
|
||||
df = DataFrame({
|
||||
'open': [1.1],
|
||||
'high': [2.2],
|
||||
'low': [1.0],
|
||||
'close': [1.5],
|
||||
})
|
||||
mocker.patch('freqtrade.data.dataprovider.DataProvider.get_analyzed_dataframe',
|
||||
return_value=(df, 1))
|
||||
|
||||
msg = {
|
||||
'type': message_type,
|
||||
@@ -1674,6 +1710,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
|
||||
'fiat_currency': 'USD',
|
||||
'current_rate': 1.099e-05,
|
||||
'amount': 1333.3333333333335,
|
||||
'analyzed_candle': {'open': 1.1, 'high': 2.2, 'low': 1.0, 'close': 1.5},
|
||||
'open_date': arrow.utcnow().shift(hours=-1)
|
||||
}
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
@@ -1683,6 +1720,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
|
||||
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
f'\N{LARGE BLUE CIRCLE} *Binance (dry):* {enter} ETH/BTC (#1)\n'
|
||||
'*Candle OHLC*: `1.1, 2.2, 1.0, 1.5`\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
f'{leverage_text}'
|
||||
@@ -1710,7 +1748,8 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
|
||||
@pytest.mark.parametrize('message_type,enter_signal', [
|
||||
(RPCMessageType.ENTRY_CANCEL, 'long_signal_01'),
|
||||
(RPCMessageType.ENTRY_CANCEL, 'short_signal_01')])
|
||||
def test_send_msg_buy_cancel_notification(default_conf, mocker, message_type, enter_signal) -> None:
|
||||
def test_send_msg_enter_cancel_notification(
|
||||
default_conf, mocker, message_type, enter_signal) -> None:
|
||||
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
@@ -1775,7 +1814,6 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
|
||||
'leverage': leverage,
|
||||
'stake_amount': 0.01465333,
|
||||
'direction': entered,
|
||||
# 'stake_amount_fiat': 0.0,
|
||||
'stake_currency': 'BTC',
|
||||
'fiat_currency': 'USD',
|
||||
'open_rate': 1.099e-05,
|
||||
@@ -1792,6 +1830,33 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
|
||||
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
|
||||
)
|
||||
|
||||
msg_mock.reset_mock()
|
||||
telegram.send_msg({
|
||||
'type': message_type,
|
||||
'trade_id': 1,
|
||||
'enter_tag': enter_signal,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'leverage': leverage,
|
||||
'stake_amount': 0.01465333,
|
||||
'sub_trade': True,
|
||||
'direction': entered,
|
||||
'stake_currency': 'BTC',
|
||||
'fiat_currency': 'USD',
|
||||
'open_rate': 1.099e-05,
|
||||
'amount': 1333.3333333333335,
|
||||
'open_date': arrow.utcnow().shift(hours=-1)
|
||||
})
|
||||
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
f'\N{CHECK MARK} *Binance (dry):* {entered}ed ETH/BTC (#1)\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
f"{leverage_text}"
|
||||
'*Open Rate:* `0.00001099`\n'
|
||||
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
|
||||
)
|
||||
|
||||
|
||||
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
|
||||
@@ -1826,14 +1891,53 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
||||
'*Enter Tag:* `buy_signal1`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1:00:00 (60.0 min)`\n'
|
||||
'*Direction:* `Long`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`'
|
||||
'*Exit Rate:* `0.00003201`\n'
|
||||
'*Duration:* `1:00:00 (60.0 min)`'
|
||||
)
|
||||
|
||||
msg_mock.reset_mock()
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.EXIT,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'direction': 'Long',
|
||||
'gain': 'loss',
|
||||
'limit': 3.201e-05,
|
||||
'amount': 1333.3333333333335,
|
||||
'order_type': 'market',
|
||||
'open_rate': 7.5e-05,
|
||||
'current_rate': 3.201e-05,
|
||||
'cumulative_profit': -0.15746268,
|
||||
'profit_amount': -0.05746268,
|
||||
'profit_ratio': -0.57405275,
|
||||
'stake_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'enter_tag': 'buy_signal1',
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
|
||||
'close_date': arrow.utcnow(),
|
||||
'stake_amount': 0.01,
|
||||
'sub_trade': True,
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
||||
'*Unrealized Sub Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
||||
'*Cumulative Profit:* (`-0.15746268 ETH / -24.812 USD`)\n'
|
||||
'*Enter Tag:* `buy_signal1`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
'*Direction:* `Long`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Exit Rate:* `0.00003201`\n'
|
||||
'*Remaining:* `(0.01 ETH, -24.812 USD)`'
|
||||
)
|
||||
|
||||
msg_mock.reset_mock()
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.EXIT,
|
||||
@@ -1857,15 +1961,15 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
||||
'*Unrealized Profit:* `-57.41%`\n'
|
||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
||||
'*Enter Tag:* `buy_signal1`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
||||
'*Direction:* `Long`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`'
|
||||
'*Exit Rate:* `0.00003201`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
|
||||
)
|
||||
# Reset singleton function to avoid random breaks
|
||||
telegram._rpc._fiat_converter.convert_amount = old_convamount
|
||||
@@ -1940,15 +2044,15 @@ def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
|
||||
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance (dry):* Exited KEY/ETH (#1)\n'
|
||||
'*Profit:* `-57.41%`\n'
|
||||
'*Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
||||
f"*Direction:* `{direction}`\n"
|
||||
f"{leverage_text}"
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Close Rate:* `0.00003201`'
|
||||
'*Exit Rate:* `0.00003201`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
|
||||
)
|
||||
|
||||
|
||||
@@ -1980,6 +2084,16 @@ def test_startup_notification(default_conf, mocker) -> None:
|
||||
assert msg_mock.call_args[0][0] == '*Custom:* `Hello World`'
|
||||
|
||||
|
||||
def test_send_msg_strategy_msg_notification(default_conf, mocker) -> None:
|
||||
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.STRATEGY_MSG,
|
||||
'msg': 'hello world, Test msg'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == 'hello world, Test msg'
|
||||
|
||||
|
||||
def test_send_msg_unknown_type(default_conf, mocker) -> None:
|
||||
telegram, _, _ = get_telegram_testobject(mocker, default_conf)
|
||||
with pytest.raises(NotImplementedError, match=r'Unknown message type: None'):
|
||||
@@ -2066,16 +2180,16 @@ def test_send_msg_sell_notification_no_fiat(
|
||||
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
||||
'*Unrealized Profit:* `-57.41%`\n'
|
||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
'*Duration:* `2:35:03 (155.1 min)`\n'
|
||||
f'*Direction:* `{direction}`\n'
|
||||
f'{leverage_text}'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`'
|
||||
'*Exit Rate:* `0.00003201`\n'
|
||||
'*Duration:* `2:35:03 (155.1 min)`'
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
|
||||
from pandas import DataFrame
|
||||
from strategy_test_v2 import StrategyTestV2
|
||||
from strategy_test_v3 import StrategyTestV3
|
||||
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from freqtrade.strategy import BooleanParameter, DecimalParameter, IntParameter, RealParameter
|
||||
|
||||
|
||||
class HyperoptableStrategy(StrategyTestV2):
|
||||
class HyperoptableStrategy(StrategyTestV3):
|
||||
"""
|
||||
Default Strategy provided by freqtrade bot.
|
||||
Please do not modify this strategy, it's intended for internal use only.
|
||||
@@ -44,6 +44,11 @@ class HyperoptableStrategy(StrategyTestV2):
|
||||
})
|
||||
return prot
|
||||
|
||||
bot_loop_started = False
|
||||
|
||||
def bot_loop_start(self):
|
||||
self.bot_loop_started = True
|
||||
|
||||
def bot_start(self, **kwargs) -> None:
|
||||
"""
|
||||
Parameters can also be defined here ...
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
|
||||
from strategy_test_v2 import StrategyTestV2
|
||||
|
||||
from freqtrade.strategy import BooleanParameter, DecimalParameter, IntParameter, RealParameter
|
||||
|
||||
|
||||
class HyperoptableStrategyV2(StrategyTestV2):
|
||||
"""
|
||||
Default Strategy provided by freqtrade bot.
|
||||
Please do not modify this strategy, it's intended for internal use only.
|
||||
Please look at the SampleStrategy in the user_data/strategy directory
|
||||
or strategy repository https://github.com/freqtrade/freqtrade-strategies
|
||||
for samples and inspiration.
|
||||
"""
|
||||
|
||||
buy_params = {
|
||||
'buy_rsi': 35,
|
||||
# Intentionally not specified, so "default" is tested
|
||||
# 'buy_plusdi': 0.4
|
||||
}
|
||||
|
||||
sell_params = {
|
||||
'sell_rsi': 74,
|
||||
'sell_minusdi': 0.4
|
||||
}
|
||||
|
||||
buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy')
|
||||
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell')
|
||||
sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell',
|
||||
load=False)
|
||||
protection_enabled = BooleanParameter(default=True)
|
||||
protection_cooldown_lookback = IntParameter([0, 50], default=30)
|
||||
|
||||
@property
|
||||
def protections(self):
|
||||
prot = []
|
||||
if self.protection_enabled.value:
|
||||
prot.append({
|
||||
"method": "CooldownPeriod",
|
||||
"stop_duration_candles": self.protection_cooldown_lookback.value
|
||||
})
|
||||
return prot
|
||||
|
||||
bot_loop_started = False
|
||||
|
||||
def bot_loop_start(self):
|
||||
self.bot_loop_started = True
|
||||
|
||||
def bot_start(self, **kwargs) -> None:
|
||||
"""
|
||||
Parameters can also be defined here ...
|
||||
"""
|
||||
self.buy_rsi = IntParameter([0, 50], default=30, space='buy')
|
||||
@@ -185,9 +185,12 @@ class StrategyTestV3(IStrategy):
|
||||
|
||||
return 3.0
|
||||
|
||||
def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float,
|
||||
current_profit: float,
|
||||
min_stake: Optional[float], max_stake: float, **kwargs):
|
||||
def adjust_trade_position(self, trade: Trade, current_time: datetime,
|
||||
current_rate: float, current_profit: float,
|
||||
min_stake: Optional[float], max_stake: float,
|
||||
current_entry_rate: float, current_exit_rate: float,
|
||||
current_entry_profit: float, current_exit_profit: float,
|
||||
**kwargs) -> Optional[float]:
|
||||
|
||||
if current_profit < -0.0075:
|
||||
orders = trade.select_filled_orders(trade.entry_side)
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from freqtrade.strategy import (BooleanParameter, DecimalParameter, IntParameter, IStrategy,
|
||||
RealParameter)
|
||||
|
||||
|
||||
class StrategyTestV3Analysis(IStrategy):
|
||||
"""
|
||||
Strategy used by tests freqtrade bot.
|
||||
Please do not modify this strategy, it's intended for internal use only.
|
||||
Please look at the SampleStrategy in the user_data/strategy directory
|
||||
or strategy repository https://github.com/freqtrade/freqtrade-strategies
|
||||
for samples and inspiration.
|
||||
"""
|
||||
INTERFACE_VERSION = 3
|
||||
|
||||
# Minimal ROI designed for the strategy
|
||||
minimal_roi = {
|
||||
"40": 0.0,
|
||||
"30": 0.01,
|
||||
"20": 0.02,
|
||||
"0": 0.04
|
||||
}
|
||||
|
||||
# Optimal stoploss designed for the strategy
|
||||
stoploss = -0.10
|
||||
|
||||
# Optimal timeframe for the strategy
|
||||
timeframe = '5m'
|
||||
|
||||
# Optional order type mapping
|
||||
order_types = {
|
||||
'entry': 'limit',
|
||||
'exit': 'limit',
|
||||
'stoploss': 'limit',
|
||||
'stoploss_on_exchange': False
|
||||
}
|
||||
|
||||
# Number of candles the strategy requires before producing valid signals
|
||||
startup_candle_count: int = 20
|
||||
|
||||
# Optional time in force for orders
|
||||
order_time_in_force = {
|
||||
'entry': 'gtc',
|
||||
'exit': 'gtc',
|
||||
}
|
||||
|
||||
buy_params = {
|
||||
'buy_rsi': 35,
|
||||
# Intentionally not specified, so "default" is tested
|
||||
# 'buy_plusdi': 0.4
|
||||
}
|
||||
|
||||
sell_params = {
|
||||
'sell_rsi': 74,
|
||||
'sell_minusdi': 0.4
|
||||
}
|
||||
|
||||
buy_rsi = IntParameter([0, 50], default=30, space='buy')
|
||||
buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy')
|
||||
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell')
|
||||
sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell',
|
||||
load=False)
|
||||
protection_enabled = BooleanParameter(default=True)
|
||||
protection_cooldown_lookback = IntParameter([0, 50], default=30)
|
||||
|
||||
# TODO: Can this work with protection tests? (replace HyperoptableStrategy implicitly ... )
|
||||
# @property
|
||||
# def protections(self):
|
||||
# prot = []
|
||||
# if self.protection_enabled.value:
|
||||
# prot.append({
|
||||
# "method": "CooldownPeriod",
|
||||
# "stop_duration_candles": self.protection_cooldown_lookback.value
|
||||
# })
|
||||
# return prot
|
||||
|
||||
bot_started = False
|
||||
|
||||
def bot_start(self):
|
||||
self.bot_started = True
|
||||
|
||||
def informative_pairs(self):
|
||||
|
||||
return []
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
|
||||
# Momentum Indicator
|
||||
# ------------------------------------
|
||||
|
||||
# ADX
|
||||
dataframe['adx'] = ta.ADX(dataframe)
|
||||
|
||||
# MACD
|
||||
macd = ta.MACD(dataframe)
|
||||
dataframe['macd'] = macd['macd']
|
||||
dataframe['macdsignal'] = macd['macdsignal']
|
||||
dataframe['macdhist'] = macd['macdhist']
|
||||
|
||||
# Minus Directional Indicator / Movement
|
||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||
|
||||
# Plus Directional Indicator / Movement
|
||||
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||
|
||||
# RSI
|
||||
dataframe['rsi'] = ta.RSI(dataframe)
|
||||
|
||||
# Stoch fast
|
||||
stoch_fast = ta.STOCHF(dataframe)
|
||||
dataframe['fastd'] = stoch_fast['fastd']
|
||||
dataframe['fastk'] = stoch_fast['fastk']
|
||||
|
||||
# Bollinger bands
|
||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||
dataframe['bb_lowerband'] = bollinger['lower']
|
||||
dataframe['bb_middleband'] = bollinger['mid']
|
||||
dataframe['bb_upperband'] = bollinger['upper']
|
||||
|
||||
# EMA - Exponential Moving Average
|
||||
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['rsi'] < self.buy_rsi.value) &
|
||||
(dataframe['fastd'] < 35) &
|
||||
(dataframe['adx'] > 30) &
|
||||
(dataframe['plus_di'] > self.buy_plusdi.value)
|
||||
) |
|
||||
(
|
||||
(dataframe['adx'] > 65) &
|
||||
(dataframe['plus_di'] > self.buy_plusdi.value)
|
||||
),
|
||||
['enter_long', 'enter_tag']] = 1, 'enter_tag_long'
|
||||
|
||||
dataframe.loc[
|
||||
(
|
||||
qtpylib.crossed_below(dataframe['rsi'], self.sell_rsi.value)
|
||||
),
|
||||
['enter_short', 'enter_tag']] = 1, 'enter_tag_short'
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(
|
||||
(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) |
|
||||
(qtpylib.crossed_above(dataframe['fastd'], 70))
|
||||
) &
|
||||
(dataframe['adx'] > 10) &
|
||||
(dataframe['minus_di'] > 0)
|
||||
) |
|
||||
(
|
||||
(dataframe['adx'] > 70) &
|
||||
(dataframe['minus_di'] > self.sell_minusdi.value)
|
||||
),
|
||||
['exit_long', 'exit_tag']] = 1, 'exit_tag_long'
|
||||
|
||||
dataframe.loc[
|
||||
(
|
||||
qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)
|
||||
),
|
||||
['exit_long', 'exit_tag']] = 1, 'exit_tag_short'
|
||||
|
||||
return dataframe
|
||||
@@ -408,28 +408,31 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'profit,adjusted,expected,trailing,custom,profit2,adjusted2,expected2,custom_stop', [
|
||||
'profit,adjusted,expected,liq,trailing,custom,profit2,adjusted2,expected2,custom_stop', [
|
||||
# Profit, adjusted stoploss(absolute), profit for 2nd call, enable trailing,
|
||||
# enable custom stoploss, expected after 1st call, expected after 2nd call
|
||||
(0.2, 0.9, ExitType.NONE, False, False, 0.3, 0.9, ExitType.NONE, None),
|
||||
(0.2, 0.9, ExitType.NONE, False, False, -0.2, 0.9, ExitType.STOP_LOSS, None),
|
||||
(0.2, 1.14, ExitType.NONE, True, False, 0.05, 1.14, ExitType.TRAILING_STOP_LOSS, None),
|
||||
(0.01, 0.96, ExitType.NONE, True, False, 0.05, 1, ExitType.NONE, None),
|
||||
(0.05, 1, ExitType.NONE, True, False, -0.01, 1, ExitType.TRAILING_STOP_LOSS, None),
|
||||
(0.2, 0.9, ExitType.NONE, None, False, False, 0.3, 0.9, ExitType.NONE, None),
|
||||
(0.2, 0.9, ExitType.NONE, None, False, False, -0.2, 0.9, ExitType.STOP_LOSS, None),
|
||||
(0.2, 0.9, ExitType.NONE, 0.8, False, False, -0.2, 0.9, ExitType.LIQUIDATION, None),
|
||||
(0.2, 1.14, ExitType.NONE, None, True, False, 0.05, 1.14, ExitType.TRAILING_STOP_LOSS,
|
||||
None),
|
||||
(0.01, 0.96, ExitType.NONE, None, True, False, 0.05, 1, ExitType.NONE, None),
|
||||
(0.05, 1, ExitType.NONE, None, True, False, -0.01, 1, ExitType.TRAILING_STOP_LOSS, None),
|
||||
# Default custom case - trails with 10%
|
||||
(0.05, 0.95, ExitType.NONE, False, True, -0.02, 0.95, ExitType.NONE, None),
|
||||
(0.05, 0.95, ExitType.NONE, False, True, -0.06, 0.95, ExitType.TRAILING_STOP_LOSS, None),
|
||||
(0.05, 1, ExitType.NONE, False, True, -0.06, 1, ExitType.TRAILING_STOP_LOSS,
|
||||
(0.05, 0.95, ExitType.NONE, None, False, True, -0.02, 0.95, ExitType.NONE, None),
|
||||
(0.05, 0.95, ExitType.NONE, None, False, True, -0.06, 0.95, ExitType.TRAILING_STOP_LOSS,
|
||||
None),
|
||||
(0.05, 1, ExitType.NONE, None, False, True, -0.06, 1, ExitType.TRAILING_STOP_LOSS,
|
||||
lambda **kwargs: -0.05),
|
||||
(0.05, 1, ExitType.NONE, False, True, 0.09, 1.04, ExitType.NONE,
|
||||
(0.05, 1, ExitType.NONE, None, False, True, 0.09, 1.04, ExitType.NONE,
|
||||
lambda **kwargs: -0.05),
|
||||
(0.05, 0.95, ExitType.NONE, False, True, 0.09, 0.98, ExitType.NONE,
|
||||
(0.05, 0.95, ExitType.NONE, None, False, True, 0.09, 0.98, ExitType.NONE,
|
||||
lambda current_profit, **kwargs: -0.1 if current_profit < 0.6 else -(current_profit * 2)),
|
||||
# Error case - static stoploss in place
|
||||
(0.05, 0.9, ExitType.NONE, False, True, 0.09, 0.9, ExitType.NONE,
|
||||
(0.05, 0.9, ExitType.NONE, None, False, True, 0.09, 0.9, ExitType.NONE,
|
||||
lambda **kwargs: None),
|
||||
])
|
||||
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom,
|
||||
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, trailing, custom,
|
||||
profit2, adjusted2, expected2, custom_stop) -> None:
|
||||
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@@ -442,6 +445,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
liquidation_price=liq,
|
||||
)
|
||||
trade.adjust_min_max_rates(trade.open_rate, trade.open_rate)
|
||||
strategy.trailing_stop = trailing
|
||||
@@ -916,7 +920,7 @@ def test_hyperopt_parameters():
|
||||
|
||||
|
||||
def test_auto_hyperopt_interface(default_conf):
|
||||
default_conf.update({'strategy': 'HyperoptableStrategy'})
|
||||
default_conf.update({'strategy': 'HyperoptableStrategyV2'})
|
||||
PairLocks.timeframe = default_conf['timeframe']
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
strategy.ft_bot_start()
|
||||
|
||||
+446
-41
@@ -68,8 +68,14 @@ def test_process_stopped(mocker, default_conf_usdt) -> None:
|
||||
assert coo_mock.call_count == 1
|
||||
|
||||
|
||||
def test_process_calls_sendmsg(mocker, default_conf_usdt) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
freqtrade.process()
|
||||
assert freqtrade.rpc.process_msg_queue.call_count == 1
|
||||
|
||||
|
||||
def test_bot_cleanup(mocker, default_conf_usdt, caplog) -> None:
|
||||
mock_cleanup = mocker.patch('freqtrade.freqtradebot.cleanup_db')
|
||||
mock_cleanup = mocker.patch('freqtrade.freqtradebot.Trade.commit')
|
||||
coo_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cancel_all_open_orders')
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
freqtrade.cleanup()
|
||||
@@ -837,8 +843,8 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
|
||||
# In case of closed order
|
||||
order['status'] = 'closed'
|
||||
order['price'] = 10
|
||||
order['cost'] = 100
|
||||
order['average'] = 10
|
||||
order['cost'] = 300
|
||||
order['id'] = '444'
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
@@ -849,7 +855,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert trade.open_rate == 10
|
||||
assert trade.stake_amount == round(order['price'] * order['filled'] / leverage, 8)
|
||||
assert trade.stake_amount == round(order['average'] * order['filled'] / leverage, 8)
|
||||
assert pytest.approx(trade.liquidation_price) == liq_price
|
||||
|
||||
# In case of rejected or expired order and partially filled
|
||||
@@ -857,8 +863,8 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
order['amount'] = 30.0
|
||||
order['filled'] = 20.0
|
||||
order['remaining'] = 10.00
|
||||
order['price'] = 0.5
|
||||
order['cost'] = 15.0
|
||||
order['average'] = 0.5
|
||||
order['cost'] = 10.0
|
||||
order['id'] = '555'
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
MagicMock(return_value=order))
|
||||
@@ -866,9 +872,9 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
trade = Trade.query.all()[3]
|
||||
trade.is_short = is_short
|
||||
assert trade
|
||||
assert trade.open_order_id == '555'
|
||||
assert trade.open_order_id is None
|
||||
assert trade.open_rate == 0.5
|
||||
assert trade.stake_amount == round(order['price'] * order['filled'] / leverage, 8)
|
||||
assert trade.stake_amount == round(order['average'] * order['filled'] / leverage, 8)
|
||||
|
||||
# Test with custom stake
|
||||
order['status'] = 'open'
|
||||
@@ -895,7 +901,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
order['amount'] = 30.0 * leverage
|
||||
order['filled'] = 0.0
|
||||
order['remaining'] = 30.0
|
||||
order['price'] = 0.5
|
||||
order['average'] = 0.5
|
||||
order['cost'] = 0.0
|
||||
order['id'] = '66'
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
@@ -967,6 +973,14 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
trade.is_short = is_short
|
||||
assert pytest.approx(trade.stake_amount) == 500
|
||||
|
||||
order['id'] = '55673'
|
||||
|
||||
freqtrade.strategy.leverage.reset_mock()
|
||||
assert freqtrade.execute_entry(pair, 200, leverage_=3)
|
||||
assert freqtrade.strategy.leverage.call_count == 0
|
||||
trade = Trade.query.all()[10]
|
||||
assert trade.leverage == 1 if trading_mode == 'spot' else 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None:
|
||||
@@ -1077,7 +1091,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
'last': 1.9
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': enter_order['id']},
|
||||
enter_order,
|
||||
exit_order,
|
||||
]),
|
||||
get_fee=fee,
|
||||
@@ -1103,20 +1117,20 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
# should do nothing and return false
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
trade.stoploss_order_id = "100"
|
||||
|
||||
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', hanging_stoploss_order)
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert trade.stoploss_order_id == 100
|
||||
assert trade.stoploss_order_id == "100"
|
||||
|
||||
# Third case: when stoploss was set but it was canceled for some reason
|
||||
# should set a stoploss immediately and return False
|
||||
caplog.clear()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
trade.stoploss_order_id = "100"
|
||||
|
||||
canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', canceled_stoploss_order)
|
||||
@@ -2033,6 +2047,7 @@ def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit
|
||||
|
||||
trade = MagicMock()
|
||||
trade.open_order_id = '123'
|
||||
trade.amount = 123
|
||||
|
||||
# Test raise of OperationalException exception
|
||||
mocker.patch(
|
||||
@@ -2060,8 +2075,9 @@ def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) ->
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_update_trade_state_sell(
|
||||
default_conf_usdt, trades_for_order, limit_order_open, limit_order, is_short, mocker,
|
||||
default_conf_usdt, trades_for_order, limit_order_open, limit_order, is_short, mocker
|
||||
):
|
||||
buy_order = limit_order[entry_side(is_short)]
|
||||
open_order = limit_order_open[exit_side(is_short)]
|
||||
l_order = limit_order[exit_side(is_short)]
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||
@@ -2088,6 +2104,9 @@ def test_update_trade_state_sell(
|
||||
leverage=1,
|
||||
is_short=is_short,
|
||||
)
|
||||
order = Order.parse_from_ccxt_object(buy_order, 'LTC/ETH', entry_side(is_short))
|
||||
trade.orders.append(order)
|
||||
|
||||
order = Order.parse_from_ccxt_object(open_order, 'LTC/ETH', exit_side(is_short))
|
||||
trade.orders.append(order)
|
||||
assert order.status == 'open'
|
||||
@@ -2135,8 +2154,6 @@ def test_handle_trade(
|
||||
assert trade
|
||||
|
||||
time.sleep(0.01) # Race condition fix
|
||||
oobj = Order.parse_from_ccxt_object(enter_order, enter_order['symbol'], entry_side(is_short))
|
||||
trade.update_trade(oobj)
|
||||
assert trade.is_open is True
|
||||
freqtrade.wallets.update()
|
||||
|
||||
@@ -2146,11 +2163,15 @@ def test_handle_trade(
|
||||
assert trade.open_order_id == exit_order['id']
|
||||
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
oobj = Order.parse_from_ccxt_object(exit_order, exit_order['symbol'], exit_side(is_short))
|
||||
trade.update_trade(oobj)
|
||||
trade.orders[-1].ft_is_open = False
|
||||
trade.orders[-1].status = 'closed'
|
||||
trade.orders[-1].filled = trade.orders[-1].remaining
|
||||
trade.orders[-1].remaining = 0.0
|
||||
|
||||
assert trade.close_rate == 2.0 if is_short else 2.2
|
||||
assert trade.close_profit == close_profit
|
||||
trade.update_trade(trade.orders[-1])
|
||||
|
||||
assert trade.close_rate == (2.0 if is_short else 2.2)
|
||||
assert pytest.approx(trade.close_profit) == close_profit
|
||||
assert trade.calc_profit(trade.close_rate) == 5.685
|
||||
assert trade.close_date is not None
|
||||
assert trade.exit_reason == 'sell_signal1'
|
||||
@@ -2340,9 +2361,9 @@ def test_close_trade(
|
||||
trade.is_short = is_short
|
||||
assert trade
|
||||
|
||||
oobj = Order.parse_from_ccxt_object(enter_order, enter_order['symbol'], 'buy')
|
||||
oobj = Order.parse_from_ccxt_object(enter_order, enter_order['symbol'], trade.enter_side)
|
||||
trade.update_trade(oobj)
|
||||
oobj = Order.parse_from_ccxt_object(exit_order, exit_order['symbol'], 'sell')
|
||||
oobj = Order.parse_from_ccxt_object(exit_order, exit_order['symbol'], trade.exit_side)
|
||||
trade.update_trade(oobj)
|
||||
assert trade.is_open is False
|
||||
|
||||
@@ -2385,8 +2406,8 @@ def test_manage_open_orders_entry_usercustom(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker_usdt,
|
||||
fetch_order=MagicMock(return_value=old_order),
|
||||
cancel_order_with_result=cancel_order_wr_mock,
|
||||
cancel_order=cancel_order_mock,
|
||||
cancel_order_with_result=cancel_order_wr_mock,
|
||||
get_fee=fee
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
@@ -2434,7 +2455,9 @@ 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)
|
||||
old_order['id'] = open_trade.open_order_id
|
||||
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)
|
||||
limit_buy_cancel['status'] = 'canceled'
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
|
||||
@@ -2625,7 +2648,9 @@ 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}
|
||||
limit_sell_order_old['id'] = open_trade_usdt.open_order_id
|
||||
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
||||
order = Order.parse_from_ccxt_object(limit_sell_order_old, 'mocked', 'sell')
|
||||
open_trade_usdt.orders[0] = order
|
||||
if is_short:
|
||||
limit_sell_order_old['side'] = 'buy'
|
||||
open_trade_usdt.is_short = is_short
|
||||
@@ -2753,6 +2778,8 @@ 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
|
||||
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@@ -2787,6 +2814,7 @@ def test_manage_open_orders_partial(
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
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['side'] = 'sell' if is_short else 'buy'
|
||||
limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
|
||||
@@ -2872,6 +2900,7 @@ def test_manage_open_orders_partial_except(
|
||||
limit_buy_order_old_partial_canceled, mocker
|
||||
) -> None:
|
||||
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
|
||||
@@ -3090,7 +3119,27 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None:
|
||||
close_date=arrow.utcnow().datetime,
|
||||
exit_reason="sell_reason_whatever",
|
||||
)
|
||||
order = {'remaining': 1,
|
||||
trade.orders = [
|
||||
Order(
|
||||
ft_order_side='buy',
|
||||
ft_pair=trade.pair,
|
||||
ft_is_open=True,
|
||||
order_id='123456',
|
||||
status="closed",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side="buy",
|
||||
price=trade.open_rate,
|
||||
average=trade.open_rate,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
cost=trade.open_rate * trade.amount,
|
||||
order_date=trade.open_date,
|
||||
order_filled_date=trade.open_date,
|
||||
),
|
||||
]
|
||||
order = {'id': "123456",
|
||||
'remaining': 1,
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
@@ -3214,6 +3263,9 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'stake_amount': pytest.approx(60),
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -3274,6 +3326,9 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'stake_amount': pytest.approx(60),
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -3355,6 +3410,9 @@ def test_execute_trade_exit_custom_exit_price(
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'stake_amount': pytest.approx(60),
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -3423,6 +3481,9 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'stake_amount': pytest.approx(60),
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -3626,7 +3687,7 @@ def test_execute_trade_exit_market_order(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker_usdt,
|
||||
get_fee=fee,
|
||||
_is_dry_limit_order_filled=MagicMock(return_value=False),
|
||||
_is_dry_limit_order_filled=MagicMock(return_value=True),
|
||||
)
|
||||
patch_whitelist(mocker, default_conf_usdt)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
@@ -3642,7 +3703,8 @@ def test_execute_trade_exit_market_order(
|
||||
# Increase the price and sell it
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker_usdt_sell_up
|
||||
fetch_ticker=ticker_usdt_sell_up,
|
||||
_is_dry_limit_order_filled=MagicMock(return_value=False),
|
||||
)
|
||||
freqtrade.config['order_types']['exit'] = 'market'
|
||||
|
||||
@@ -3653,9 +3715,9 @@ def test_execute_trade_exit_market_order(
|
||||
)
|
||||
|
||||
assert not trade.is_open
|
||||
assert trade.close_profit == profit_ratio
|
||||
assert pytest.approx(trade.close_profit) == profit_ratio
|
||||
|
||||
assert rpc_mock.call_count == 3
|
||||
assert rpc_mock.call_count == 4
|
||||
last_msg = rpc_mock.call_args_list[-2][0][0]
|
||||
assert {
|
||||
'type': RPCMessageType.EXIT,
|
||||
@@ -3681,6 +3743,9 @@ def test_execute_trade_exit_market_order(
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
'sub_trade': False,
|
||||
'cumulative_profit': 0.0,
|
||||
'stake_amount': pytest.approx(60),
|
||||
|
||||
} == last_msg
|
||||
|
||||
@@ -3752,7 +3817,7 @@ def test_exit_profit_only(
|
||||
'last': bid
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_order_open[eside],
|
||||
limit_order[eside],
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
@@ -3951,9 +4016,9 @@ def test_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limit_order_
|
||||
|
||||
# Test if entry-signal is absent (should sell due to roi = true)
|
||||
if is_short:
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_short=False)
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_short=False, exit_tag='something')
|
||||
else:
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=False, exit_tag='something')
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.exit_reason == ExitType.ROI.value
|
||||
|
||||
@@ -4044,7 +4109,7 @@ def test_trailing_stop_loss_positive(
|
||||
'last': enter_price - (-0.01 if is_short else 0.01),
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_order_open[eside],
|
||||
limit_order[eside],
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
@@ -4595,7 +4660,7 @@ def test_order_book_entry_pricing1(mocker, default_conf_usdt, order_book_l2, exc
|
||||
with pytest.raises(PricingError):
|
||||
freqtrade.exchange.get_rate('ETH/USDT', side="entry", is_short=False, refresh=True)
|
||||
assert log_has_re(
|
||||
r'Entry Price at location 1 from orderbook could not be determined.', caplog)
|
||||
r'ETH/USDT - Entry Price at location 1 from orderbook could not be determined.', caplog)
|
||||
else:
|
||||
assert freqtrade.exchange.get_rate(
|
||||
'ETH/USDT', side="entry", is_short=False, refresh=True) == 0.043935
|
||||
@@ -4674,8 +4739,9 @@ def test_order_book_exit_pricing(
|
||||
return_value={'bids': [[]], 'asks': [[]]})
|
||||
with pytest.raises(PricingError):
|
||||
freqtrade.handle_trade(trade)
|
||||
assert log_has_re(r'Exit Price at location 1 from orderbook could not be determined\..*',
|
||||
caplog)
|
||||
assert log_has_re(
|
||||
r"ETH/USDT - Exit Price at location 1 from orderbook could not be determined\..*",
|
||||
caplog)
|
||||
|
||||
|
||||
def test_startup_state(default_conf_usdt, mocker):
|
||||
@@ -5348,7 +5414,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
'status': None,
|
||||
'price': 9,
|
||||
'amount': 12,
|
||||
'cost': 100,
|
||||
'cost': 108,
|
||||
'ft_is_open': True,
|
||||
'id': '651',
|
||||
'order_id': '651'
|
||||
@@ -5443,7 +5509,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
assert trade.open_order_id is None
|
||||
assert pytest.approx(trade.open_rate) == 9.90909090909
|
||||
assert trade.amount == 22
|
||||
assert trade.stake_amount == 218
|
||||
assert pytest.approx(trade.stake_amount) == 218
|
||||
|
||||
orders = Order.query.all()
|
||||
assert orders
|
||||
@@ -5496,6 +5562,329 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
# Make sure the closed order is found as the second order.
|
||||
order = trade.select_order('buy', False)
|
||||
assert order.order_id == '652'
|
||||
closed_sell_dca_order_1 = {
|
||||
'ft_pair': pair,
|
||||
'status': 'closed',
|
||||
'ft_order_side': 'sell',
|
||||
'side': 'sell',
|
||||
'type': 'limit',
|
||||
'price': 8,
|
||||
'average': 8,
|
||||
'amount': 15,
|
||||
'filled': 15,
|
||||
'cost': 120,
|
||||
'ft_is_open': False,
|
||||
'id': '653',
|
||||
'order_id': '653'
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_1))
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_1))
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_1))
|
||||
assert freqtrade.execute_trade_exit(trade=trade, limit=8,
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT),
|
||||
sub_trade_amt=15)
|
||||
|
||||
# Assert trade is as expected (averaged dca)
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert trade.is_open
|
||||
assert trade.amount == 22
|
||||
assert trade.stake_amount == 192.05405405405406
|
||||
assert pytest.approx(trade.open_rate) == 8.729729729729
|
||||
|
||||
orders = Order.query.all()
|
||||
assert orders
|
||||
assert len(orders) == 4
|
||||
|
||||
# Make sure the closed order is found as the second order.
|
||||
order = trade.select_order('sell', False)
|
||||
assert order.order_id == '653'
|
||||
|
||||
|
||||
def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
|
||||
"""
|
||||
TODO: Should be adjusted to test both long and short
|
||||
buy 100 @ 11
|
||||
sell 50 @ 8
|
||||
sell 50 @ 16
|
||||
"""
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
patch_wallet(mocker, free=10000)
|
||||
default_conf_usdt.update({
|
||||
"position_adjustment_enable": True,
|
||||
"dry_run": False,
|
||||
"stake_amount": 200.0,
|
||||
"dry_run_wallet": 1000.0,
|
||||
})
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
||||
bid = 11
|
||||
amount = 100
|
||||
buy_rate_mock = MagicMock(return_value=bid)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_rate=buy_rate_mock,
|
||||
fetch_ticker=MagicMock(return_value={
|
||||
'bid': 10,
|
||||
'ask': 12,
|
||||
'last': 11
|
||||
}),
|
||||
get_min_pair_stake_amount=MagicMock(return_value=1),
|
||||
get_fee=fee,
|
||||
)
|
||||
pair = 'ETH/USDT'
|
||||
# Initial buy
|
||||
closed_successful_buy_order = {
|
||||
'pair': pair,
|
||||
'ft_pair': pair,
|
||||
'ft_order_side': 'buy',
|
||||
'side': 'buy',
|
||||
'type': 'limit',
|
||||
'status': 'closed',
|
||||
'price': bid,
|
||||
'average': bid,
|
||||
'cost': bid * amount,
|
||||
'amount': amount,
|
||||
'filled': amount,
|
||||
'ft_is_open': False,
|
||||
'id': '600',
|
||||
'order_id': '600'
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
MagicMock(return_value=closed_successful_buy_order))
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||
MagicMock(return_value=closed_successful_buy_order))
|
||||
assert freqtrade.execute_entry(pair, amount)
|
||||
# Should create an closed trade with an no open order id
|
||||
# Order is filled and trade is open
|
||||
orders = Order.query.all()
|
||||
assert orders
|
||||
assert len(orders) == 1
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert trade.open_rate == bid
|
||||
assert trade.stake_amount == bid * amount
|
||||
|
||||
# Assume it does nothing since order is closed and trade is open
|
||||
freqtrade.update_closed_trades_without_assigned_fees()
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert trade.open_rate == bid
|
||||
assert trade.stake_amount == bid * amount
|
||||
assert not trade.fee_updated(trade.entry_side)
|
||||
|
||||
freqtrade.manage_open_orders()
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert trade.open_rate == bid
|
||||
assert trade.stake_amount == bid * amount
|
||||
assert not trade.fee_updated(trade.entry_side)
|
||||
|
||||
amount = 50
|
||||
ask = 8
|
||||
closed_sell_dca_order_1 = {
|
||||
'ft_pair': pair,
|
||||
'status': 'closed',
|
||||
'ft_order_side': 'sell',
|
||||
'side': 'sell',
|
||||
'type': 'limit',
|
||||
'price': ask,
|
||||
'average': ask,
|
||||
'amount': amount,
|
||||
'filled': amount,
|
||||
'cost': amount * ask,
|
||||
'ft_is_open': False,
|
||||
'id': '601',
|
||||
'order_id': '601'
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_1))
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_1))
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_1))
|
||||
assert freqtrade.execute_trade_exit(trade=trade, limit=ask,
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT),
|
||||
sub_trade_amt=amount)
|
||||
trades: List[Trade] = trade.get_open_trades_without_assigned_fees()
|
||||
assert len(trades) == 1
|
||||
# Assert trade is as expected (averaged dca)
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert trade.amount == 50
|
||||
assert trade.open_rate == 11
|
||||
assert trade.stake_amount == 550
|
||||
assert pytest.approx(trade.realized_profit) == -152.375
|
||||
assert pytest.approx(trade.close_profit_abs) == -152.375
|
||||
|
||||
orders = Order.query.all()
|
||||
assert orders
|
||||
assert len(orders) == 2
|
||||
# Make sure the closed order is found as the second order.
|
||||
order = trade.select_order('sell', False)
|
||||
assert order.order_id == '601'
|
||||
|
||||
amount = 50
|
||||
ask = 16
|
||||
closed_sell_dca_order_2 = {
|
||||
'ft_pair': pair,
|
||||
'status': 'closed',
|
||||
'ft_order_side': 'sell',
|
||||
'side': 'sell',
|
||||
'type': 'limit',
|
||||
'price': ask,
|
||||
'average': ask,
|
||||
'amount': amount,
|
||||
'filled': amount,
|
||||
'cost': amount * ask,
|
||||
'ft_is_open': False,
|
||||
'id': '602',
|
||||
'order_id': '602'
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_2))
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_2))
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_2))
|
||||
assert freqtrade.execute_trade_exit(trade=trade, limit=ask,
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT),
|
||||
sub_trade_amt=amount)
|
||||
# Assert trade is as expected (averaged dca)
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert trade.amount == 50
|
||||
assert trade.open_rate == 11
|
||||
assert trade.stake_amount == 550
|
||||
# Trade fully realized
|
||||
assert pytest.approx(trade.realized_profit) == 94.25
|
||||
assert pytest.approx(trade.close_profit_abs) == 94.25
|
||||
orders = Order.query.all()
|
||||
assert orders
|
||||
assert len(orders) == 3
|
||||
|
||||
# Make sure the closed order is found as the second order.
|
||||
order = trade.select_order('sell', False)
|
||||
assert order.order_id == '602'
|
||||
assert trade.is_open is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data', [
|
||||
(
|
||||
# tuple 1 - side amount, price
|
||||
# tuple 2 - amount, open_rate, stake_amount, cumulative_profit, realized_profit, rel_profit
|
||||
(('buy', 100, 10), (100.0, 10.0, 1000.0, 0.0, None, None)),
|
||||
(('buy', 100, 15), (200.0, 12.5, 2500.0, 0.0, None, None)),
|
||||
(('sell', 50, 12), (150.0, 12.5, 1875.0, -28.0625, -28.0625, -0.044788)),
|
||||
(('sell', 100, 20), (50.0, 12.5, 625.0, 713.8125, 741.875, 0.59201995)),
|
||||
(('sell', 50, 5), (50.0, 12.5, 625.0, 336.625, 336.625, 0.1343142)), # final profit (sum)
|
||||
),
|
||||
(
|
||||
(('buy', 100, 3), (100.0, 3.0, 300.0, 0.0, None, None)),
|
||||
(('buy', 100, 7), (200.0, 5.0, 1000.0, 0.0, None, None)),
|
||||
(('sell', 100, 11), (100.0, 5.0, 500.0, 596.0, 596.0, 1.189027)),
|
||||
(('buy', 150, 15), (250.0, 11.0, 2750.0, 596.0, 596.0, 1.189027)),
|
||||
(('sell', 100, 19), (150.0, 11.0, 1650.0, 1388.5, 792.5, 0.7186579)),
|
||||
(('sell', 150, 23), (150.0, 11.0, 1650.0, 3175.75, 3175.75, 0.9747170)), # final profit
|
||||
)
|
||||
])
|
||||
def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None:
|
||||
default_conf_usdt.update({
|
||||
"position_adjustment_enable": True,
|
||||
"dry_run": False,
|
||||
"stake_amount": 200.0,
|
||||
"dry_run_wallet": 1000.0,
|
||||
})
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
patch_wallet(mocker, free=10000)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
trade = None
|
||||
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
||||
for idx, (order, result) in enumerate(data):
|
||||
amount = order[1]
|
||||
price = order[2]
|
||||
price_mock = MagicMock(return_value=price)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_rate=price_mock,
|
||||
fetch_ticker=MagicMock(return_value={
|
||||
'bid': 10,
|
||||
'ask': 12,
|
||||
'last': 11
|
||||
}),
|
||||
get_min_pair_stake_amount=MagicMock(return_value=1),
|
||||
get_fee=fee,
|
||||
)
|
||||
pair = 'ETH/USDT'
|
||||
closed_successful_order = {
|
||||
'pair': pair,
|
||||
'ft_pair': pair,
|
||||
'ft_order_side': order[0],
|
||||
'side': order[0],
|
||||
'type': 'limit',
|
||||
'status': 'closed',
|
||||
'price': price,
|
||||
'average': price,
|
||||
'cost': price * amount,
|
||||
'amount': amount,
|
||||
'filled': amount,
|
||||
'ft_is_open': False,
|
||||
'id': f'60{idx}',
|
||||
'order_id': f'60{idx}'
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
MagicMock(return_value=closed_successful_order))
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||
MagicMock(return_value=closed_successful_order))
|
||||
if order[0] == 'buy':
|
||||
assert freqtrade.execute_entry(pair, amount, trade=trade)
|
||||
else:
|
||||
assert freqtrade.execute_trade_exit(
|
||||
trade=trade, limit=price,
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT),
|
||||
sub_trade_amt=amount)
|
||||
|
||||
orders1 = Order.query.all()
|
||||
assert orders1
|
||||
assert len(orders1) == idx + 1
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
if idx < len(data) - 1:
|
||||
assert trade.is_open is True
|
||||
assert trade.open_order_id is None
|
||||
assert trade.amount == result[0]
|
||||
assert trade.open_rate == result[1]
|
||||
assert trade.stake_amount == result[2]
|
||||
assert pytest.approx(trade.realized_profit) == result[3]
|
||||
assert pytest.approx(trade.close_profit_abs) == result[4]
|
||||
assert pytest.approx(trade.close_profit) == result[5]
|
||||
|
||||
order_obj = trade.select_order(order[0], False)
|
||||
assert order_obj.order_id == f'60{idx}'
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert trade.is_open is False
|
||||
|
||||
|
||||
def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||
@@ -5519,9 +5908,25 @@ def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, ca
|
||||
"max_entry_position_adjustment": 0,
|
||||
})
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
|
||||
buy_rate_mock = MagicMock(return_value=10)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_rate=buy_rate_mock,
|
||||
fetch_ticker=MagicMock(return_value={
|
||||
'bid': 10,
|
||||
'ask': 12,
|
||||
'last': 11
|
||||
}),
|
||||
get_min_pair_stake_amount=MagicMock(return_value=1),
|
||||
get_fee=fee,
|
||||
)
|
||||
create_mock_trades(fee)
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=10)
|
||||
freqtrade.process_open_trade_positions()
|
||||
assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog)
|
||||
|
||||
caplog.clear()
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-10)
|
||||
freqtrade.process_open_trade_positions()
|
||||
assert log_has_re(r"LIMIT_SELL has been fulfilled.*", caplog)
|
||||
|
||||
@@ -6,7 +6,7 @@ from freqtrade.enums import ExitCheckTuple, ExitType
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.persistence.models import Order
|
||||
from freqtrade.rpc.rpc import RPC
|
||||
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
||||
from tests.conftest import get_patched_freqtradebot, log_has_re, patch_get_signal
|
||||
|
||||
|
||||
def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
@@ -455,3 +455,60 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
||||
# Check the 2 filled orders equal the above amount
|
||||
assert pytest.approx(trade.orders[1].amount) == 30.150753768
|
||||
assert pytest.approx(trade.orders[-1].amount) == 61.538461232
|
||||
|
||||
|
||||
def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog) -> None:
|
||||
default_conf_usdt['position_adjustment_enable'] = True
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker_usdt,
|
||||
get_fee=fee,
|
||||
amount_to_precision=lambda s, x, y: y,
|
||||
price_to_precision=lambda s, x, y: y,
|
||||
get_min_pair_stake_amount=MagicMock(return_value=10),
|
||||
)
|
||||
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade.enter_positions()
|
||||
|
||||
assert len(Trade.get_trades().all()) == 1
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert pytest.approx(trade.amount) == 30.0
|
||||
assert trade.open_rate == 2.0
|
||||
|
||||
# Too small size
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-59)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 1
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert pytest.approx(trade.amount) == 30.0
|
||||
assert log_has_re("Remaining amount of 1.6.* would be too small.", caplog)
|
||||
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-20)
|
||||
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
assert trade.orders[-1].ft_order_side == 'sell'
|
||||
assert pytest.approx(trade.stake_amount) == 40.198
|
||||
assert pytest.approx(trade.amount) == 20.099
|
||||
assert trade.open_rate == 2.0
|
||||
assert trade.is_open
|
||||
caplog.clear()
|
||||
|
||||
# Sell more than what we got (we got ~20 coins left)
|
||||
# First adjusts the amount to 20 - then rejects.
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-50)
|
||||
freqtrade.process()
|
||||
assert log_has_re("Adjusting amount to trade.amount as it is higher.*", caplog)
|
||||
assert log_has_re("Remaining amount of 0.0 would be too small.", caplog)
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 2
|
||||
assert trade.orders[-1].ft_order_side == 'sell'
|
||||
assert pytest.approx(trade.stake_amount) == 40.198
|
||||
assert trade.is_open
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import time_machine
|
||||
|
||||
from freqtrade.configuration import PeriodicCache
|
||||
from freqtrade.util import PeriodicCache
|
||||
|
||||
|
||||
def test_ttl_cache():
|
||||
|
||||
+345
-81
@@ -99,7 +99,7 @@ def test_enter_exit_side(fee, is_short):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_set_stop_loss_isolated_liq(fee):
|
||||
def test_set_stop_loss_liquidation(fee):
|
||||
trade = Trade(
|
||||
id=2,
|
||||
pair='ADA/USDT',
|
||||
@@ -115,73 +115,94 @@ def test_set_stop_loss_isolated_liq(fee):
|
||||
leverage=2.0,
|
||||
trading_mode=margin
|
||||
)
|
||||
trade.set_isolated_liq(0.09)
|
||||
trade.set_liquidation_price(0.09)
|
||||
assert trade.liquidation_price == 0.09
|
||||
assert trade.stop_loss is None
|
||||
assert trade.initial_stop_loss is None
|
||||
|
||||
trade._set_stop_loss(0.1, (1.0 / 9.0))
|
||||
trade.adjust_stop_loss(2.0, 0.2, True)
|
||||
assert trade.liquidation_price == 0.09
|
||||
assert trade.stop_loss == 0.1
|
||||
assert trade.initial_stop_loss == 0.1
|
||||
assert trade.stop_loss == 1.8
|
||||
assert trade.initial_stop_loss == 1.8
|
||||
|
||||
trade.set_isolated_liq(0.08)
|
||||
trade.set_liquidation_price(0.08)
|
||||
assert trade.liquidation_price == 0.08
|
||||
assert trade.stop_loss == 0.1
|
||||
assert trade.initial_stop_loss == 0.1
|
||||
assert trade.stop_loss == 1.8
|
||||
assert trade.initial_stop_loss == 1.8
|
||||
|
||||
trade.set_isolated_liq(0.11)
|
||||
trade._set_stop_loss(0.1, 0)
|
||||
trade.set_liquidation_price(0.11)
|
||||
trade.adjust_stop_loss(2.0, 0.2)
|
||||
assert trade.liquidation_price == 0.11
|
||||
assert trade.stop_loss == 0.11
|
||||
assert trade.initial_stop_loss == 0.1
|
||||
# Stoploss does not change from liquidation price
|
||||
assert trade.stop_loss == 1.8
|
||||
assert trade.initial_stop_loss == 1.8
|
||||
|
||||
# lower stop doesn't move stoploss
|
||||
trade._set_stop_loss(0.1, 0)
|
||||
trade.adjust_stop_loss(1.8, 0.2)
|
||||
assert trade.liquidation_price == 0.11
|
||||
assert trade.stop_loss == 0.11
|
||||
assert trade.initial_stop_loss == 0.1
|
||||
assert trade.stop_loss == 1.8
|
||||
assert trade.initial_stop_loss == 1.8
|
||||
|
||||
# higher stop does move stoploss
|
||||
trade.adjust_stop_loss(2.1, 0.1)
|
||||
assert trade.liquidation_price == 0.11
|
||||
assert pytest.approx(trade.stop_loss) == 1.994999
|
||||
assert trade.initial_stop_loss == 1.8
|
||||
assert trade.stoploss_or_liquidation == trade.stop_loss
|
||||
|
||||
trade.stop_loss = None
|
||||
trade.liquidation_price = None
|
||||
trade.initial_stop_loss = None
|
||||
trade.initial_stop_loss_pct = None
|
||||
|
||||
trade._set_stop_loss(0.07, 0)
|
||||
trade.adjust_stop_loss(2.0, 0.1, True)
|
||||
assert trade.liquidation_price is None
|
||||
assert trade.stop_loss == 0.07
|
||||
assert trade.initial_stop_loss == 0.07
|
||||
assert trade.stop_loss == 1.9
|
||||
assert trade.initial_stop_loss == 1.9
|
||||
assert trade.stoploss_or_liquidation == 1.9
|
||||
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
trade.stop_loss = None
|
||||
trade.initial_stop_loss = None
|
||||
trade.initial_stop_loss_pct = None
|
||||
|
||||
trade.set_isolated_liq(0.09)
|
||||
assert trade.liquidation_price == 0.09
|
||||
trade.set_liquidation_price(3.09)
|
||||
assert trade.liquidation_price == 3.09
|
||||
assert trade.stop_loss is None
|
||||
assert trade.initial_stop_loss is None
|
||||
|
||||
trade._set_stop_loss(0.08, (1.0 / 9.0))
|
||||
assert trade.liquidation_price == 0.09
|
||||
assert trade.stop_loss == 0.08
|
||||
assert trade.initial_stop_loss == 0.08
|
||||
trade.adjust_stop_loss(2.0, 0.2)
|
||||
assert trade.liquidation_price == 3.09
|
||||
assert trade.stop_loss == 2.2
|
||||
assert trade.initial_stop_loss == 2.2
|
||||
assert trade.stoploss_or_liquidation == 2.2
|
||||
|
||||
trade.set_isolated_liq(0.1)
|
||||
assert trade.liquidation_price == 0.1
|
||||
assert trade.stop_loss == 0.08
|
||||
assert trade.initial_stop_loss == 0.08
|
||||
trade.set_liquidation_price(3.1)
|
||||
assert trade.liquidation_price == 3.1
|
||||
assert trade.stop_loss == 2.2
|
||||
assert trade.initial_stop_loss == 2.2
|
||||
assert trade.stoploss_or_liquidation == 2.2
|
||||
|
||||
trade.set_isolated_liq(0.07)
|
||||
trade._set_stop_loss(0.1, (1.0 / 8.0))
|
||||
assert trade.liquidation_price == 0.07
|
||||
assert trade.stop_loss == 0.07
|
||||
assert trade.initial_stop_loss == 0.08
|
||||
trade.set_liquidation_price(3.8)
|
||||
assert trade.liquidation_price == 3.8
|
||||
# Stoploss does not change from liquidation price
|
||||
assert trade.stop_loss == 2.2
|
||||
assert trade.initial_stop_loss == 2.2
|
||||
|
||||
# Stop doesn't move stop higher
|
||||
trade._set_stop_loss(0.1, (1.0 / 9.0))
|
||||
assert trade.liquidation_price == 0.07
|
||||
assert trade.stop_loss == 0.07
|
||||
assert trade.initial_stop_loss == 0.08
|
||||
trade.adjust_stop_loss(2.0, 0.3)
|
||||
assert trade.liquidation_price == 3.8
|
||||
assert trade.stop_loss == 2.2
|
||||
assert trade.initial_stop_loss == 2.2
|
||||
|
||||
# Stoploss does move lower
|
||||
trade.set_liquidation_price(1.5)
|
||||
trade.adjust_stop_loss(1.8, 0.1)
|
||||
assert trade.liquidation_price == 1.5
|
||||
assert pytest.approx(trade.stop_loss) == 1.89
|
||||
assert trade.initial_stop_loss == 2.2
|
||||
assert trade.stoploss_or_liquidation == 1.5
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest,trading_mode', [
|
||||
@@ -479,8 +500,9 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
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 trade.open_rate == open_rate
|
||||
@@ -493,14 +515,15 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_
|
||||
caplog)
|
||||
|
||||
caplog.clear()
|
||||
trade.open_order_id = 'something'
|
||||
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 trade.close_rate == close_rate
|
||||
assert trade.close_profit == profit
|
||||
assert pytest.approx(trade.close_profit) == profit
|
||||
assert trade.close_date is not None
|
||||
assert log_has_re(f"LIMIT_{exit_side.upper()} has been fulfilled for "
|
||||
r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, "
|
||||
@@ -527,8 +550,9 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
||||
leverage=1.0,
|
||||
)
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
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 trade.open_rate == 2.0
|
||||
@@ -541,12 +565,13 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
||||
|
||||
caplog.clear()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = 'something'
|
||||
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 trade.close_rate == 2.2
|
||||
assert trade.close_profit == round(0.0945137157107232, 8)
|
||||
assert pytest.approx(trade.close_profit) == 0.094513715710723
|
||||
assert trade.close_date is not None
|
||||
assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, "
|
||||
r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, "
|
||||
@@ -605,14 +630,14 @@ def test_calc_open_close_trade_price(
|
||||
trade.open_rate = 2.0
|
||||
trade.close_rate = 2.2
|
||||
trade.recalc_open_trade_value()
|
||||
assert isclose(trade._calc_open_trade_value(), open_value)
|
||||
assert isclose(trade._calc_open_trade_value(trade.amount, trade.open_rate), open_value)
|
||||
assert isclose(trade.calc_close_trade_value(trade.close_rate), close_value)
|
||||
assert isclose(trade.calc_profit(trade.close_rate), round(profit, 8))
|
||||
assert pytest.approx(trade.calc_profit_ratio(trade.close_rate)) == profit_ratio
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_trade_close(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
def test_trade_close(fee):
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
@@ -624,14 +649,41 @@ def test_trade_close(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
||||
interest_rate=0.0005,
|
||||
exchange='binance',
|
||||
trading_mode=margin
|
||||
trading_mode=margin,
|
||||
leverage=1.0,
|
||||
)
|
||||
trade.orders.append(Order(
|
||||
ft_order_side=trade.entry_side,
|
||||
order_id=f'{trade.pair}-{trade.entry_side}-{trade.open_date}',
|
||||
ft_pair=trade.pair,
|
||||
amount=trade.amount,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
price=trade.open_rate,
|
||||
average=trade.open_rate,
|
||||
status="closed",
|
||||
order_type="limit",
|
||||
side=trade.entry_side,
|
||||
))
|
||||
trade.orders.append(Order(
|
||||
ft_order_side=trade.exit_side,
|
||||
order_id=f'{trade.pair}-{trade.exit_side}-{trade.open_date}',
|
||||
ft_pair=trade.pair,
|
||||
amount=trade.amount,
|
||||
filled=trade.amount,
|
||||
remaining=0,
|
||||
price=2.2,
|
||||
average=2.2,
|
||||
status="closed",
|
||||
order_type="limit",
|
||||
side=trade.exit_side,
|
||||
))
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
assert trade.is_open is True
|
||||
trade.close(2.2)
|
||||
assert trade.is_open is False
|
||||
assert trade.close_profit == round(0.0945137157107232, 8)
|
||||
assert pytest.approx(trade.close_profit) == 0.094513715
|
||||
assert trade.close_date is not None
|
||||
|
||||
new_date = arrow.Arrow(2020, 2, 2, 15, 6, 1).datetime,
|
||||
@@ -763,7 +815,7 @@ def test_calc_open_trade_value(
|
||||
trade.update_trade(oobj) # Buy @ 2.0
|
||||
|
||||
# Get the open rate price with the standard fee rate
|
||||
assert trade._calc_open_trade_value() == result
|
||||
assert trade._calc_open_trade_value(trade.amount, trade.open_rate) == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -853,7 +905,7 @@ def test_calc_close_trade_price(
|
||||
('binance', False, 1, 1.9, 0.003, -3.3209999, -0.055211970, spot, 0),
|
||||
('binance', False, 1, 2.2, 0.003, 5.6520000, 0.093965087, spot, 0),
|
||||
|
||||
# # FUTURES, funding_fee=1
|
||||
# FUTURES, funding_fee=1
|
||||
('binance', False, 1, 2.1, 0.0025, 3.6925, 0.06138819, futures, 1),
|
||||
('binance', False, 3, 2.1, 0.0025, 3.6925, 0.18416458, futures, 1),
|
||||
('binance', True, 1, 2.1, 0.0025, -2.3074999, -0.03855472, futures, 1),
|
||||
@@ -1139,6 +1191,11 @@ def test_calc_profit(
|
||||
assert pytest.approx(trade.calc_profit(rate=close_rate)) == round(profit, 8)
|
||||
assert pytest.approx(trade.calc_profit_ratio(rate=close_rate)) == round(profit_ratio, 8)
|
||||
|
||||
assert pytest.approx(trade.calc_profit(close_rate, trade.amount,
|
||||
trade.open_rate)) == round(profit, 8)
|
||||
assert pytest.approx(trade.calc_profit_ratio(close_rate, trade.amount,
|
||||
trade.open_rate)) == round(profit_ratio, 8)
|
||||
|
||||
|
||||
def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
@@ -1200,7 +1257,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
0.00258580, {stake}, {amount},
|
||||
'2019-11-28 12:44:24.000000',
|
||||
0.0, 0.0, 0.0, '5m',
|
||||
'buy_order', 'stop_order_id222')
|
||||
'buy_order', 'dry_stop_order_id222')
|
||||
""".format(fee=fee.return_value,
|
||||
stake=default_conf.get("stake_amount"),
|
||||
amount=amount
|
||||
@@ -1226,7 +1283,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
'buy',
|
||||
'ETC/BTC',
|
||||
0,
|
||||
'buy_order',
|
||||
'dry_buy_order',
|
||||
'closed',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
@@ -1238,12 +1295,44 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
{amount * 0.00258580}
|
||||
),
|
||||
(
|
||||
1,
|
||||
'buy',
|
||||
'ETC/BTC',
|
||||
1,
|
||||
'dry_buy_order22',
|
||||
'canceled',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'buy',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
{amount * 0.00258580}
|
||||
),
|
||||
(
|
||||
1,
|
||||
'stoploss',
|
||||
'ETC/BTC',
|
||||
1,
|
||||
'dry_stop_order_id11X',
|
||||
'canceled',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'sell',
|
||||
0.00258580,
|
||||
{amount},
|
||||
{amount},
|
||||
0,
|
||||
'stop_order_id222',
|
||||
'closed',
|
||||
{amount * 0.00258580}
|
||||
),
|
||||
(
|
||||
1,
|
||||
'stoploss',
|
||||
'ETC/BTC',
|
||||
1,
|
||||
'dry_stop_order_id222',
|
||||
'open',
|
||||
'ETC/BTC',
|
||||
'limit',
|
||||
'sell',
|
||||
@@ -1292,22 +1381,31 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
assert trade.exit_reason is None
|
||||
assert trade.strategy is None
|
||||
assert trade.timeframe == '5m'
|
||||
assert trade.stoploss_order_id == 'stop_order_id222'
|
||||
assert trade.stoploss_order_id == 'dry_stop_order_id222'
|
||||
assert trade.stoploss_last_update is None
|
||||
assert log_has("trying trades_bak1", caplog)
|
||||
assert log_has("trying trades_bak2", caplog)
|
||||
assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0",
|
||||
caplog)
|
||||
assert trade.open_trade_value == trade._calc_open_trade_value()
|
||||
assert trade.open_trade_value == trade._calc_open_trade_value(trade.amount, trade.open_rate)
|
||||
assert trade.close_profit_abs is None
|
||||
|
||||
orders = trade.orders
|
||||
assert len(orders) == 2
|
||||
assert orders[0].order_id == 'buy_order'
|
||||
assert len(orders) == 4
|
||||
assert orders[0].order_id == 'dry_buy_order'
|
||||
assert orders[0].ft_order_side == 'buy'
|
||||
|
||||
assert orders[1].order_id == 'stop_order_id222'
|
||||
assert orders[1].ft_order_side == 'stoploss'
|
||||
assert orders[-1].order_id == 'dry_stop_order_id222'
|
||||
assert orders[-1].ft_order_side == 'stoploss'
|
||||
assert orders[-1].ft_is_open is True
|
||||
|
||||
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[2].order_id == 'dry_stop_order_id11X'
|
||||
assert orders[2].ft_order_side == 'stoploss'
|
||||
assert orders[2].ft_is_open is False
|
||||
|
||||
|
||||
def test_migrate_too_old(mocker, default_conf, fee, caplog):
|
||||
@@ -1465,26 +1563,26 @@ def test_adjust_stop_loss(fee):
|
||||
|
||||
# Get percent of profit with a custom rate (Higher than open rate)
|
||||
trade.adjust_stop_loss(1.3, -0.1)
|
||||
assert round(trade.stop_loss, 8) == 1.17
|
||||
assert pytest.approx(trade.stop_loss) == 1.17
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
assert trade.initial_stop_loss == 0.95
|
||||
assert trade.initial_stop_loss_pct == -0.05
|
||||
|
||||
# current rate lower again ... should not change
|
||||
trade.adjust_stop_loss(1.2, 0.1)
|
||||
assert round(trade.stop_loss, 8) == 1.17
|
||||
assert pytest.approx(trade.stop_loss) == 1.17
|
||||
assert trade.initial_stop_loss == 0.95
|
||||
assert trade.initial_stop_loss_pct == -0.05
|
||||
|
||||
# current rate higher... should raise stoploss
|
||||
trade.adjust_stop_loss(1.4, 0.1)
|
||||
assert round(trade.stop_loss, 8) == 1.26
|
||||
assert pytest.approx(trade.stop_loss) == 1.26
|
||||
assert trade.initial_stop_loss == 0.95
|
||||
assert trade.initial_stop_loss_pct == -0.05
|
||||
|
||||
# Initial is true but stop_loss set - so doesn't do anything
|
||||
trade.adjust_stop_loss(1.7, 0.1, True)
|
||||
assert round(trade.stop_loss, 8) == 1.26
|
||||
assert pytest.approx(trade.stop_loss) == 1.26
|
||||
assert trade.initial_stop_loss == 0.95
|
||||
assert trade.initial_stop_loss_pct == -0.05
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
@@ -1537,9 +1635,10 @@ def test_adjust_stop_loss_short(fee):
|
||||
assert trade.initial_stop_loss == 1.05
|
||||
assert trade.initial_stop_loss_pct == -0.05
|
||||
assert trade.stop_loss_pct == -0.1
|
||||
trade.set_isolated_liq(0.63)
|
||||
# Liquidation price is lower than stoploss - so liquidation would trigger first.
|
||||
trade.set_liquidation_price(0.63)
|
||||
trade.adjust_stop_loss(0.59, -0.1)
|
||||
assert trade.stop_loss == 0.63
|
||||
assert trade.stop_loss == 0.649
|
||||
assert trade.liquidation_price == 0.63
|
||||
|
||||
|
||||
@@ -1650,6 +1749,7 @@ def test_to_json(fee):
|
||||
'stake_amount': 0.001,
|
||||
'trade_duration': None,
|
||||
'trade_duration_s': None,
|
||||
'realized_profit': 0.0,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
@@ -1726,6 +1826,7 @@ def test_to_json(fee):
|
||||
'initial_stop_loss_abs': None,
|
||||
'initial_stop_loss_pct': None,
|
||||
'initial_stop_loss_ratio': None,
|
||||
'realized_profit': 0.0,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
@@ -1937,10 +2038,10 @@ def test_stoploss_reinitialization_short(default_conf, fee):
|
||||
assert trade_adj.initial_stop_loss == 1.01
|
||||
assert trade_adj.initial_stop_loss_pct == -0.05
|
||||
# Stoploss can't go above liquidation price
|
||||
trade_adj.set_isolated_liq(0.985)
|
||||
trade_adj.set_liquidation_price(0.985)
|
||||
trade.adjust_stop_loss(0.9799, -0.05)
|
||||
assert trade_adj.stop_loss == 0.985
|
||||
assert trade_adj.stop_loss == 0.985
|
||||
assert trade_adj.stop_loss == 0.989699
|
||||
assert trade_adj.liquidation_price == 0.985
|
||||
|
||||
|
||||
def test_update_fee(fee):
|
||||
@@ -2075,6 +2176,24 @@ def test_get_trades_proxy(fee, use_db, is_short):
|
||||
Trade.use_db = True
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize('is_short', [True, False])
|
||||
def test_get_trades__query(fee, is_short):
|
||||
query = Trade.get_trades([])
|
||||
# without orders there should be no join issued.
|
||||
query1 = Trade.get_trades([], include_orders=False)
|
||||
|
||||
assert "JOIN orders" in str(query)
|
||||
assert "JOIN orders" not in str(query1)
|
||||
|
||||
create_mock_trades(fee, is_short)
|
||||
query = Trade.get_trades([])
|
||||
query1 = Trade.get_trades([], include_orders=False)
|
||||
|
||||
assert "JOIN orders" in str(query)
|
||||
assert "JOIN orders" not in str(query1)
|
||||
|
||||
|
||||
def test_get_trades_backtest():
|
||||
Trade.use_db = False
|
||||
with pytest.raises(NotImplementedError, match=r"`Trade.get_trades\(\)` not .*"):
|
||||
@@ -2150,7 +2269,7 @@ def test_update_order_from_ccxt(caplog):
|
||||
'symbol': 'ADA/USDT',
|
||||
'type': 'limit',
|
||||
'price': 1234.5,
|
||||
'amount': 20.0,
|
||||
'amount': 20.0,
|
||||
'filled': 9,
|
||||
'remaining': 11,
|
||||
'status': 'open',
|
||||
@@ -2256,6 +2375,7 @@ def test_Trade_object_idem():
|
||||
'delete',
|
||||
'session',
|
||||
'commit',
|
||||
'rollback',
|
||||
'query',
|
||||
'open_date',
|
||||
'get_best_pair',
|
||||
@@ -2309,7 +2429,7 @@ def test_recalc_trade_from_orders(fee):
|
||||
)
|
||||
|
||||
assert fee.return_value == 0.0025
|
||||
assert trade._calc_open_trade_value() == o1_trade_val
|
||||
assert trade._calc_open_trade_value(trade.amount, trade.open_rate) == o1_trade_val
|
||||
assert trade.amount == o1_amount
|
||||
assert trade.stake_amount == o1_cost
|
||||
assert trade.open_rate == o1_rate
|
||||
@@ -2421,7 +2541,8 @@ def test_recalc_trade_from_orders(fee):
|
||||
assert pytest.approx(trade.fee_open_cost) == o1_fee_cost + o2_fee_cost + o3_fee_cost
|
||||
assert pytest.approx(trade.open_trade_value) == o1_trade_val + o2_trade_val + o3_trade_val
|
||||
|
||||
# Just to make sure sell orders are ignored, let's calculate one more time.
|
||||
# Just to make sure full sell orders are ignored, let's calculate one more time.
|
||||
|
||||
sell1 = Order(
|
||||
ft_order_side='sell',
|
||||
ft_pair=trade.pair,
|
||||
@@ -2583,7 +2704,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
|
||||
assert trade.open_trade_value == 2 * o1_trade_val
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
|
||||
# Just to make sure exit orders are ignored, let's calculate one more time.
|
||||
# Reduce position - this will reduce amount again.
|
||||
sell1 = Order(
|
||||
ft_order_side=exit_side,
|
||||
ft_pair=trade.pair,
|
||||
@@ -2594,7 +2715,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
|
||||
side=exit_side,
|
||||
price=4,
|
||||
average=3,
|
||||
filled=2,
|
||||
filled=o1_amount,
|
||||
remaining=1,
|
||||
cost=5,
|
||||
order_date=trade.open_date,
|
||||
@@ -2603,11 +2724,11 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
|
||||
trade.orders.append(sell1)
|
||||
trade.recalc_trade_from_orders()
|
||||
|
||||
assert trade.amount == 2 * o1_amount
|
||||
assert trade.stake_amount == 2 * o1_amount
|
||||
assert trade.amount == o1_amount
|
||||
assert trade.stake_amount == o1_amount
|
||||
assert trade.open_rate == o1_rate
|
||||
assert trade.fee_open_cost == 2 * o1_fee_cost
|
||||
assert trade.open_trade_value == 2 * o1_trade_val
|
||||
assert trade.fee_open_cost == o1_fee_cost
|
||||
assert trade.open_trade_value == o1_trade_val
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
|
||||
# Check with 1 order
|
||||
@@ -2631,11 +2752,11 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
|
||||
trade.recalc_trade_from_orders()
|
||||
|
||||
# Calling recalc with single initial order should not change anything
|
||||
assert trade.amount == 3 * o1_amount
|
||||
assert trade.stake_amount == 3 * o1_amount
|
||||
assert trade.amount == 2 * o1_amount
|
||||
assert trade.stake_amount == 2 * o1_amount
|
||||
assert trade.open_rate == o1_rate
|
||||
assert trade.fee_open_cost == 3 * o1_fee_cost
|
||||
assert trade.open_trade_value == 3 * o1_trade_val
|
||||
assert trade.fee_open_cost == 2 * o1_fee_cost
|
||||
assert trade.open_trade_value == 2 * o1_trade_val
|
||||
assert trade.nr_of_successful_entries == 3
|
||||
|
||||
|
||||
@@ -2699,5 +2820,148 @@ def test_order_to_ccxt(limit_buy_order_open):
|
||||
del raw_order['fee']
|
||||
del raw_order['datetime']
|
||||
del raw_order['info']
|
||||
assert raw_order['stopPrice'] is None
|
||||
del raw_order['stopPrice']
|
||||
del limit_buy_order_open['datetime']
|
||||
assert raw_order == limit_buy_order_open
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize('data', [
|
||||
{
|
||||
# tuple 1 - side, amount, price
|
||||
# tuple 2 - amount, open_rate, stake_amount, cumulative_profit, realized_profit, rel_profit
|
||||
'orders': [
|
||||
(('buy', 100, 10), (100.0, 10.0, 1000.0, 0.0, None, None)),
|
||||
(('buy', 100, 15), (200.0, 12.5, 2500.0, 0.0, None, None)),
|
||||
(('sell', 50, 12), (150.0, 12.5, 1875.0, -25.0, -25.0, -0.04)),
|
||||
(('sell', 100, 20), (50.0, 12.5, 625.0, 725.0, 750.0, 0.60)),
|
||||
(('sell', 50, 5), (50.0, 12.5, 625.0, 350.0, -375.0, -0.60)),
|
||||
],
|
||||
'end_profit': 350.0,
|
||||
'end_profit_ratio': 0.14,
|
||||
'fee': 0.0,
|
||||
},
|
||||
{
|
||||
'orders': [
|
||||
(('buy', 100, 10), (100.0, 10.0, 1000.0, 0.0, None, None)),
|
||||
(('buy', 100, 15), (200.0, 12.5, 2500.0, 0.0, None, None)),
|
||||
(('sell', 50, 12), (150.0, 12.5, 1875.0, -28.0625, -28.0625, -0.044788)),
|
||||
(('sell', 100, 20), (50.0, 12.5, 625.0, 713.8125, 741.875, 0.59201995)),
|
||||
(('sell', 50, 5), (50.0, 12.5, 625.0, 336.625, -377.1875, -0.60199501)),
|
||||
],
|
||||
'end_profit': 336.625,
|
||||
'end_profit_ratio': 0.1343142,
|
||||
'fee': 0.0025,
|
||||
},
|
||||
{
|
||||
'orders': [
|
||||
(('buy', 100, 3), (100.0, 3.0, 300.0, 0.0, None, None)),
|
||||
(('buy', 100, 7), (200.0, 5.0, 1000.0, 0.0, None, None)),
|
||||
(('sell', 100, 11), (100.0, 5.0, 500.0, 596.0, 596.0, 1.189027)),
|
||||
(('buy', 150, 15), (250.0, 11.0, 2750.0, 596.0, 596.0, 1.189027)),
|
||||
(('sell', 100, 19), (150.0, 11.0, 1650.0, 1388.5, 792.5, 0.7186579)),
|
||||
(('sell', 150, 23), (150.0, 11.0, 1650.0, 3175.75, 1787.25, 1.08048062)),
|
||||
],
|
||||
'end_profit': 3175.75,
|
||||
'end_profit_ratio': 0.9747170,
|
||||
'fee': 0.0025,
|
||||
},
|
||||
{
|
||||
# Test above without fees
|
||||
'orders': [
|
||||
(('buy', 100, 3), (100.0, 3.0, 300.0, 0.0, None, None)),
|
||||
(('buy', 100, 7), (200.0, 5.0, 1000.0, 0.0, None, None)),
|
||||
(('sell', 100, 11), (100.0, 5.0, 500.0, 600.0, 600.0, 1.2)),
|
||||
(('buy', 150, 15), (250.0, 11.0, 2750.0, 600.0, 600.0, 1.2)),
|
||||
(('sell', 100, 19), (150.0, 11.0, 1650.0, 1400.0, 800.0, 0.72727273)),
|
||||
(('sell', 150, 23), (150.0, 11.0, 1650.0, 3200.0, 1800.0, 1.09090909)),
|
||||
],
|
||||
'end_profit': 3200.0,
|
||||
'end_profit_ratio': 0.98461538,
|
||||
'fee': 0.0,
|
||||
},
|
||||
{
|
||||
'orders': [
|
||||
(('buy', 100, 8), (100.0, 8.0, 800.0, 0.0, None, None)),
|
||||
(('buy', 100, 9), (200.0, 8.5, 1700.0, 0.0, None, None)),
|
||||
(('sell', 100, 10), (100.0, 8.5, 850.0, 150.0, 150.0, 0.17647059)),
|
||||
(('buy', 150, 11), (250.0, 10, 2500.0, 150.0, 150.0, 0.17647059)),
|
||||
(('sell', 100, 12), (150.0, 10.0, 1500.0, 350.0, 350.0, 0.2)),
|
||||
(('sell', 150, 14), (150.0, 10.0, 1500.0, 950.0, 950.0, 0.40)),
|
||||
],
|
||||
'end_profit': 950.0,
|
||||
'end_profit_ratio': 0.283582,
|
||||
'fee': 0.0,
|
||||
},
|
||||
])
|
||||
def test_recalc_trade_from_orders_dca(data) -> None:
|
||||
|
||||
pair = 'ETH/USDT'
|
||||
trade = Trade(
|
||||
id=2,
|
||||
pair=pair,
|
||||
stake_amount=1000,
|
||||
open_rate=data['orders'][0][0][2],
|
||||
amount=data['orders'][0][0][1],
|
||||
is_open=True,
|
||||
open_date=arrow.utcnow().datetime,
|
||||
fee_open=data['fee'],
|
||||
fee_close=data['fee'],
|
||||
exchange='binance',
|
||||
is_short=False,
|
||||
leverage=1.0,
|
||||
trading_mode=TradingMode.SPOT
|
||||
)
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
for idx, (order, result) in enumerate(data['orders']):
|
||||
amount = order[1]
|
||||
price = order[2]
|
||||
|
||||
order_obj = Order(
|
||||
ft_order_side=order[0],
|
||||
ft_pair=trade.pair,
|
||||
order_id=f"order_{order[0]}_{idx}",
|
||||
ft_is_open=False,
|
||||
status="closed",
|
||||
symbol=trade.pair,
|
||||
order_type="market",
|
||||
side=order[0],
|
||||
price=price,
|
||||
average=price,
|
||||
filled=amount,
|
||||
remaining=0,
|
||||
cost=amount * price,
|
||||
order_date=arrow.utcnow().shift(hours=-10 + idx).datetime,
|
||||
order_filled_date=arrow.utcnow().shift(hours=-10 + idx).datetime,
|
||||
)
|
||||
trade.orders.append(order_obj)
|
||||
trade.recalc_trade_from_orders()
|
||||
Trade.commit()
|
||||
|
||||
orders1 = Order.query.all()
|
||||
assert orders1
|
||||
assert len(orders1) == idx + 1
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
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 trade.amount == result[0]
|
||||
assert trade.open_rate == result[1]
|
||||
assert trade.stake_amount == result[2]
|
||||
# TODO: enable the below.
|
||||
assert pytest.approx(trade.realized_profit) == result[3]
|
||||
# assert pytest.approx(trade.close_profit_abs) == result[4]
|
||||
assert pytest.approx(trade.close_profit) == result[5]
|
||||
|
||||
trade.close(price)
|
||||
assert pytest.approx(trade.close_profit_abs) == data['end_profit']
|
||||
assert pytest.approx(trade.close_profit) == data['end_profit_ratio']
|
||||
assert not trade.is_open
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
|
||||
+18
-18
@@ -72,7 +72,7 @@ def test_add_indicators(default_conf, testdatadir, caplog):
|
||||
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
# Generate buy/sell signals and indicators
|
||||
# Generate entry/exit signals and indicators
|
||||
data = strategy.analyze_ticker(data, {'pair': pair})
|
||||
fig = generate_empty_figure()
|
||||
|
||||
@@ -113,7 +113,7 @@ def test_add_areas(default_conf, testdatadir, caplog):
|
||||
ind_plain = {"macd": {"fill_to": "macdhist"}}
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
# Generate buy/sell signals and indicators
|
||||
# Generate entry/exit signals and indicators
|
||||
data = strategy.analyze_ticker(data, {'pair': pair})
|
||||
fig = generate_empty_figure()
|
||||
|
||||
@@ -165,24 +165,24 @@ def test_plot_trades(testdatadir, caplog):
|
||||
fig = plot_trades(fig, trades)
|
||||
figure = fig1.layout.figure
|
||||
|
||||
# Check buys - color, should be in first graph, ...
|
||||
trade_buy = find_trace_in_fig_data(figure.data, 'Trade buy')
|
||||
assert isinstance(trade_buy, go.Scatter)
|
||||
assert trade_buy.yaxis == 'y'
|
||||
assert len(trades) == len(trade_buy.x)
|
||||
assert trade_buy.marker.color == 'cyan'
|
||||
assert trade_buy.marker.symbol == 'circle-open'
|
||||
assert trade_buy.text[0] == '3.99%, buy_tag, roi, 15 min'
|
||||
# Check entry - color, should be in first graph, ...
|
||||
trade_entries = find_trace_in_fig_data(figure.data, 'Trade entry')
|
||||
assert isinstance(trade_entries, go.Scatter)
|
||||
assert trade_entries.yaxis == 'y'
|
||||
assert len(trades) == len(trade_entries.x)
|
||||
assert trade_entries.marker.color == 'cyan'
|
||||
assert trade_entries.marker.symbol == 'circle-open'
|
||||
assert trade_entries.text[0] == '3.99%, buy_tag, roi, 15 min'
|
||||
|
||||
trade_sell = find_trace_in_fig_data(figure.data, 'Sell - Profit')
|
||||
assert isinstance(trade_sell, go.Scatter)
|
||||
assert trade_sell.yaxis == 'y'
|
||||
assert len(trades.loc[trades['profit_ratio'] > 0]) == len(trade_sell.x)
|
||||
assert trade_sell.marker.color == 'green'
|
||||
assert trade_sell.marker.symbol == 'square-open'
|
||||
assert trade_sell.text[0] == '3.99%, buy_tag, roi, 15 min'
|
||||
trade_exit = find_trace_in_fig_data(figure.data, 'Exit - Profit')
|
||||
assert isinstance(trade_exit, go.Scatter)
|
||||
assert trade_exit.yaxis == 'y'
|
||||
assert len(trades.loc[trades['profit_ratio'] > 0]) == len(trade_exit.x)
|
||||
assert trade_exit.marker.color == 'green'
|
||||
assert trade_exit.marker.symbol == 'square-open'
|
||||
assert trade_exit.text[0] == '3.99%, buy_tag, roi, 15 min'
|
||||
|
||||
trade_sell_loss = find_trace_in_fig_data(figure.data, 'Sell - Loss')
|
||||
trade_sell_loss = find_trace_in_fig_data(figure.data, 'Exit - Loss')
|
||||
assert isinstance(trade_sell_loss, go.Scatter)
|
||||
assert trade_sell_loss.yaxis == 'y'
|
||||
assert len(trades.loc[trades['profit_ratio'] <= 0]) == len(trade_sell_loss.x)
|
||||
|
||||
Reference in New Issue
Block a user