Merge branch 'develop' into feat/short

This commit is contained in:
Matthias
2021-11-18 20:20:01 +01:00
49 changed files with 1112 additions and 363 deletions

View File

@@ -17,7 +17,7 @@ from telegram import Chat, Message, Update
from freqtrade import constants
from freqtrade.commands import Arguments
from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.edge import Edge, PairInfo
from freqtrade.edge import PairInfo
from freqtrade.enums import Collateral, RunMode, TradingMode
from freqtrade.enums.signaltype import SignalDirection
from freqtrade.exchange import Exchange
@@ -163,11 +163,6 @@ def patch_edge(mocker) -> None:
mocker.patch('freqtrade.edge.Edge.calculate', MagicMock(return_value=True))
def get_patched_edge(mocker, config) -> Edge:
patch_edge(mocker)
edge = Edge(config)
return edge
# Functions for recurrent object patching
@@ -2370,6 +2365,46 @@ def market_buy_order_usdt():
}
@pytest.fixture
def market_buy_order_usdt_doublefee(market_buy_order_usdt):
order = deepcopy(market_buy_order_usdt)
order['fee'] = None
# Market orders filled with 2 trades can have fees in different currencies
# assuming the account runs out of BNB.
order['fees'] = [
{'cost': 0.00025125, 'currency': 'BNB'},
{'cost': 0.05030681, 'currency': 'USDT'},
]
order['trades'] = [{
'timestamp': None,
'datetime': None,
'symbol': 'ETH/USDT',
'id': None,
'order': '123',
'type': 'market',
'side': 'sell',
'takerOrMaker': None,
'price': 2.01,
'amount': 25.0,
'cost': 50.25,
'fee': {'cost': 0.00025125, 'currency': 'BNB'}
}, {
'timestamp': None,
'datetime': None,
'symbol': 'ETH/USDT',
'id': None,
'order': '123',
'type': 'market',
'side': 'sell',
'takerOrMaker': None,
'price': 2.0,
'amount': 5,
'cost': 10,
'fee': {'cost': 0.0100306, 'currency': 'USDT'}
}]
return order
@pytest.fixture
def market_sell_order_usdt():
return {

View File

@@ -360,13 +360,16 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog):
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
pair = 'ETH/BTC'
res = await exchange._async_get_historic_ohlcv(pair, "5m",
1500000000000, is_new_pair=False)
respair, restf, res = await exchange._async_get_historic_ohlcv(
pair, "5m", 1500000000000, is_new_pair=False)
assert respair == pair
assert restf == '5m'
# Call with very old timestamp - causes tons of requests
assert exchange._api_async.fetch_ohlcv.call_count > 400
# assert res == ohlcv
exchange._api_async.fetch_ohlcv.reset_mock()
res = await exchange._async_get_historic_ohlcv(pair, "5m", 1500000000000, is_new_pair=True)
_, _, res = await exchange._async_get_historic_ohlcv(
pair, "5m", 1500000000000, is_new_pair=True)
# Called twice - one "init" call - and one to get the actual data.
assert exchange._api_async.fetch_ohlcv.call_count == 2
@@ -375,7 +378,7 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog):
@pytest.mark.parametrize("trading_mode,collateral,config", [
("", "", {}),
("spot", "", {}),
("margin", "cross", {"options": {"defaultType": "margin"}}),
("futures", "isolated", {"options": {"defaultType": "future"}}),
])

View File

