Merge branch 'develop' into feature_keyval_storage

Update rpc/telegram to use MAX_MESSAGE_LENGTH.
This commit is contained in:
eSeR1805
2022-08-11 11:26:45 +03:00
109 changed files with 16046 additions and 13336 deletions
+61 -47
View File
@@ -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": [
{
+4 -2
View File
@@ -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)
+2 -2
View File
@@ -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',
+24
View File
@@ -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
+1 -1
View File
@@ -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):
+26 -1
View File
@@ -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:
+31 -26
View File
@@ -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
View File
@@ -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),
+15 -3
View File
@@ -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"))
+19
View File
@@ -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')
+2 -2
View File
@@ -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')
+2 -2
View File
@@ -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,
+5 -5
View File
@@ -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
+37 -24
View File
@@ -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
+44 -4
View File
@@ -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
+34 -3
View File
@@ -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
View File
@@ -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):
+53 -34
View File
@@ -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'
]}
+19 -2
View File
@@ -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
View File
@@ -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')
+6 -3
View File
@@ -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
+18 -14
View File
@@ -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
View File
@@ -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)
+58 -1
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
import time_machine
from freqtrade.configuration import PeriodicCache
from freqtrade.util import PeriodicCache
def test_ttl_cache():
+345 -81
View File
@@ -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
View File
@@ -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)