@@ -240,9 +240,9 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog):
(2.9999, 4, 0.005, 2.995),
])
def test_amount_to_precision(default_conf, mocker, amount, precision_mode, precision, expected):
'''
"""
Test rounds down
'''
"""
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': precision}}})
@@ -281,9 +281,7 @@ def test_amount_to_precision(default_conf, mocker, amount, precision_mode, preci
])
def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected):
'''
Test price to precision
'''
"""Test price to precision"""
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}})
exchange = get_patched_exchange(mocker, default_conf, id="binance")
@@ -967,9 +965,22 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog):
default_conf['startup_candle_count'] = 20
ex = Exchange(default_conf)
assert ex
default_conf['startup_candle_count'] = 600
# assumption is that the exchange provides 500 candles per call.s
assert ex.validate_required_startup_candles(200, '5m') == 1
assert ex.validate_required_startup_candles(499, '5m') == 1
assert ex.validate_required_startup_candles(600, '5m') == 2
assert ex.validate_required_startup_candles(501, '5m') == 2
assert ex.validate_required_startup_candles(499, '5m') == 1
assert ex.validate_required_startup_candles(1000, '5m') == 3
assert ex.validate_required_startup_candles(2499, '5m') == 5
assert log_has_re(r'Using 5 calls to get OHLCV. This.*', caplog)
with pytest.raises(OperationalException, match=r'This strategy requires 600.*'):
with pytest.raises(OperationalException, match=r'This strategy requires 2500.*'):
ex.validate_required_startup_candles(2500, '5m')
# Ensure the same also happens on init
default_conf['startup_candle_count'] = 6000
with pytest.raises(OperationalException, match=r'This strategy requires 6000.*'):
Exchange(default_conf)
@@ -1570,6 +1581,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name):
assert exchange._async_get_candle_history.call_count == 2
# Returns twice the above OHLCV data
assert len(ret) == 2
assert log_has_re(r'Downloaded data for .* with length .*\.', caplog)
caplog.clear()
@@ -1651,12 +1663,13 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
pair = 'ETH/USDT'
res = await exchange._async_get_historic_ohlcv(pair, "5m",
1500000000000, is_new_pair=False)
respair, restf, res = await exchange._async_get_historic_ohlcv(
pair, "5m", 1500000000000, is_new_pair=False)
assert respair == pair
assert restf == '5m'
# Call with very old timestamp - causes tons of requests
assert exchange._api_async.fetch_ohlcv.call_count > 200
assert res[0] == ohlcv[0]
assert log_has_re(r'Downloaded data for .* with length .*\.', caplog)
def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
@@ -1694,12 +1707,14 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
assert exchange._api_async.fetch_ohlcv.call_count == 2
exchange._api_async.fetch_ohlcv.reset_mock()
exchange.required_candle_call_count = 2
res = exchange.refresh_latest_ohlcv(pairs)
assert len(res) == len(pairs)
assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog)
assert exchange._klines
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert exchange._api_async.fetch_ohlcv.call_count == 4
exchange._api_async.fetch_ohlcv.reset_mock()
for pair in pairs:
assert isinstance(exchange.klines(pair), DataFrame)
assert len(exchange.klines(pair)) > 0
@@ -1715,7 +1730,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
assert len(res) == len(pairs)
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert exchange._api_async.fetch_ohlcv.call_count == 0
assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, "
f"timeframe {pairs[0][1]} ...",
caplog)
@@ -2066,15 +2081,6 @@ def test_get_sell_rate_exception(default_conf, mocker, caplog):
assert exchange.get_rate(pair, refresh=True, side="sell") == 0.13
def make_fetch_ohlcv_mock(data):
def fetch_ohlcv_mock(pair, timeframe, since):
if since:
assert since > data[-1][0]
return []
return data
return fetch_ohlcv_mock
@pytest.mark.parametrize("exchange_name", EXCHANGES)
@pytest.mark.asyncio
async def test___async_get_candle_history_sort(default_conf, mocker, exchange_name):

View File

@@ -427,6 +427,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
return_value=(Arrow(2017, 12, 10), Arrow(2017, 12, 13)))
patch_exchange(mocker)
mocker.patch.object(Path, 'open')
mocker.patch('freqtrade.configuration.config_validation.validate_config_schema')
mocker.patch('freqtrade.optimize.hyperopt.load', return_value={'XRP/BTC': None})
optimizer_param = {

View File

@@ -1018,7 +1018,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert len(res) == 1
assert res[0]['mix_tag'] == 'Other Other'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit'], 6.2)
assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.buy_tag = "TESTBUY"
trade.sell_reason = "TESTSELL"
@@ -1027,7 +1027,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert len(res) == 1
assert res[0]['mix_tag'] == 'TESTBUY TESTSELL'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit'], 6.2)
assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee):
@@ -1046,10 +1046,10 @@ def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee):
assert len(res) == 2
assert res[0]['mix_tag'] == 'TEST1 sell_signal'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit'], 0.5)
assert prec_satoshi(res[0]['profit_pct'], 0.5)
assert res[1]['mix_tag'] == 'Other roi'
assert res[1]['count'] == 1
assert prec_satoshi(res[1]['profit'], 1.0)
assert prec_satoshi(res[1]['profit_pct'], 1.0)
# Test for a specific pair
res = rpc._rpc_mix_tag_performance('ETC/BTC')
@@ -1057,7 +1057,7 @@ def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee):
assert len(res) == 1
assert res[0]['count'] == 1
assert res[0]['mix_tag'] == 'TEST1 sell_signal'
assert prec_satoshi(res[0]['profit'], 0.5)
assert prec_satoshi(res[0]['profit_pct'], 0.5)
def test_rpc_count(mocker, default_conf, ticker, fee) -> None:

View File

@@ -521,7 +521,7 @@ def test_api_locks(botclient):
assert rc.json()['lock_count'] == 0
def test_api_show_config(botclient, mocker):
def test_api_show_config(botclient):
ftbot, client = botclient
patch_get_signal(ftbot)
@@ -537,6 +537,8 @@ def test_api_show_config(botclient, mocker):
assert not rc.json()['trailing_stop']
assert 'bid_strategy' in rc.json()
assert 'ask_strategy' in rc.json()
assert 'unfilledtimeout' in rc.json()
assert 'version' in rc.json()
def test_api_daily(botclient, mocker, ticker, fee, markets):
@@ -704,7 +706,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
'is_short,expected',
[(
True,
{'best_pair': 'ETC/BTC', 'best_rate': -0.5, 'profit_all_coin': 43.61269123,
{'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_ratio_mean': 0.664109545, 'profit_all_percent_sum': 398.47,
'profit_all_ratio_sum': 3.98465727, 'profit_all_percent': 4.36,
@@ -716,7 +719,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
),
(
False,
{'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'profit_all_coin': -44.0631579,
{'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_ratio_mean': -0.6641100666666667, 'profit_all_percent_sum': -398.47,
'profit_all_ratio_sum': -3.9846604, 'profit_all_percent': -4.41,
@@ -728,7 +732,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
),
(
None,
{'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'profit_all_coin': -14.43790415,
{'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_ratio_mean': 0.000835751666666662, 'profit_all_percent_sum': 0.5,
'profit_all_ratio_sum': 0.005014509999999972, 'profit_all_percent': -1.44,
@@ -763,6 +768,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
assert rc.json() == {
'avg_duration': ANY,
'best_pair': expected['best_pair'],
'best_pair_profit_ratio': expected['best_pair_profit_ratio'],
'best_rate': expected['best_rate'],
'first_trade_date': ANY,
'first_trade_timestamp': ANY,
@@ -1185,7 +1191,7 @@ def test_api_pair_candles(botclient, ohlcv_history):
assert isinstance(rc.json()['columns'], list)
assert rc.json()['columns'] == ['date', 'open', 'high',
'low', 'close', 'volume', 'sma', 'buy', 'sell',
'__date_ts', '_buy_signal_open', '_sell_signal_open']
'__date_ts', '_buy_signal_close', '_sell_signal_close']
assert 'pair' in rc.json()
assert rc.json()['pair'] == 'XRP/BTC'
@@ -1196,7 +1202,8 @@ def test_api_pair_candles(botclient, ohlcv_history):
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
None, 0, 0, 1511686200000, None, None],
['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05,
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 1511686500000, 8.88e-05, None],
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 1511686500000, 8.893e-05,
None],
['2017-11-26 09:00:00', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05,
0.7039405, 8.885e-05, 0, 0, 1511686800000, None, None]

View File

@@ -4,7 +4,7 @@
import logging
import re
from datetime import datetime
from datetime import datetime, timedelta
from functools import reduce
from random import choice, randint
from string import ascii_uppercase
@@ -94,10 +94,11 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
"['delete'], ['performance'], ['buys'], ['sells'], ['mix_tags'], "
"['stats'], ['daily'], ['count'], ['locks'], "
"['unlock', 'delete_locks'], ['reload_config', 'reload_conf'], "
"['show_config', 'show_conf'], ['stopbuy'], "
"['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version']"
"['stats'], ['daily'], ['weekly'], ['monthly'], "
"['count'], ['locks'], ['unlock', 'delete_locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
"['stopbuy'], ['whitelist'], ['blacklist'], "
"['logs'], ['edge'], ['help'], ['version']"
"]")
assert log_has(message_str, caplog)
@@ -188,16 +189,16 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'amount': 90.99181074,
'stake_amount': 90.99181074,
'buy_tag': None,
'close_profit_pct': None,
'close_profit_ratio': None,
'profit': -0.0059,
'profit_pct': -0.59,
'profit_ratio': -0.0059,
'initial_stop_loss_abs': 1.098e-05,
'stop_loss_abs': 1.099e-05,
'sell_order_status': None,
'initial_stop_loss_pct': -0.05,
'initial_stop_loss_ratio': -0.0005,
'stoploss_current_dist': 1e-08,
'stoploss_current_dist_pct': -0.02,
'stop_loss_pct': -0.01,
'stoploss_current_dist_ratio': -0.0002,
'stop_loss_ratio': -0.0001,
'open_order': '(limit buy rem=0.00000000)',
'is_open': True
}]),
@@ -354,7 +355,8 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
context.args = ["2"]
telegram._daily(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Daily' in msg_mock.call_args_list[0][0][0]
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 str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
@@ -366,7 +368,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
context.args = []
telegram._daily(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Daily' in msg_mock.call_args_list[0][0][0]
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(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
@@ -422,7 +424,242 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
context = MagicMock()
context.args = ["today"]
telegram._daily(update=update, context=context)
assert str('Daily Profit over the last 7 days') in msg_mock.call_args_list[0][0][0]
assert str('Daily Profit over the last 7 days</b>:') in msg_mock.call_args_list[0][0][0]
def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee,
limit_sell_order, mocker) -> None:
default_conf['max_open_trades'] = 1
mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
return_value=15000.0
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
get_fee=fee,
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
# Create some test data
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
# Simulate fulfilled LIMIT_SELL order for trade
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
# Try valid data
# /weekly 2
context = MagicMock()
context.args = ["2"]
telegram._weekly(update=update, context=context)
assert msg_mock.call_count == 1
assert "Weekly Profit over the last 2 weeks (starting from Monday)</b>:" \
in msg_mock.call_args_list[0][0][0]
assert 'Monday ' in msg_mock.call_args_list[0][0][0]
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 str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0]
# Reset msg_mock
msg_mock.reset_mock()
context.args = []
telegram._weekly(update=update, context=context)
assert msg_mock.call_count == 1
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 str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0]
# Reset msg_mock
msg_mock.reset_mock()
freqtradebot.config['max_open_trades'] = 2
# Add two other trades
n = freqtradebot.enter_positions()
assert n == 2
trades = Trade.query.all()
for trade in trades:
trade.update(limit_buy_order)
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
# /weekly 1
# By default, the 8 previous weeks are shown
# So the previous modified trade should be excluded from the stats
context = MagicMock()
context.args = ["1"]
telegram._weekly(update=update, context=context)
assert str(' 0.00018651 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 2.798 USD') in msg_mock.call_args_list[0][0][0]
assert str(' 3 trades') in msg_mock.call_args_list[0][0][0]
def test_weekly_wrong_input(default_conf, update, ticker, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
# Try invalid data
msg_mock.reset_mock()
freqtradebot.state = State.RUNNING
# /weekly -3
context = MagicMock()
context.args = ["-3"]
telegram._weekly(update=update, context=context)
assert msg_mock.call_count == 1
assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0]
# Try invalid data
msg_mock.reset_mock()
freqtradebot.state = State.RUNNING
# /weekly this week
context = MagicMock()
context.args = ["this week"]
telegram._weekly(update=update, context=context)
assert str('Weekly Profit over the last 8 weeks (starting from Monday)</b>:') \
in msg_mock.call_args_list[0][0][0]
def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
limit_sell_order, mocker) -> None:
default_conf['max_open_trades'] = 1
mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
return_value=15000.0
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
get_fee=fee,
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
# Create some test data
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
# Simulate fulfilled LIMIT_SELL order for trade
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
# Try valid data
# /monthly 2
context = MagicMock()
context.args = ["2"]
telegram._monthly(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Monthly Profit over the last 2 months</b>:' in msg_mock.call_args_list[0][0][0]
assert 'Month ' in msg_mock.call_args_list[0][0][0]
today = datetime.utcnow().date()
current_month = f"{today.year}-{today.month} "
assert current_month in msg_mock.call_args_list[0][0][0]
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0]
# Reset msg_mock
msg_mock.reset_mock()
context.args = []
telegram._monthly(update=update, context=context)
assert msg_mock.call_count == 1
# Default to 6 months
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 str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0]
# Reset msg_mock
msg_mock.reset_mock()
freqtradebot.config['max_open_trades'] = 2
# Add two other trades
n = freqtradebot.enter_positions()
assert n == 2
trades = Trade.query.all()
for trade in trades:
trade.update(limit_buy_order)
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
# /monthly 12
context = MagicMock()
context.args = ["12"]
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 str(' 0.00018651 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 2.798 USD') in msg_mock.call_args_list[0][0][0]
assert str(' 3 trades') in msg_mock.call_args_list[0][0][0]
# The one-digit months should contain a zero, Eg: September 2021 = "2021-09"
# Since we loaded the last 12 months, any month should appear
assert str('-09') in msg_mock.call_args_list[0][0][0]
def test_monthly_wrong_input(default_conf, update, ticker, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
# Try invalid data
msg_mock.reset_mock()
freqtradebot.state = State.RUNNING
# /monthly -3
context = MagicMock()
context.args = ["-3"]
telegram._monthly(update=update, context=context)
assert msg_mock.call_count == 1
assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0]
# Try invalid data
msg_mock.reset_mock()
freqtradebot.state = State.RUNNING
# /monthly february
context = MagicMock()
context.args = ["february"]
telegram._monthly(update=update, context=context)
assert str('Monthly Profit over the last 6 months</b>:') in msg_mock.call_args_list[0][0][0]
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
@@ -495,7 +732,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee,
telegram._stats(update=update, context=MagicMock())
assert msg_mock.call_count == 1
# assert 'No trades yet.' in msg_mock.call_args_list[0][0][0]
assert 'No trades yet.' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
# Create some test data
@@ -1453,17 +1690,25 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
telegram.send_msg({
'type': RPCMessageType.BUY_FILL,
'buy_tag': 'buy_signal_01',
'trade_id': 1,
'buy_tag': 'buy_signal_01',
'exchange': 'Binance',
'pair': 'ETH/USDT',
'open_rate': 200,
'stake_amount': 100,
'amount': 0.5,
'open_date': arrow.utcnow().datetime
'pair': 'ETH/BTC',
'stake_amount': 0.001,
# 'stake_amount_fiat': 0.0,
'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] == '\N{LARGE CIRCLE} *Binance:* '
'Buy order for ETH/USDT (#1) filled for 200.')
assert msg_mock.call_args[0][0] \
== '\N{CHECK MARK} *Binance:* Bought ETH/BTC (#1)\n' \
'*Buy Tag:* `buy_signal_01`\n' \
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \
'*Total:* `(0.00100000 BTC, 12.345 USD)`'
def test_send_msg_sell_notification(default_conf, mocker) -> None:
@@ -1494,7 +1739,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
})
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
'*Buy Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n'
'*Duration:* `1:00:00 (60.0 min)`\n'
@@ -1526,7 +1771,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
})
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Profit:* `-57.41%`\n'
'*Unrealized Profit:* `-57.41%`\n'
'*Buy Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
@@ -1580,25 +1825,30 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
'type': RPCMessageType.SELL_FILL,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'ETH/USDT',
'pair': 'KEY/ETH',
'gain': 'loss',
'limit': 3.201e-05,
'amount': 0.1,
'amount': 1333.3333333333335,
'order_type': 'market',
'open_rate': 500,
'close_rate': 550,
'current_rate': 3.201e-05,
'open_rate': 7.5e-05,
'close_rate': 3.201e-05,
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-1),
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(),
})
assert msg_mock.call_args[0][0] \
== ('\N{LARGE CIRCLE} *Binance:* Sell order for ETH/USDT (#1) filled for 550.')
== ('\N{WARNING SIGN} *Binance:* Sold KEY/ETH (#1)\n'
'*Profit:* `-57.41%`\n'
'*Buy Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
'*Amount:* `1333.33333333`\n'
'*Close Rate:* `0.00003201`'
)
def test_send_msg_status_notification(default_conf, mocker) -> None:
@@ -1690,7 +1940,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'close_date': arrow.utcnow(),
})
assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Profit:* `-57.41%`\n'
'*Unrealized Profit:* `-57.41%`\n'
'*Buy Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n'
'*Duration:* `2:35:03 (155.1 min)`\n'

View File

@@ -171,12 +171,8 @@ def test_edge_called_in_process(mocker, edge_conf) -> None:
patch_RPCManager(mocker)
patch_edge(mocker)
def _refresh_whitelist(list):
return ['ETH/USDT', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC']
patch_exchange(mocker)
freqtrade = FreqtradeBot(edge_conf)
freqtrade.pairlists._validate_whitelist = _refresh_whitelist
patch_get_signal(freqtrade)
freqtrade.process()
assert freqtrade.active_pair_whitelist == ['NEO/BTC', 'LTC/BTC']
@@ -328,7 +324,7 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke
@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.parametrize('stake_amount,create,amount_enough,max_open_trades', [
(5.0, True, True, 99),
(0.00005, True, False, 99),
(0.049, True, False, 99), # Amount will be adjusted to min - which is 0.051
(0, False, True, 99),
(UNLIMITED_STAKE_AMOUNT, False, True, 0),
])
@@ -678,9 +674,6 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker)
patch_RPCManager(mocker)
patch_exchange(mocker)
def _refresh_whitelist(list):
return ['ETH/USDT', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC']
refresh_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -697,7 +690,6 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker)
mocker.patch('time.sleep', return_value=None)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.pairlists._validate_whitelist = _refresh_whitelist
freqtrade.strategy.informative_pairs = inf_pairs
# patch_get_signal(freqtrade)
@@ -1733,7 +1725,6 @@ def test_exit_positions_exception(
trade = MagicMock()
trade.is_short = is_short
trade.open_order_id = None
trade.open_fee = 0.001
trade.pair = 'ETH/USDT'
trades = [trade]
@@ -1853,8 +1844,6 @@ def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit
trade = MagicMock()
trade.open_order_id = '123'
trade.open_fee = 0.001
trade.is_short = is_short
# Test raise of OperationalException exception
mocker.patch(
@@ -1872,7 +1861,6 @@ def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) ->
trade = MagicMock()
trade.open_order_id = '123'
trade.open_fee = 0.001
# Test raise of OperationalException exception
grm_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", MagicMock())
@@ -2364,12 +2352,13 @@ def test_check_handle_timedout_buy_exception(
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_sell_usercustom(
default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
is_short, open_trade_usdt
is_short, open_trade_usdt, caplog
) -> None:
default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440}
default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440, "exit_timeout_count": 1}
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit')
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
@@ -2412,6 +2401,14 @@ def test_check_handle_timedout_sell_usercustom(
assert open_trade_usdt.is_open is True
assert freqtrade.strategy.check_sell_timeout.call_count == 1
# 2nd canceled trade ...
caplog.clear()
open_trade_usdt.open_order_id = 'order_id_2'
mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1)
freqtrade.check_handle_timedout()
assert log_has_re('Emergencyselling trade.*', caplog)
assert et_mock.call_count == 1
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_sell(
@@ -2837,6 +2834,8 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
)
assert rpc_mock.call_count == 0
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
assert id(freqtrade.strategy.confirm_trade_exit.call_args_list[0][1]['trade']) != id(trade)
assert freqtrade.strategy.confirm_trade_exit.call_args_list[0][1]['trade'].id == trade.id
# Repatch with true
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
@@ -3700,7 +3699,7 @@ def test_trailing_stop_loss_positive(
# stop-loss not reached, adjusted stoploss
assert freqtrade.handle_trade(trade) is False
caplog_text = (f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
f"{'0.0249' if not is_short else '0.0224'}%")
f"{'2.49' if not is_short else '2.24'}%")
if trail_if_reached:
assert not log_has(caplog_text, caplog)
assert not log_has("ETH/USDT - Adjusting stoploss...", caplog)
@@ -3721,7 +3720,7 @@ def test_trailing_stop_loss_positive(
assert freqtrade.handle_trade(trade) is False
assert log_has(
f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
f"{'0.0572' if not is_short else '0.0567'}%",
f"{'5.72' if not is_short else '5.67'}%",
caplog
)
assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
@@ -3997,6 +3996,31 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_
assert freqtrade.get_real_amount(trade, limit_buy_order_usdt) == amount
def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee,
fee, mocker):
tfo_mock = mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', return_value='BNB/USDT')
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 200})
trade = Trade(
pair='LTC/USDT',
amount=30.0,
exchange='binance',
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.245441,
open_order_id="123456"
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
# Amount does not change
assert trade.fee_open == 0.0025
assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee) == 30.0
assert tfo_mock.call_count == 0
# Fetch fees from trades dict if available to get "proper" values
assert round(trade.fee_open, 4) == 0.001
def test_get_real_amount_wrong_amount(default_conf_usdt, trades_for_order, buy_order_fee, fee,
mocker):
limit_buy_order_usdt = deepcopy(buy_order_fee)

View File

@@ -14,8 +14,8 @@ from freqtrade import constants
from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
from tests.conftest import (create_mock_trades, create_mock_trades_with_leverage, get_sides,
log_has, log_has_re)
from tests.conftest import (create_mock_trades, create_mock_trades_usdt,
create_mock_trades_with_leverage, get_sides, log_has, log_has_re)
spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURES
@@ -1980,6 +1980,13 @@ def test_get_best_pair_lev(fee):
assert res[1] == 0.1713156134055116
def test_get_exit_order_count(fee):
create_mock_trades_usdt(fee)
trade = Trade.get_trades([Trade.pair == 'ETC/USDT']).first()
assert trade.get_exit_order_count() == 1
@pytest.mark.usefixtures("init_persistence")
def test_update_order_from_ccxt(caplog):
# Most basic order return (only has orderid)

View File

@@ -1,4 +1,3 @@
from copy import deepcopy
from pathlib import Path
from unittest.mock import MagicMock
@@ -172,7 +171,7 @@ def test_plot_trades(testdatadir, caplog):
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] == '4.0%, roi, 15 min'
assert trade_buy.text[0] == '3.99%, roi, 15 min'
trade_sell = find_trace_in_fig_data(figure.data, 'Sell - Profit')
assert isinstance(trade_sell, go.Scatter)
@@ -180,7 +179,7 @@ def test_plot_trades(testdatadir, caplog):
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] == '4.0%, roi, 15 min'
assert trade_sell.text[0] == '3.99%, roi, 15 min'
trade_sell_loss = find_trace_in_fig_data(figure.data, 'Sell - Loss')
assert isinstance(trade_sell_loss, go.Scatter)
@@ -188,7 +187,7 @@ def test_plot_trades(testdatadir, caplog):
assert len(trades.loc[trades['profit_ratio'] <= 0]) == len(trade_sell_loss.x)
assert trade_sell_loss.marker.color == 'red'
assert trade_sell_loss.marker.symbol == 'square-open'
assert trade_sell_loss.text[5] == '-10.4%, stop_loss, 720 min'
assert trade_sell_loss.text[5] == '-10.45%, stop_loss, 720 min'
def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, testdatadir, caplog):

View File

@@ -185,17 +185,18 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
(100, 11, 500, 100),
(1000, 11, 500, 500), # Above max-stake
(20, 15, 10, 0), # Minimum stake > max-stake
(1, 11, 100, 11), # Below min stake
(9, 11, 100, 11), # Below min stake
(1, 15, 10, 0), # Below min stake and min_stake > max_stake
(20, 50, 100, 0), # Below min stake and stake * 1.3 > min_stake
])
def test__validate_stake_amount(mocker, default_conf,
stake_amount, min_stake_amount, max_stake_amount, expected):
def test_validate_stake_amount(mocker, default_conf,
stake_amount, min_stake_amount, max_stake_amount, expected):
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch("freqtrade.wallets.Wallets.get_available_stake_amount",
return_value=max_stake_amount)
res = freqtrade.wallets._validate_stake_amount('XRP/USDT', stake_amount, min_stake_amount)
res = freqtrade.wallets.validate_stake_amount('XRP/USDT', stake_amount, min_stake_amount)
assert res == expected