Merge branch 'develop' into cyber-forcesell-tg

This commit is contained in:
Ron Klinkien
2022-04-02 20:02:42 +02:00
committed by GitHub
173 changed files with 32904 additions and 4381 deletions

View File

@@ -44,6 +44,8 @@ def test_start_new_config(mocker, caplog, exchange):
'fiat_display_currency': 'EUR',
'timeframe': '15m',
'dry_run': True,
'trading_mode': 'spot',
'margin_mode': '',
'exchange_name': exchange,
'exchange_key': 'sampleKey',
'exchange_secret': 'Samplesecret',

View File

@@ -19,8 +19,8 @@ from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_in
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from tests.conftest import (create_mock_trades, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_args, log_has,
log_has_re, patch_exchange, patched_configuration_load_config_file)
from tests.conftest_trades import MOCK_TRADE_COUNT
@@ -231,9 +231,9 @@ def test_list_markets(mocker, markets_static, capsys):
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 10 active markets: "
"BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, "
"TKN/BTC, XLTCUSDT, XRP/BTC.\n"
assert ("Exchange Bittrex has 12 active markets: "
"ADA/USDT:USDT, BLK/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, "
"LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n"
in captured.out)
patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static)
@@ -246,7 +246,7 @@ def test_list_markets(mocker, markets_static, capsys):
pargs['config'] = None
start_list_markets(pargs, False)
captured = capsys.readouterr()
assert re.match("\nExchange Binance has 10 active markets:\n",
assert re.match("\nExchange Binance has 12 active markets:\n",
captured.out)
patch_exchange(mocker, api_mock=api_mock, id="bittrex", mock_markets=markets_static)
@@ -258,9 +258,9 @@ def test_list_markets(mocker, markets_static, capsys):
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 12 markets: "
"BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, "
"TKN/BTC, XLTCUSDT, XRP/BTC.\n"
assert ("Exchange Bittrex has 14 markets: "
"ADA/USDT:USDT, BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, ETH/USDT:USDT, "
"LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n"
in captured.out)
# Test list-pairs subcommand: active pairs
@@ -297,8 +297,8 @@ def test_list_markets(mocker, markets_static, capsys):
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 6 active markets with ETH, LTC as base currencies: "
"ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n"
assert ("Exchange Bittrex has 7 active markets with ETH, LTC as base currencies: "
"ETH/BTC, ETH/USDT, ETH/USDT:USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n"
in captured.out)
# active markets, base=LTC
@@ -323,8 +323,8 @@ def test_list_markets(mocker, markets_static, capsys):
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 3 active markets with USDT, USD as quote currencies: "
"ETH/USDT, LTC/USD, XLTCUSDT.\n"
assert ("Exchange Bittrex has 5 active markets with USDT, USD as quote currencies: "
"ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, LTC/USD, XLTCUSDT.\n"
in captured.out)
# active markets, quote=USDT
@@ -336,8 +336,8 @@ def test_list_markets(mocker, markets_static, capsys):
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 2 active markets with USDT as quote currency: "
"ETH/USDT, XLTCUSDT.\n"
assert ("Exchange Bittrex has 4 active markets with USDT as quote currency: "
"ADA/USDT:USDT, ETH/USDT, ETH/USDT:USDT, XLTCUSDT.\n"
in captured.out)
# active markets, base=LTC, quote=USDT
@@ -399,7 +399,7 @@ def test_list_markets(mocker, markets_static, capsys):
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Exchange Bittrex has 10 active markets:\n"
assert ("Exchange Bittrex has 12 active markets:\n"
in captured.out)
# Test tabular output, no markets found
@@ -422,8 +422,8 @@ def test_list_markets(mocker, markets_static, capsys):
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ('["BLK/BTC","ETH/BTC","ETH/USDT","LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC",'
'"TKN/BTC","XLTCUSDT","XRP/BTC"]'
assert ('["ADA/USDT:USDT","BLK/BTC","ETH/BTC","ETH/USDT","ETH/USDT:USDT",'
'"LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC","TKN/BTC","XLTCUSDT","XRP/BTC"]'
in captured.out)
# Test --print-csv
@@ -434,9 +434,9 @@ def test_list_markets(mocker, markets_static, capsys):
]
start_list_markets(get_args(args), False)
captured = capsys.readouterr()
assert ("Id,Symbol,Base,Quote,Active,Is pair" in captured.out)
assert ("blkbtc,BLK/BTC,BLK,BTC,True,True" in captured.out)
assert ("USD-LTC,LTC/USD,LTC,USD,True,True" in captured.out)
assert ("Id,Symbol,Base,Quote,Active,Spot,Margin,Future,Leverage" in captured.out)
assert ("blkbtc,BLK/BTC,BLK,BTC,True,Spot" in captured.out)
assert ("USD-LTC,LTC/USD,LTC,USD,True,Spot" in captured.out)
# Test --one-column
args = [
@@ -810,10 +810,24 @@ def test_download_data_trades(mocker, caplog):
"--days", "20",
"--dl-trades"
]
start_download_data(get_args(args))
pargs = get_args(args)
pargs['config'] = None
start_download_data(pargs)
assert dl_mock.call_args[1]['timerange'].starttype == "date"
assert dl_mock.call_count == 1
assert convert_mock.call_count == 1
args = [
"download-data",
"--exchange", "kraken",
"--pairs", "ETH/BTC", "XRP/BTC",
"--days", "20",
"--trading-mode", "futures",
"--dl-trades"
]
with pytest.raises(OperationalException,
match="Trade download not supported for futures."):
start_download_data(get_args(args))
def test_start_convert_trades(mocker, caplog):
@@ -846,7 +860,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
captured = capsys.readouterr()
assert "TestStrategyLegacyV1" in captured.out
assert "legacy_strategy_v1.py" not in captured.out
assert "StrategyTestV2" in captured.out
assert CURRENT_TEST_STRATEGY in captured.out
# Test regular output
args = [
@@ -861,7 +875,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
captured = capsys.readouterr()
assert "TestStrategyLegacyV1" in captured.out
assert "legacy_strategy_v1.py" in captured.out
assert "StrategyTestV2" in captured.out
assert CURRENT_TEST_STRATEGY in captured.out
# Test color output
args = [
@@ -875,7 +889,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
captured = capsys.readouterr()
assert "TestStrategyLegacyV1" in captured.out
assert "legacy_strategy_v1.py" in captured.out
assert "StrategyTestV2" in captured.out
assert CURRENT_TEST_STRATEGY in captured.out
assert "LOAD FAILED" in captured.out
@@ -943,7 +957,7 @@ def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmpdir):
mocker.patch(
'freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist',
return_value=True
)
)
def fake_iterator(*args, **kwargs):
yield from [saved_hyperopt_results]
@@ -1327,8 +1341,8 @@ def test_start_list_data(testdatadir, capsys):
start_list_data(pargs)
captured = capsys.readouterr()
assert "Found 17 pair / timeframe combinations." in captured.out
assert "\n| Pair | Timeframe |\n" in captured.out
assert "\n| UNITTEST/BTC | 1m, 5m, 8m, 30m |\n" in captured.out
assert "\n| Pair | Timeframe | Type |\n" in captured.out
assert "\n| UNITTEST/BTC | 1m, 5m, 8m, 30m | spot |\n" in captured.out
args = [
"list-data",
@@ -1343,15 +1357,33 @@ def test_start_list_data(testdatadir, capsys):
start_list_data(pargs)
captured = capsys.readouterr()
assert "Found 2 pair / timeframe combinations." in captured.out
assert "\n| Pair | Timeframe |\n" in captured.out
assert "\n| Pair | Timeframe | Type |\n" in captured.out
assert "UNITTEST/BTC" not in captured.out
assert "\n| XRP/ETH | 1m, 5m |\n" in captured.out
assert "\n| XRP/ETH | 1m, 5m | spot |\n" in captured.out
args = [
"list-data",
"--data-format-ohlcv",
"json",
"--trading-mode", "futures",
"--datadir",
str(testdatadir),
]
pargs = get_args(args)
pargs['config'] = None
start_list_data(pargs)
captured = capsys.readouterr()
assert "Found 5 pair / timeframe combinations." in captured.out
assert "\n| Pair | Timeframe | Type |\n" in captured.out
assert "\n| XRP/USDT | 1h | futures |\n" in captured.out
assert "\n| XRP/USDT | 1h, 8h | mark |\n" in captured.out
@pytest.mark.usefixtures("init_persistence")
def test_show_trades(mocker, fee, capsys, caplog):
mocker.patch("freqtrade.persistence.init_db")
create_mock_trades(fee)
create_mock_trades(fee, False)
args = [
"show-trades",
"--db-url",

File diff suppressed because it is too large Load Diff

View File

@@ -6,12 +6,24 @@ from freqtrade.persistence.models import Order, Trade
MOCK_TRADE_COUNT = 6
def mock_order_1():
def enter_side(is_short: bool):
return "sell" if is_short else "buy"
def exit_side(is_short: bool):
return "buy" if is_short else "sell"
def direc(is_short: bool):
return "short" if is_short else "long"
def mock_order_1(is_short: bool):
return {
'id': '1234',
'id': f'1234_{direc(is_short)}',
'symbol': 'ETH/BTC',
'status': 'closed',
'side': 'buy',
'side': enter_side(is_short),
'type': 'limit',
'price': 0.123,
'average': 0.123,
@@ -21,7 +33,7 @@ def mock_order_1():
}
def mock_trade_1(fee):
def mock_trade_1(fee, is_short: bool):
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
@@ -33,21 +45,22 @@ def mock_trade_1(fee):
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17),
open_rate=0.123,
exchange='binance',
open_order_id='dry_run_buy_12345',
strategy='StrategyTestV2',
open_order_id=f'dry_run_buy_{direc(is_short)}_12345',
strategy='StrategyTestV3',
timeframe=5,
is_short=is_short
)
o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy')
o = Order.parse_from_ccxt_object(mock_order_1(is_short), 'ETH/BTC', enter_side(is_short))
trade.orders.append(o)
return trade
def mock_order_2():
def mock_order_2(is_short: bool):
return {
'id': '1235',
'id': f'1235_{direc(is_short)}',
'symbol': 'ETC/BTC',
'status': 'closed',
'side': 'buy',
'side': enter_side(is_short),
'type': 'limit',
'price': 0.123,
'amount': 123.0,
@@ -56,12 +69,12 @@ def mock_order_2():
}
def mock_order_2_sell():
def mock_order_2_sell(is_short: bool):
return {
'id': '12366',
'id': f'12366_{direc(is_short)}',
'symbol': 'ETC/BTC',
'status': 'closed',
'side': 'sell',
'side': exit_side(is_short),
'type': 'limit',
'price': 0.128,
'amount': 123.0,
@@ -70,7 +83,7 @@ def mock_order_2_sell():
}
def mock_trade_2(fee):
def mock_trade_2(fee, is_short: bool):
"""
Closed trade...
"""
@@ -83,31 +96,32 @@ def mock_trade_2(fee):
fee_close=fee.return_value,
open_rate=0.123,
close_rate=0.128,
close_profit=0.005,
close_profit_abs=0.000584127,
close_profit=-0.005 if is_short else 0.005,
close_profit_abs=-0.005584127 if is_short else 0.000584127,
exchange='binance',
is_open=False,
open_order_id='dry_run_sell_12345',
strategy='StrategyTestV2',
open_order_id=f'dry_run_sell_{direc(is_short)}_12345',
strategy='StrategyTestV3',
timeframe=5,
buy_tag='TEST1',
enter_tag='TEST1',
sell_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
is_short=is_short
)
o = Order.parse_from_ccxt_object(mock_order_2(), 'ETC/BTC', 'buy')
o = Order.parse_from_ccxt_object(mock_order_2(is_short), 'ETC/BTC', enter_side(is_short))
trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_2_sell(), 'ETC/BTC', 'sell')
o = Order.parse_from_ccxt_object(mock_order_2_sell(is_short), 'ETC/BTC', exit_side(is_short))
trade.orders.append(o)
return trade
def mock_order_3():
def mock_order_3(is_short: bool):
return {
'id': '41231a12a',
'id': f'41231a12a_{direc(is_short)}',
'symbol': 'XRP/BTC',
'status': 'closed',
'side': 'buy',
'side': enter_side(is_short),
'type': 'limit',
'price': 0.05,
'amount': 123.0,
@@ -116,12 +130,12 @@ def mock_order_3():
}
def mock_order_3_sell():
def mock_order_3_sell(is_short: bool):
return {
'id': '41231a666a',
'id': f'41231a666a_{direc(is_short)}',
'symbol': 'XRP/BTC',
'status': 'closed',
'side': 'sell',
'side': exit_side(is_short),
'type': 'stop_loss_limit',
'price': 0.06,
'average': 0.06,
@@ -131,7 +145,7 @@ def mock_order_3_sell():
}
def mock_trade_3(fee):
def mock_trade_3(fee, is_short: bool):
"""
Closed trade
"""
@@ -144,29 +158,30 @@ def mock_trade_3(fee):
fee_close=fee.return_value,
open_rate=0.05,
close_rate=0.06,
close_profit=0.01,
close_profit_abs=0.000155,
close_profit=-0.01 if is_short else 0.01,
close_profit_abs=-0.001155 if is_short else 0.000155,
exchange='binance',
is_open=False,
strategy='StrategyTestV2',
strategy='StrategyTestV3',
timeframe=5,
sell_reason='roi',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc),
is_short=is_short
)
o = Order.parse_from_ccxt_object(mock_order_3(), 'XRP/BTC', 'buy')
o = Order.parse_from_ccxt_object(mock_order_3(is_short), 'XRP/BTC', enter_side(is_short))
trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_3_sell(), 'XRP/BTC', 'sell')
o = Order.parse_from_ccxt_object(mock_order_3_sell(is_short), 'XRP/BTC', exit_side(is_short))
trade.orders.append(o)
return trade
def mock_order_4():
def mock_order_4(is_short: bool):
return {
'id': 'prod_buy_12345',
'id': f'prod_buy_{direc(is_short)}_12345',
'symbol': 'ETC/BTC',
'status': 'open',
'side': 'buy',
'side': enter_side(is_short),
'type': 'limit',
'price': 0.123,
'amount': 123.0,
@@ -175,7 +190,7 @@ def mock_order_4():
}
def mock_trade_4(fee):
def mock_trade_4(fee, is_short: bool):
"""
Simulate prod entry
"""
@@ -190,21 +205,22 @@ def mock_trade_4(fee):
is_open=True,
open_rate=0.123,
exchange='binance',
open_order_id='prod_buy_12345',
strategy='StrategyTestV2',
open_order_id=f'prod_buy_{direc(is_short)}_12345',
strategy='StrategyTestV3',
timeframe=5,
is_short=is_short
)
o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy')
o = Order.parse_from_ccxt_object(mock_order_4(is_short), 'ETC/BTC', enter_side(is_short))
trade.orders.append(o)
return trade
def mock_order_5():
def mock_order_5(is_short: bool):
return {
'id': 'prod_buy_3455',
'id': f'prod_buy_{direc(is_short)}_3455',
'symbol': 'XRP/BTC',
'status': 'closed',
'side': 'buy',
'side': enter_side(is_short),
'type': 'limit',
'price': 0.123,
'amount': 123.0,
@@ -213,12 +229,12 @@ def mock_order_5():
}
def mock_order_5_stoploss():
def mock_order_5_stoploss(is_short: bool):
return {
'id': 'prod_stoploss_3455',
'id': f'prod_stoploss_{direc(is_short)}_3455',
'symbol': 'XRP/BTC',
'status': 'open',
'side': 'sell',
'side': exit_side(is_short),
'type': 'stop_loss_limit',
'price': 0.123,
'amount': 123.0,
@@ -227,7 +243,7 @@ def mock_order_5_stoploss():
}
def mock_trade_5(fee):
def mock_trade_5(fee, is_short: bool):
"""
Simulate prod entry with stoploss
"""
@@ -243,23 +259,24 @@ def mock_trade_5(fee):
open_rate=0.123,
exchange='binance',
strategy='SampleStrategy',
buy_tag='TEST1',
stoploss_order_id='prod_stoploss_3455',
enter_tag='TEST1',
stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455',
timeframe=5,
is_short=is_short
)
o = Order.parse_from_ccxt_object(mock_order_5(), 'XRP/BTC', 'buy')
o = Order.parse_from_ccxt_object(mock_order_5(is_short), 'XRP/BTC', enter_side(is_short))
trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_5_stoploss(), 'XRP/BTC', 'stoploss')
o = Order.parse_from_ccxt_object(mock_order_5_stoploss(is_short), 'XRP/BTC', 'stoploss')
trade.orders.append(o)
return trade
def mock_order_6():
def mock_order_6(is_short: bool):
return {
'id': 'prod_buy_6',
'id': f'prod_buy_{direc(is_short)}_6',
'symbol': 'LTC/BTC',
'status': 'closed',
'side': 'buy',
'side': enter_side(is_short),
'type': 'limit',
'price': 0.15,
'amount': 2.0,
@@ -268,23 +285,23 @@ def mock_order_6():
}
def mock_order_6_sell():
def mock_order_6_sell(is_short: bool):
return {
'id': 'prod_sell_6',
'id': f'prod_sell_{direc(is_short)}_6',
'symbol': 'LTC/BTC',
'status': 'open',
'side': 'sell',
'side': exit_side(is_short),
'type': 'limit',
'price': 0.20,
'price': 0.15 if is_short else 0.20,
'amount': 2.0,
'filled': 0.0,
'remaining': 2.0,
}
def mock_trade_6(fee):
def mock_trade_6(fee, is_short: bool):
"""
Simulate prod entry with open sell order
Simulate prod entry with open exit order
"""
trade = Trade(
pair='LTC/BTC',
@@ -298,12 +315,188 @@ def mock_trade_6(fee):
open_rate=0.15,
exchange='binance',
strategy='SampleStrategy',
buy_tag='TEST2',
open_order_id="prod_sell_6",
enter_tag='TEST2',
open_order_id=f"prod_sell_{direc(is_short)}_6",
timeframe=5,
is_short=is_short
)
o = Order.parse_from_ccxt_object(mock_order_6(), 'LTC/BTC', 'buy')
o = Order.parse_from_ccxt_object(mock_order_6(is_short), 'LTC/BTC', enter_side(is_short))
trade.orders.append(o)
o = Order.parse_from_ccxt_object(mock_order_6_sell(), 'LTC/BTC', 'sell')
o = Order.parse_from_ccxt_object(mock_order_6_sell(is_short), 'LTC/BTC', exit_side(is_short))
trade.orders.append(o)
return trade
def short_order():
return {
'id': '1236',
'symbol': 'ETC/BTC',
'status': 'closed',
'side': 'sell',
'type': 'limit',
'price': 0.123,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
}
def exit_short_order():
return {
'id': '12367',
'symbol': 'ETC/BTC',
'status': 'closed',
'side': 'buy',
'type': 'limit',
'price': 0.128,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
}
def short_trade(fee):
"""
10 minute short limit trade on binance
Short trade
fee: 0.25% base
interest_rate: 0.05% per day
open_rate: 0.123 base
close_rate: 0.128 base
amount: 123.0 crypto
stake_amount: 15.129 base
borrowed: 123.0 crypto
time-periods: 10 minutes(rounds up to 1/24 time-period of 1 day)
interest: borrowed * interest_rate * time-periods
= 123.0 * 0.0005 * 1/24 = 0.0025625 crypto
open_value: (amount * open_rate) - (amount * open_rate * fee)
= (123 * 0.123) - (123 * 0.123 * 0.0025)
= 15.091177499999999
amount_closed: amount + interest = 123 + 0.0025625 = 123.0025625
close_value: (amount_closed * close_rate) + (amount_closed * close_rate * fee)
= (123.0025625 * 0.128) + (123.0025625 * 0.128 * 0.0025)
= 15.78368882
total_profit = open_value - close_value
= 15.091177499999999 - 15.78368882
= -0.6925113200000013
total_profit_percentage = total_profit / stake_amount
= -0.6925113200000013 / 15.129
= -0.04577376693766946
"""
trade = Trade(
pair='ETC/BTC',
stake_amount=15.129,
amount=123.0,
amount_requested=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
# close_rate=0.128,
# close_profit=-0.04577376693766946,
# close_profit_abs=-0.6925113200000013,
exchange='binance',
is_open=True,
open_order_id='dry_run_exit_short_12345',
strategy='DefaultStrategy',
timeframe=5,
sell_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
# close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
is_short=True
)
o = Order.parse_from_ccxt_object(short_order(), 'ETC/BTC', 'sell')
trade.orders.append(o)
o = Order.parse_from_ccxt_object(exit_short_order(), 'ETC/BTC', 'sell')
trade.orders.append(o)
return trade
def leverage_order():
return {
'id': '1237',
'symbol': 'DOGE/BTC',
'status': 'closed',
'side': 'buy',
'type': 'limit',
'price': 0.123,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
'leverage': 5.0
}
def leverage_order_sell():
return {
'id': '12368',
'symbol': 'DOGE/BTC',
'status': 'closed',
'side': 'sell',
'type': 'limit',
'price': 0.128,
'amount': 123.0,
'filled': 123.0,
'remaining': 0.0,
'leverage': 5.0
}
def leverage_trade(fee):
"""
5 hour short limit trade on kraken
Short trade
fee: 0.25% base
interest_rate: 0.05% per day
open_rate: 0.123 base
close_rate: 0.128 base
amount: 615 crypto
stake_amount: 15.129 base
borrowed: 60.516 base
leverage: 5
hours: 5
interest: borrowed * interest_rate * ceil(1 + hours/4)
= 60.516 * 0.0005 * ceil(1 + 5/4) = 0.090774 base
open_value: (amount * open_rate) + (amount * open_rate * fee)
= (615.0 * 0.123) + (615.0 * 0.123 * 0.0025)
= 75.83411249999999
close_value: (amount_closed * close_rate) - (amount_closed * close_rate * fee) - interest
= (615.0 * 0.128) - (615.0 * 0.128 * 0.0025) - 0.090774
= 78.432426
total_profit = close_value - open_value
= 78.432426 - 75.83411249999999
= 2.5983135000000175
total_profit_percentage = ((close_value/open_value)-1) * leverage
= ((78.432426/75.83411249999999)-1) * 5
= 0.1713156134055116
"""
trade = Trade(
pair='DOGE/BTC',
stake_amount=15.129,
amount=615.0,
leverage=5.0,
amount_requested=615.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
close_rate=0.128,
close_profit=0.1713156134055116,
close_profit_abs=2.5983135000000175,
exchange='kraken',
is_open=False,
open_order_id='dry_run_leverage_buy_12368',
strategy='DefaultStrategy',
timeframe=5,
sell_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300),
close_date=datetime.now(tz=timezone.utc),
interest_rate=0.0005
)
o = Order.parse_from_ccxt_object(leverage_order(), 'DOGE/BTC', 'sell')
trade.orders.append(o)
o = Order.parse_from_ccxt_object(leverage_order_sell(), 'DOGE/BTC', 'sell')
trade.orders.append(o)
return trade

View File

@@ -17,7 +17,7 @@ from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelis
load_trades_from_db)
from freqtrade.data.history import load_data, load_pair_history
from freqtrade.exceptions import OperationalException
from tests.conftest import create_mock_trades
from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades
from tests.conftest_trades import MOCK_TRADE_COUNT
@@ -108,7 +108,8 @@ def test_load_backtest_data_multi(testdatadir):
for strategy in ('StrategyTestV2', 'TestStrategy'):
bt_data = load_backtest_data(filename, strategy=strategy)
assert isinstance(bt_data, DataFrame)
assert set(bt_data.columns) == set(BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp'])
assert set(bt_data.columns) == set(
BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp'])
assert len(bt_data) == 179
# Test loading from string (must yield same result)
@@ -123,9 +124,10 @@ def test_load_backtest_data_multi(testdatadir):
@pytest.mark.usefixtures("init_persistence")
def test_load_trades_from_db(default_conf, fee, mocker):
@pytest.mark.parametrize('is_short', [False, True])
def test_load_trades_from_db(default_conf, fee, is_short, mocker):
create_mock_trades(fee)
create_mock_trades(fee, is_short)
# remove init so it does not init again
init_mock = mocker.patch('freqtrade.data.btanalysis.init_db', MagicMock())
@@ -140,7 +142,7 @@ def test_load_trades_from_db(default_conf, fee, mocker):
for col in BT_DATA_COLUMNS:
if col not in ['index', 'open_at_end']:
assert col in trades.columns
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='StrategyTestV2')
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy=CURRENT_TEST_STRATEGY)
assert len(trades) == 4
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy')
assert len(trades) == 0
@@ -198,7 +200,7 @@ def test_load_trades(default_conf, mocker):
db_url=default_conf.get('db_url'),
exportfilename=default_conf.get('exportfilename'),
no_trades=False,
strategy="StrategyTestV2",
strategy=CURRENT_TEST_STRATEGY,
)
assert db_mock.call_count == 1

View File

@@ -12,6 +12,8 @@ from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_forma
trades_to_ohlcv, trim_dataframe)
from freqtrade.data.history import (get_timerange, load_data, load_pair_history,
validate_backtest_data)
from freqtrade.data.history.idatahandler import IDataHandler
from freqtrade.enums import CandleType
from tests.conftest import log_has, log_has_re
from tests.data.test_history import _clean_test_file
@@ -75,7 +77,8 @@ def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
def test_ohlcv_fill_up_missing_data2(caplog):
timeframe = '5m'
ticks = [[
ticks = [
[
1511686200000, # 8:50:00
8.794e-05, # open
8.948e-05, # high
@@ -106,7 +109,7 @@ def test_ohlcv_fill_up_missing_data2(caplog):
8.895e-05,
8.817e-05,
123551
]
]
]
# Generate test-data without filling missing
@@ -133,7 +136,8 @@ def test_ohlcv_fill_up_missing_data2(caplog):
def test_ohlcv_drop_incomplete(caplog):
timeframe = '1d'
ticks = [[
ticks = [
[
1559750400000, # 2019-06-04
8.794e-05, # open
8.948e-05, # high
@@ -164,7 +168,7 @@ def test_ohlcv_drop_incomplete(caplog):
8.895e-05,
8.817e-05,
123551
]
]
]
caplog.set_level(logging.DEBUG)
data = ohlcv_to_dataframe(ticks, timeframe, pair="UNITTEST/BTC",
@@ -287,42 +291,56 @@ def test_convert_trades_format(default_conf, testdatadir, tmpdir):
file['new'].unlink()
def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir):
@pytest.mark.parametrize('file_base,candletype', [
(['XRP_ETH-5m', 'XRP_ETH-1m'], CandleType.SPOT),
(['UNITTEST_USDT-1h-mark', 'XRP_USDT-1h-mark'], CandleType.MARK),
(['XRP_USDT-1h-futures'], CandleType.FUTURES),
])
def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir, file_base, candletype):
tmpdir1 = Path(tmpdir)
prependix = '' if candletype == CandleType.SPOT else 'futures/'
files_orig = []
files_temp = []
files_new = []
for file in file_base:
file_orig = testdatadir / f"{prependix}{file}.json"
file_temp = tmpdir1 / f"{prependix}{file}.json"
file_new = tmpdir1 / f"{prependix}{file}.json.gz"
IDataHandler.create_dir_if_needed(file_temp)
copyfile(file_orig, file_temp)
file1_orig = testdatadir / "XRP_ETH-5m.json"
file1 = tmpdir1 / "XRP_ETH-5m.json"
file1_new = tmpdir1 / "XRP_ETH-5m.json.gz"
file2_orig = testdatadir / "XRP_ETH-1m.json"
file2 = tmpdir1 / "XRP_ETH-1m.json"
file2_new = tmpdir1 / "XRP_ETH-1m.json.gz"
copyfile(file1_orig, file1)
copyfile(file2_orig, file2)
files_orig.append(file_orig)
files_temp.append(file_temp)
files_new.append(file_new)
default_conf['datadir'] = tmpdir1
default_conf['pairs'] = ['XRP_ETH']
default_conf['timeframes'] = ['1m', '5m']
default_conf['pairs'] = ['XRP_ETH', 'XRP_USDT', 'UNITTEST_USDT']
default_conf['timeframes'] = ['1m', '5m', '1h']
assert not file1_new.exists()
assert not file2_new.exists()
assert not file_new.exists()
convert_ohlcv_format(default_conf, convert_from='json',
convert_to='jsongz', erase=False)
assert file1_new.exists()
assert file2_new.exists()
assert file1.exists()
assert file2.exists()
convert_ohlcv_format(
default_conf,
convert_from='json',
convert_to='jsongz',
erase=False,
candle_type=candletype
)
for file in (files_temp + files_new):
assert file.exists()
# Remove original files
file1.unlink()
file2.unlink()
for file in (files_temp):
file.unlink()
# Convert back
convert_ohlcv_format(default_conf, convert_from='jsongz',
convert_to='json', erase=True)
assert file1.exists()
assert file2.exists()
assert not file1_new.exists()
assert not file2_new.exists()
convert_ohlcv_format(
default_conf,
convert_from='jsongz',
convert_to='json',
erase=True,
candle_type=candletype
)
for file in (files_temp):
assert file.exists()
for file in (files_new):
assert not file.exists()

View File

@@ -5,40 +5,49 @@ import pytest
from pandas import DataFrame
from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import RunMode
from freqtrade.enums import CandleType, RunMode
from freqtrade.exceptions import ExchangeError, OperationalException
from freqtrade.plugins.pairlistmanager import PairListManager
from tests.conftest import get_patched_exchange
def test_ohlcv(mocker, default_conf, ohlcv_history):
@pytest.mark.parametrize('candle_type', [
'mark',
'',
])
def test_dp_ohlcv(mocker, default_conf, ohlcv_history, candle_type):
default_conf["runmode"] = RunMode.DRY_RUN
timeframe = default_conf["timeframe"]
exchange = get_patched_exchange(mocker, default_conf)
exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history
exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history
candletype = CandleType.from_string(candle_type)
exchange._klines[("XRP/BTC", timeframe, candletype)] = ohlcv_history
exchange._klines[("UNITTEST/BTC", timeframe, candletype)] = ohlcv_history
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.DRY_RUN
assert ohlcv_history.equals(dp.ohlcv("UNITTEST/BTC", timeframe))
assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe), DataFrame)
assert dp.ohlcv("UNITTEST/BTC", timeframe) is not ohlcv_history
assert dp.ohlcv("UNITTEST/BTC", timeframe, copy=False) is ohlcv_history
assert not dp.ohlcv("UNITTEST/BTC", timeframe).empty
assert dp.ohlcv("NONESENSE/AAA", timeframe).empty
assert ohlcv_history.equals(dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candletype))
assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candletype), DataFrame)
assert dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candletype) is not ohlcv_history
assert dp.ohlcv("UNITTEST/BTC", timeframe, copy=False, candle_type=candletype) is ohlcv_history
assert not dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candletype).empty
assert dp.ohlcv("NONESENSE/AAA", timeframe, candle_type=candletype).empty
# Test with and without parameter
assert dp.ohlcv("UNITTEST/BTC", timeframe).equals(dp.ohlcv("UNITTEST/BTC"))
assert dp.ohlcv(
"UNITTEST/BTC",
timeframe,
candle_type=candletype
).equals(dp.ohlcv("UNITTEST/BTC", candle_type=candle_type))
default_conf["runmode"] = RunMode.LIVE
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.LIVE
assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe), DataFrame)
assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candle_type), DataFrame)
default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.BACKTEST
assert dp.ohlcv("UNITTEST/BTC", timeframe).empty
assert dp.ohlcv("UNITTEST/BTC", timeframe, candle_type=candle_type).empty
def test_historic_ohlcv(mocker, default_conf, ohlcv_history):
@@ -77,37 +86,50 @@ def test_historic_ohlcv_dataformat(mocker, default_conf, ohlcv_history):
jsonloadmock.assert_not_called()
def test_get_pair_dataframe(mocker, default_conf, ohlcv_history):
@pytest.mark.parametrize('candle_type', [
'mark',
'futures',
'',
])
def test_get_pair_dataframe(mocker, default_conf, ohlcv_history, candle_type):
default_conf["runmode"] = RunMode.DRY_RUN
timeframe = default_conf["timeframe"]
exchange = get_patched_exchange(mocker, default_conf)
exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history
exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history
candletype = CandleType.from_string(candle_type)
exchange._klines[("XRP/BTC", timeframe, candletype)] = ohlcv_history
exchange._klines[("UNITTEST/BTC", timeframe, candletype)] = ohlcv_history
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.DRY_RUN
assert ohlcv_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", timeframe))
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame)
assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe) is not ohlcv_history
assert not dp.get_pair_dataframe("UNITTEST/BTC", timeframe).empty
assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty
assert ohlcv_history.equals(dp.get_pair_dataframe(
"UNITTEST/BTC", timeframe, candle_type=candle_type))
assert ohlcv_history.equals(dp.get_pair_dataframe(
"UNITTEST/BTC", timeframe, candle_type=candletype))
assert isinstance(dp.get_pair_dataframe(
"UNITTEST/BTC", timeframe, candle_type=candle_type), DataFrame)
assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe,
candle_type=candle_type) is not ohlcv_history
assert not dp.get_pair_dataframe("UNITTEST/BTC", timeframe, candle_type=candle_type).empty
assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe, candle_type=candle_type).empty
# Test with and without parameter
assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe)\
.equals(dp.get_pair_dataframe("UNITTEST/BTC"))
assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe, candle_type=candle_type)\
.equals(dp.get_pair_dataframe("UNITTEST/BTC", candle_type=candle_type))
default_conf["runmode"] = RunMode.LIVE
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.LIVE
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame)
assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty
assert isinstance(dp.get_pair_dataframe(
"UNITTEST/BTC", timeframe, candle_type=candle_type), DataFrame)
assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe, candle_type=candle_type).empty
historymock = MagicMock(return_value=ohlcv_history)
mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock)
default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.BACKTEST
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame)
assert isinstance(dp.get_pair_dataframe(
"UNITTEST/BTC", timeframe, candle_type=candle_type), DataFrame)
# assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty
@@ -230,8 +252,8 @@ def test_get_analyzed_dataframe(mocker, default_conf, ohlcv_history):
exchange = get_patched_exchange(mocker, default_conf)
dp = DataProvider(default_conf, exchange)
dp._set_cached_df("XRP/BTC", timeframe, ohlcv_history)
dp._set_cached_df("UNITTEST/BTC", timeframe, ohlcv_history)
dp._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT)
dp._set_cached_df("UNITTEST/BTC", timeframe, ohlcv_history, CandleType.SPOT)
assert dp.runmode == RunMode.DRY_RUN
dataframe, time = dp.get_analyzed_dataframe("UNITTEST/BTC", timeframe)
@@ -276,7 +298,7 @@ def test_no_exchange_mode(default_conf):
dp.refresh([()])
with pytest.raises(OperationalException, match=message):
dp.ohlcv('XRP/USDT', '5m')
dp.ohlcv('XRP/USDT', '5m', '')
with pytest.raises(OperationalException, match=message):
dp.market('XRP/USDT')

View File

@@ -1,6 +1,7 @@
# pragma pylint: disable=missing-docstring, protected-access, C0103
import json
import re
import uuid
from pathlib import Path
from shutil import copyfile
@@ -23,10 +24,12 @@ from freqtrade.data.history.history_utils import (_download_pair_history, _downl
validate_backtest_data)
from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler, get_datahandlerclass
from freqtrade.data.history.jsondatahandler import JsonDataHandler, JsonGzDataHandler
from freqtrade.enums import CandleType, TradingMode
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import file_dump_json
from freqtrade.resolvers import StrategyResolver
from tests.conftest import get_patched_exchange, log_has, log_has_re, patch_exchange
from tests.conftest import (CURRENT_TEST_STRATEGY, get_patched_exchange, log_has, log_has_re,
patch_exchange)
# Change this if modifying UNITTEST/BTC testdatafile
@@ -78,7 +81,7 @@ def test_load_data_7min_timeframe(mocker, caplog, default_conf, testdatadir) ->
assert isinstance(ld, DataFrame)
assert ld.empty
assert log_has(
'No history data for pair: "UNITTEST/BTC", timeframe: 7m. '
'No history for UNITTEST/BTC, spot, 7m found. '
'Use `freqtrade download-data` to download the data', caplog
)
@@ -94,6 +97,17 @@ def test_load_data_1min_timeframe(ohlcv_history, mocker, caplog, testdatadir) ->
)
def test_load_data_mark(ohlcv_history, mocker, caplog, testdatadir) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history)
file = testdatadir / 'futures/UNITTEST_USDT-1h-mark.json'
load_data(datadir=testdatadir, timeframe='1h', pairs=['UNITTEST/BTC'], candle_type='mark')
assert file.is_file()
assert not log_has(
'Download history data for pair: "UNITTEST/USDT", interval: 1m '
'and store in None.', caplog
)
def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) -> None:
ltfmock = mocker.patch(
'freqtrade.data.history.jsondatahandler.JsonDataHandler._ohlcv_load',
@@ -109,8 +123,9 @@ def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) ->
assert ltfmock.call_args_list[0][1]['timerange'].startts == timerange.startts - 20 * 60
@pytest.mark.parametrize('candle_type', ['mark', ''])
def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog,
default_conf, tmpdir) -> None:
default_conf, tmpdir, candle_type) -> None:
"""
Test load_pair_history() with 1 min timeframe
"""
@@ -120,21 +135,22 @@ def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog,
file = tmpdir1 / 'MEME_BTC-1m.json'
# do not download a new pair if refresh_pairs isn't set
load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC')
load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC', candle_type=candle_type)
assert not file.is_file()
assert log_has(
'No history data for pair: "MEME/BTC", timeframe: 1m. '
'Use `freqtrade download-data` to download the data', caplog
f"No history for MEME/BTC, {candle_type}, 1m found. "
"Use `freqtrade download-data` to download the data", caplog
)
# download a new pair if refresh_pairs is set
refresh_data(datadir=tmpdir1, timeframe='1m', pairs=['MEME/BTC'],
exchange=exchange)
load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC')
exchange=exchange, candle_type=CandleType.SPOT
)
load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC', candle_type=candle_type)
assert file.is_file()
assert log_has_re(
r'Download history data for pair: "MEME/BTC" \(0/1\), timeframe: 1m '
r'and store in .*', caplog
r'Download history data for pair: "MEME/BTC" \(0/1\), timeframe: 1m, '
r'candle type: spot and store in .*', caplog
)
@@ -142,19 +158,31 @@ def test_testdata_path(testdatadir) -> None:
assert str(Path('tests') / 'testdata') in str(testdatadir)
@pytest.mark.parametrize("pair,expected_result", [
("ETH/BTC", 'freqtrade/hello/world/ETH_BTC-5m.json'),
("Fabric Token/ETH", 'freqtrade/hello/world/Fabric_Token_ETH-5m.json'),
("ETHH20", 'freqtrade/hello/world/ETHH20-5m.json'),
(".XBTBON2H", 'freqtrade/hello/world/_XBTBON2H-5m.json'),
("ETHUSD.d", 'freqtrade/hello/world/ETHUSD_d-5m.json'),
("ACC_OLD/BTC", 'freqtrade/hello/world/ACC_OLD_BTC-5m.json'),
@pytest.mark.parametrize("pair,expected_result,candle_type", [
("ETH/BTC", 'freqtrade/hello/world/ETH_BTC-5m.json', ""),
("Fabric Token/ETH", 'freqtrade/hello/world/Fabric_Token_ETH-5m.json', ""),
("ETHH20", 'freqtrade/hello/world/ETHH20-5m.json', ""),
(".XBTBON2H", 'freqtrade/hello/world/_XBTBON2H-5m.json', ""),
("ETHUSD.d", 'freqtrade/hello/world/ETHUSD_d-5m.json', ""),
("ACC_OLD/BTC", 'freqtrade/hello/world/ACC_OLD_BTC-5m.json', ""),
("ETH/BTC", 'freqtrade/hello/world/futures/ETH_BTC-5m-mark.json', "mark"),
("ACC_OLD/BTC", 'freqtrade/hello/world/futures/ACC_OLD_BTC-5m-index.json', "index"),
])
def test_json_pair_data_filename(pair, expected_result):
fn = JsonDataHandler._pair_data_filename(Path('freqtrade/hello/world'), pair, '5m')
def test_json_pair_data_filename(pair, expected_result, candle_type):
fn = JsonDataHandler._pair_data_filename(
Path('freqtrade/hello/world'),
pair,
'5m',
CandleType.from_string(candle_type)
)
assert isinstance(fn, Path)
assert fn == Path(expected_result)
fn = JsonGzDataHandler._pair_data_filename(Path('freqtrade/hello/world'), pair, '5m')
fn = JsonGzDataHandler._pair_data_filename(
Path('freqtrade/hello/world'),
pair,
'5m',
candle_type=CandleType.from_string(candle_type)
)
assert isinstance(fn, Path)
assert fn == Path(expected_result + '.gz')
@@ -195,14 +223,16 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None:
# timeframe starts earlier than the cached data
# should fully update data
timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0)
data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler)
data, start_ts = _load_cached_data_for_updating(
'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT)
assert data.empty
assert start_ts == test_data[0][0] - 1000
# timeframe starts in the center of the cached data
# should return the cached data w/o the last item
timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0)
data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler)
data, start_ts = _load_cached_data_for_updating(
'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT)
assert_frame_equal(data, test_data_df.iloc[:-1])
assert test_data[-2][0] <= start_ts < test_data[-1][0]
@@ -210,42 +240,59 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None:
# timeframe starts after the cached data
# should return the cached data w/o the last item
timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 100, 0)
data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler)
data, start_ts = _load_cached_data_for_updating(
'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT)
assert_frame_equal(data, test_data_df.iloc[:-1])
assert test_data[-2][0] <= start_ts < test_data[-1][0]
# no datafile exist
# should return timestamp start time
timerange = TimeRange('date', None, now_ts - 10000, 0)
data, start_ts = _load_cached_data_for_updating('NONEXIST/BTC', '1m', timerange, data_handler)
data, start_ts = _load_cached_data_for_updating(
'NONEXIST/BTC', '1m', timerange, data_handler, CandleType.SPOT)
assert data.empty
assert start_ts == (now_ts - 10000) * 1000
# no datafile exist, no timeframe is set
# should return an empty array and None
data, start_ts = _load_cached_data_for_updating('NONEXIST/BTC', '1m', None, data_handler)
data, start_ts = _load_cached_data_for_updating(
'NONEXIST/BTC', '1m', None, data_handler, CandleType.SPOT)
assert data.empty
assert start_ts is None
def test_download_pair_history(ohlcv_history_list, mocker, default_conf, tmpdir) -> None:
@pytest.mark.parametrize('candle_type,subdir,file_tail', [
('mark', 'futures/', '-mark'),
('spot', '', ''),
])
def test_download_pair_history(
ohlcv_history_list,
mocker,
default_conf,
tmpdir,
candle_type,
subdir,
file_tail
) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list)
exchange = get_patched_exchange(mocker, default_conf)
tmpdir1 = Path(tmpdir)
file1_1 = tmpdir1 / 'MEME_BTC-1m.json'
file1_5 = tmpdir1 / 'MEME_BTC-5m.json'
file2_1 = tmpdir1 / 'CFI_BTC-1m.json'
file2_5 = tmpdir1 / 'CFI_BTC-5m.json'
file1_1 = tmpdir1 / f'{subdir}MEME_BTC-1m{file_tail}.json'
file1_5 = tmpdir1 / f'{subdir}MEME_BTC-5m{file_tail}.json'
file2_1 = tmpdir1 / f'{subdir}CFI_BTC-1m{file_tail}.json'
file2_5 = tmpdir1 / f'{subdir}CFI_BTC-5m{file_tail}.json'
assert not file1_1.is_file()
assert not file2_1.is_file()
assert _download_pair_history(datadir=tmpdir1, exchange=exchange,
pair='MEME/BTC',
timeframe='1m')
timeframe='1m',
candle_type=candle_type)
assert _download_pair_history(datadir=tmpdir1, exchange=exchange,
pair='CFI/BTC',
timeframe='1m')
timeframe='1m',
candle_type=candle_type)
assert not exchange._pairs_last_refresh_time
assert file1_1.is_file()
assert file2_1.is_file()
@@ -259,10 +306,12 @@ def test_download_pair_history(ohlcv_history_list, mocker, default_conf, tmpdir)
assert _download_pair_history(datadir=tmpdir1, exchange=exchange,
pair='MEME/BTC',
timeframe='5m')
timeframe='5m',
candle_type=candle_type)
assert _download_pair_history(datadir=tmpdir1, exchange=exchange,
pair='CFI/BTC',
timeframe='5m')
timeframe='5m',
candle_type=candle_type)
assert not exchange._pairs_last_refresh_time
assert file1_5.is_file()
assert file2_5.is_file()
@@ -279,10 +328,12 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick)
exchange = get_patched_exchange(mocker, default_conf)
_download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/BTC",
timeframe='1m')
timeframe='1m', candle_type='spot')
_download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/BTC",
timeframe='3m')
assert json_dump_mock.call_count == 2
timeframe='3m', candle_type='spot')
_download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/USDT",
timeframe='1h', candle_type='mark')
assert json_dump_mock.call_count == 3
def test_download_backtesting_data_exception(mocker, caplog, default_conf, tmpdir) -> None:
@@ -293,7 +344,7 @@ def test_download_backtesting_data_exception(mocker, caplog, default_conf, tmpdi
assert not _download_pair_history(datadir=tmpdir1, exchange=exchange,
pair='MEME/BTC',
timeframe='1m')
timeframe='1m', candle_type='spot')
assert log_has('Failed to download history data for pair: "MEME/BTC", timeframe: 1m.', caplog)
@@ -310,8 +361,8 @@ def test_load_partial_missing(testdatadir, caplog) -> None:
td = ((end - start).total_seconds() // 60 // 5) + 1
assert td != len(data['UNITTEST/BTC'])
start_real = data['UNITTEST/BTC'].iloc[0, 0]
assert log_has(f'Missing data at start for pair '
f'UNITTEST/BTC at 5m, data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}',
assert log_has(f'UNITTEST/BTC, spot, 5m, '
f'data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}',
caplog)
# Make sure we start fresh - test missing data at end
caplog.clear()
@@ -325,8 +376,8 @@ def test_load_partial_missing(testdatadir, caplog) -> None:
# Shift endtime with +5 - as last candle is dropped (partial candle)
end_real = arrow.get(data['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5)
assert log_has(f'Missing data at end for pair '
f'UNITTEST/BTC at 5m, data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}',
assert log_has(f'UNITTEST/BTC, spot, 5m, '
f'data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}',
caplog)
@@ -344,7 +395,8 @@ def test_init_with_refresh(default_conf, mocker) -> None:
datadir=Path(''),
pairs=[],
timeframe=default_conf['timeframe'],
exchange=exchange
exchange=exchange,
candle_type=CandleType.SPOT
)
assert {} == load_data(
datadir=Path(''),
@@ -380,7 +432,7 @@ def test_file_dump_json_tofile(testdatadir) -> None:
def test_get_timerange(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker)
default_conf.update({'strategy': 'StrategyTestV2'})
default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
strategy = StrategyResolver.load_strategy(default_conf)
data = strategy.advise_all_indicators(
@@ -398,7 +450,7 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None:
def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None:
patch_exchange(mocker)
default_conf.update({'strategy': 'StrategyTestV2'})
default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
strategy = StrategyResolver.load_strategy(default_conf)
data = strategy.advise_all_indicators(
@@ -422,7 +474,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir)
def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None:
patch_exchange(mocker)
default_conf.update({'strategy': 'StrategyTestV2'})
default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
strategy = StrategyResolver.load_strategy(default_conf)
timerange = TimeRange('index', 'index', 200, 250)
@@ -442,7 +494,13 @@ def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> No
assert len(caplog.record_tuples) == 0
def test_refresh_backtest_ohlcv_data(mocker, default_conf, markets, caplog, testdatadir):
@pytest.mark.parametrize('trademode,callcount', [
('spot', 4),
('margin', 4),
('futures', 8), # Called 8 times - 4 normal, 2 funding and 2 mark/index calls
])
def test_refresh_backtest_ohlcv_data(
mocker, default_conf, markets, caplog, testdatadir, trademode, callcount):
dl_mock = mocker.patch('freqtrade.data.history.history_utils._download_pair_history',
MagicMock())
mocker.patch(
@@ -455,10 +513,11 @@ def test_refresh_backtest_ohlcv_data(mocker, default_conf, markets, caplog, test
timerange = TimeRange.parse_timerange("20190101-20190102")
refresh_backtest_ohlcv_data(exchange=ex, pairs=["ETH/BTC", "XRP/BTC"],
timeframes=["1m", "5m"], datadir=testdatadir,
timerange=timerange, erase=True
timerange=timerange, erase=True,
trading_mode=trademode
)
assert dl_mock.call_count == 4
assert dl_mock.call_count == callcount
assert dl_mock.call_args[1]['timerange'].starttype == 'date'
assert log_has("Downloading pair ETH/BTC, interval 1m.", caplog)
@@ -476,7 +535,8 @@ def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir):
unav_pairs = refresh_backtest_ohlcv_data(exchange=ex, pairs=["BTT/BTC", "LTC/USDT"],
timeframes=["1m", "5m"],
datadir=testdatadir,
timerange=timerange, erase=False
timerange=timerange, erase=False,
trading_mode='spot'
)
assert dl_mock.call_count == 0
@@ -604,33 +664,101 @@ def test_convert_trades_to_ohlcv(testdatadir, tmpdir, caplog):
def test_datahandler_ohlcv_get_pairs(testdatadir):
pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '5m')
pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '5m', candle_type=CandleType.SPOT)
# Convert to set to avoid failures due to sorting
assert set(pairs) == {'UNITTEST/BTC', 'XLM/BTC', 'ETH/BTC', 'TRX/BTC', 'LTC/BTC',
'XMR/BTC', 'ZEC/BTC', 'ADA/BTC', 'ETC/BTC', 'NXT/BTC',
'DASH/BTC', 'XRP/ETH'}
pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, '8m')
pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, '8m', candle_type=CandleType.SPOT)
assert set(pairs) == {'UNITTEST/BTC'}
pairs = HDF5DataHandler.ohlcv_get_pairs(testdatadir, '5m')
pairs = HDF5DataHandler.ohlcv_get_pairs(testdatadir, '5m', candle_type=CandleType.SPOT)
assert set(pairs) == {'UNITTEST/BTC'}
pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.MARK)
assert set(pairs) == {'UNITTEST/USDT', 'XRP/USDT'}
pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.FUTURES)
assert set(pairs) == {'XRP/USDT'}
pairs = HDF5DataHandler.ohlcv_get_pairs(testdatadir, '1h', candle_type=CandleType.MARK)
assert set(pairs) == {'UNITTEST/USDT:USDT'}
@pytest.mark.parametrize('filename,pair,timeframe,candletype', [
('XMR_BTC-5m.json', 'XMR_BTC', '5m', ''),
('XMR_USDT-1h.h5', 'XMR_USDT', '1h', ''),
('BTC-PERP-1h.h5', 'BTC-PERP', '1h', ''),
('BTC_USDT-2h.jsongz', 'BTC_USDT', '2h', ''),
('BTC_USDT-2h-mark.jsongz', 'BTC_USDT', '2h', 'mark'),
('XMR_USDT-1h-mark.h5', 'XMR_USDT', '1h', 'mark'),
('XMR_USDT-1h-random.h5', 'XMR_USDT', '1h', 'random'),
('BTC-PERP-1h-index.h5', 'BTC-PERP', '1h', 'index'),
('XMR_USDT_USDT-1h-mark.h5', 'XMR_USDT_USDT', '1h', 'mark'),
])
def test_datahandler_ohlcv_regex(filename, pair, timeframe, candletype):
regex = JsonDataHandler._OHLCV_REGEX
match = re.search(regex, filename)
assert len(match.groups()) > 1
assert match[1] == pair
assert match[2] == timeframe
assert match[3] == candletype
@pytest.mark.parametrize('input,expected', [
('XMR_USDT', 'XMR/USDT'),
('BTC_USDT', 'BTC/USDT'),
('USDT_BUSD', 'USDT/BUSD'),
('BTC_USDT_USDT', 'BTC/USDT:USDT'), # Futures
('XRP_USDT_USDT', 'XRP/USDT:USDT'), # futures
('BTC-PERP', 'BTC-PERP'),
('BTC-PERP_USDT', 'BTC-PERP:USDT'), # potential FTX case
('UNITTEST_USDT', 'UNITTEST/USDT'),
])
def test_rebuild_pair_from_filename(input, expected):
assert IDataHandler.rebuild_pair_from_filename(input) == expected
def test_datahandler_ohlcv_get_available_data(testdatadir):
paircombs = JsonDataHandler.ohlcv_get_available_data(testdatadir)
paircombs = JsonDataHandler.ohlcv_get_available_data(testdatadir, TradingMode.SPOT)
# Convert to set to avoid failures due to sorting
assert set(paircombs) == {('UNITTEST/BTC', '5m'), ('ETH/BTC', '5m'), ('XLM/BTC', '5m'),
('TRX/BTC', '5m'), ('LTC/BTC', '5m'), ('XMR/BTC', '5m'),
('ZEC/BTC', '5m'), ('UNITTEST/BTC', '1m'), ('ADA/BTC', '5m'),
('ETC/BTC', '5m'), ('NXT/BTC', '5m'), ('DASH/BTC', '5m'),
('XRP/ETH', '1m'), ('XRP/ETH', '5m'), ('UNITTEST/BTC', '30m'),
('UNITTEST/BTC', '8m'), ('NOPAIR/XXX', '4m')}
assert set(paircombs) == {
('UNITTEST/BTC', '5m', CandleType.SPOT),
('ETH/BTC', '5m', CandleType.SPOT),
('XLM/BTC', '5m', CandleType.SPOT),
('TRX/BTC', '5m', CandleType.SPOT),
('LTC/BTC', '5m', CandleType.SPOT),
('XMR/BTC', '5m', CandleType.SPOT),
('ZEC/BTC', '5m', CandleType.SPOT),
('UNITTEST/BTC', '1m', CandleType.SPOT),
('ADA/BTC', '5m', CandleType.SPOT),
('ETC/BTC', '5m', CandleType.SPOT),
('NXT/BTC', '5m', CandleType.SPOT),
('DASH/BTC', '5m', CandleType.SPOT),
('XRP/ETH', '1m', CandleType.SPOT),
('XRP/ETH', '5m', CandleType.SPOT),
('UNITTEST/BTC', '30m', CandleType.SPOT),
('UNITTEST/BTC', '8m', CandleType.SPOT),
('NOPAIR/XXX', '4m', CandleType.SPOT),
}
paircombs = JsonGzDataHandler.ohlcv_get_available_data(testdatadir)
assert set(paircombs) == {('UNITTEST/BTC', '8m')}
paircombs = HDF5DataHandler.ohlcv_get_available_data(testdatadir)
assert set(paircombs) == {('UNITTEST/BTC', '5m')}
paircombs = JsonDataHandler.ohlcv_get_available_data(testdatadir, TradingMode.FUTURES)
# Convert to set to avoid failures due to sorting
assert set(paircombs) == {
('UNITTEST/USDT', '1h', 'mark'),
('XRP/USDT', '1h', 'futures'),
('XRP/USDT', '1h', 'mark'),
('XRP/USDT', '8h', 'mark'),
('XRP/USDT', '8h', 'funding_rate'),
}
paircombs = JsonGzDataHandler.ohlcv_get_available_data(testdatadir, TradingMode.SPOT)
assert set(paircombs) == {('UNITTEST/BTC', '8m', CandleType.SPOT)}
paircombs = HDF5DataHandler.ohlcv_get_available_data(testdatadir, TradingMode.SPOT)
assert set(paircombs) == {('UNITTEST/BTC', '5m', CandleType.SPOT)}
def test_jsondatahandler_trades_get_pairs(testdatadir):
@@ -643,21 +771,29 @@ def test_jsondatahandler_ohlcv_purge(mocker, testdatadir):
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
dh = JsonGzDataHandler(testdatadir)
assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', '')
assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', candle_type='mark')
assert unlinkmock.call_count == 0
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
assert unlinkmock.call_count == 1
assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', '')
assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', candle_type='mark')
assert unlinkmock.call_count == 2
def test_jsondatahandler_ohlcv_load(testdatadir, caplog):
dh = JsonDataHandler(testdatadir)
df = dh.ohlcv_load('XRP/ETH', '5m')
df = dh.ohlcv_load('XRP/ETH', '5m', 'spot')
assert len(df) == 711
df_mark = dh.ohlcv_load('UNITTEST/USDT', '1h', candle_type="mark")
assert len(df_mark) == 99
df_no_mark = dh.ohlcv_load('UNITTEST/USDT', '1h', 'spot')
assert len(df_no_mark) == 0
# Failure case (empty array)
df1 = dh.ohlcv_load('NOPAIR/XXX', '4m')
df1 = dh.ohlcv_load('NOPAIR/XXX', '4m', 'spot')
assert len(df1) == 0
assert log_has("Could not load data for NOPAIR/XXX.", caplog)
assert df.columns.equals(df1.columns)
@@ -690,7 +826,9 @@ def test_jsondatahandler_trades_purge(mocker, testdatadir):
def test_datahandler_ohlcv_append(datahandler, testdatadir, ):
dh = get_datahandler(testdatadir, datahandler)
with pytest.raises(NotImplementedError):
dh.ohlcv_append('UNITTEST/ETH', '5m', DataFrame())
dh.ohlcv_append('UNITTEST/ETH', '5m', DataFrame(), CandleType.SPOT)
with pytest.raises(NotImplementedError):
dh.ohlcv_append('UNITTEST/ETH', '5m', DataFrame(), CandleType.MARK)
@pytest.mark.parametrize('datahandler', AVAILABLE_DATAHANDLERS)
@@ -772,35 +910,52 @@ def test_hdf5datahandler_trades_purge(mocker, testdatadir):
assert unlinkmock.call_count == 1
def test_hdf5datahandler_ohlcv_load_and_resave(testdatadir, tmpdir):
@pytest.mark.parametrize('pair,timeframe,candle_type,candle_append,startdt,enddt', [
# Data goes from 2018-01-10 - 2018-01-30
('UNITTEST/BTC', '5m', 'spot', '', '2018-01-15', '2018-01-19'),
# Mark data goes from to 2021-11-15 2021-11-19
('UNITTEST/USDT:USDT', '1h', 'mark', '-mark', '2021-11-16', '2021-11-18'),
])
def test_hdf5datahandler_ohlcv_load_and_resave(
testdatadir,
tmpdir,
pair,
timeframe,
candle_type,
candle_append,
startdt, enddt
):
tmpdir1 = Path(tmpdir)
tmpdir2 = tmpdir1
if candle_type not in ('', 'spot'):
tmpdir2 = tmpdir1 / 'futures'
tmpdir2.mkdir()
dh = HDF5DataHandler(testdatadir)
ohlcv = dh.ohlcv_load('UNITTEST/BTC', '5m')
ohlcv = dh._ohlcv_load(pair, timeframe, None, candle_type=candle_type)
assert isinstance(ohlcv, DataFrame)
assert len(ohlcv) > 0
file = tmpdir1 / 'UNITTEST_NEW-5m.h5'
file = tmpdir2 / f"UNITTEST_NEW-{timeframe}{candle_append}.h5"
assert not file.is_file()
dh1 = HDF5DataHandler(tmpdir1)
dh1.ohlcv_store('UNITTEST/NEW', '5m', ohlcv)
dh1.ohlcv_store('UNITTEST/NEW', timeframe, ohlcv, candle_type=candle_type)
assert file.is_file()
assert not ohlcv[ohlcv['date'] < '2018-01-15'].empty
assert not ohlcv[ohlcv['date'] < startdt].empty
# Data gores from 2018-01-10 - 2018-01-30
timerange = TimeRange.parse_timerange('20180115-20180119')
timerange = TimeRange.parse_timerange(f"{startdt.replace('-', '')}-{enddt.replace('-', '')}")
# Call private function to ensure timerange is filtered in hdf5
ohlcv = dh._ohlcv_load('UNITTEST/BTC', '5m', timerange)
ohlcv1 = dh1._ohlcv_load('UNITTEST/NEW', '5m', timerange)
ohlcv = dh._ohlcv_load(pair, timeframe, timerange, candle_type=candle_type)
ohlcv1 = dh1._ohlcv_load('UNITTEST/NEW', timeframe, timerange, candle_type=candle_type)
assert len(ohlcv) == len(ohlcv1)
assert ohlcv.equals(ohlcv1)
assert ohlcv[ohlcv['date'] < '2018-01-15'].empty
assert ohlcv[ohlcv['date'] > '2018-01-19'].empty
assert ohlcv[ohlcv['date'] < startdt].empty
assert ohlcv[ohlcv['date'] > enddt].empty
# Try loading inexisting file
ohlcv = dh.ohlcv_load('UNITTEST/NONEXIST', '5m')
ohlcv = dh.ohlcv_load('UNITTEST/NONEXIST', timeframe, candle_type=candle_type)
assert ohlcv.empty
@@ -808,12 +963,14 @@ def test_hdf5datahandler_ohlcv_purge(mocker, testdatadir):
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
dh = HDF5DataHandler(testdatadir)
assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', '')
assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', candle_type='mark')
assert unlinkmock.call_count == 0
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
assert unlinkmock.call_count == 1
assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', '')
assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m', candle_type='mark')
assert unlinkmock.call_count == 2
def test_gethandlerclass():

View File

@@ -12,7 +12,7 @@ from pandas import DataFrame, to_datetime
from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.edge import Edge, PairInfo
from freqtrade.enums import SellType
from freqtrade.enums import ExitType
from freqtrade.exceptions import OperationalException
from tests.conftest import get_patched_freqtradebot, log_has
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
@@ -95,8 +95,8 @@ tc1 = BTContainer(data=[
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
],
stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2),
BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)]
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=2),
BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=4, close_tick=6)]
)
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
@@ -107,7 +107,7 @@ tc2 = BTContainer(data=[
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.01, roi={"0": float('inf')}, profit_perc=-0.01,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)]
)
# 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss
@@ -118,7 +118,7 @@ tc3 = BTContainer(data=[
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)]
)
# 5) Stoploss and sell are hit. should sell on stoploss
@@ -129,7 +129,7 @@ tc4 = BTContainer(data=[
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)]
)
TESTS = [

View File

@@ -1,24 +1,29 @@
from datetime import datetime, timezone
from random import randint
from unittest.mock import MagicMock
from unittest.mock import MagicMock, PropertyMock
import ccxt
import pytest
from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
from tests.conftest import get_mock_coro, get_patched_exchange, log_has_re
from tests.exchange.test_exchange import ccxt_exceptionhandlers
@pytest.mark.parametrize('limitratio,expected', [
(None, 220 * 0.99),
(0.99, 220 * 0.99),
(0.98, 220 * 0.98),
@pytest.mark.parametrize('trademode', [TradingMode.FUTURES, TradingMode.SPOT])
@pytest.mark.parametrize('limitratio,expected,side', [
(None, 220 * 0.99, "sell"),
(0.99, 220 * 0.99, "sell"),
(0.98, 220 * 0.98, "sell"),
(None, 220 * 1.01, "buy"),
(0.99, 220 * 1.01, "buy"),
(0.98, 220 * 1.02, "buy"),
])
def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'stop_loss_limit'
order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'stop'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
@@ -27,45 +32,78 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
}
})
default_conf['dry_run'] = False
default_conf['margin_mode'] = MarginMode.ISOLATED
default_conf['trading_mode'] = trademode
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=190,
side=side,
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
leverage=1.0
)
api_mock.create_order.reset_mock()
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types)
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types=order_types,
side=side,
leverage=1.0
)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == order_type
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
# Price should be 1% below stopprice
assert api_mock.create_order.call_args_list[0][1]['price'] == expected
assert api_mock.create_order.call_args_list[0][1]['params'] == {'stopPrice': 220}
if trademode == TradingMode.SPOT:
params_dict = {'stopPrice': 220}
else:
params_dict = {'stopPrice': 220, 'reduceOnly': True}
assert api_mock.create_order.call_args_list[0][1]['params'] == params_dict
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0)
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_binance(default_conf, mocker):
@@ -78,12 +116,25 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=190,
side="sell",
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
leverage=1.0
)
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side="sell",
leverage=1.0
)
assert 'id' in order
assert 'info' in order
@@ -94,22 +145,383 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
assert order['amount'] == 1
def test_stoploss_adjust_binance(mocker, default_conf):
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
(1501, 1499, 1501, "sell"),
(1499, 1501, 1499, "buy")
])
def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='binance')
order = {
'type': 'stop_loss_limit',
'price': 1500,
'info': {'stopPrice': 1500},
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
assert exchange.stoploss_adjust(sl1, order, side=side)
assert not exchange.stoploss_adjust(sl2, order, side=side)
# Test with invalid order case
order['type'] = 'stop_loss'
assert not exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(sl3, order, side=side)
def test_fill_leverage_tiers_binance(default_conf, mocker):
api_mock = MagicMock()
api_mock.fetch_leverage_tiers = MagicMock(return_value={
'ADA/BUSD': [
{
"tier": 1,
"notionalFloor": 0,
"notionalCap": 100000,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20,
"info": {
"bracket": "1",
"initialLeverage": "20",
"notionalCap": "100000",
"notionalFloor": "0",
"maintMarginRatio": "0.025",
"cum": "0.0"
}
},
{
"tier": 2,
"notionalFloor": 100000,
"notionalCap": 500000,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10,
"info": {
"bracket": "2",
"initialLeverage": "10",
"notionalCap": "500000",
"notionalFloor": "100000",
"maintMarginRatio": "0.05",
"cum": "2500.0"
}
},
{
"tier": 3,
"notionalFloor": 500000,
"notionalCap": 1000000,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5,
"info": {
"bracket": "3",
"initialLeverage": "5",
"notionalCap": "1000000",
"notionalFloor": "500000",
"maintMarginRatio": "0.1",
"cum": "27500.0"
}
},
{
"tier": 4,
"notionalFloor": 1000000,
"notionalCap": 2000000,
"maintenanceMarginRate": 0.15,
"maxLeverage": 3,
"info": {
"bracket": "4",
"initialLeverage": "3",
"notionalCap": "2000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.15",
"cum": "77500.0"
}
},
{
"tier": 5,
"notionalFloor": 2000000,
"notionalCap": 5000000,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2,
"info": {
"bracket": "5",
"initialLeverage": "2",
"notionalCap": "5000000",
"notionalFloor": "2000000",
"maintMarginRatio": "0.25",
"cum": "277500.0"
}
},
{
"tier": 6,
"notionalFloor": 5000000,
"notionalCap": 30000000,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1,
"info": {
"bracket": "6",
"initialLeverage": "1",
"notionalCap": "30000000",
"notionalFloor": "5000000",
"maintMarginRatio": "0.5",
"cum": "1527500.0"
}
}
],
"ZEC/USDT": [
{
"tier": 1,
"notionalFloor": 0,
"notionalCap": 50000,
"maintenanceMarginRate": 0.01,
"maxLeverage": 50,
"info": {
"bracket": "1",
"initialLeverage": "50",
"notionalCap": "50000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2,
"notionalFloor": 50000,
"notionalCap": 150000,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "150000",
"notionalFloor": "50000",
"maintMarginRatio": "0.025",
"cum": "750.0"
}
},
{
"tier": 3,
"notionalFloor": 150000,
"notionalCap": 250000,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "250000",
"notionalFloor": "150000",
"maintMarginRatio": "0.05",
"cum": "4500.0"
}
},
{
"tier": 4,
"notionalFloor": 250000,
"notionalCap": 500000,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "500000",
"notionalFloor": "250000",
"maintMarginRatio": "0.1",
"cum": "17000.0"
}
},
{
"tier": 5,
"notionalFloor": 500000,
"notionalCap": 1000000,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4,
"info": {
"bracket": "5",
"initialLeverage": "4",
"notionalCap": "1000000",
"notionalFloor": "500000",
"maintMarginRatio": "0.125",
"cum": "29500.0"
}
},
{
"tier": 6,
"notionalFloor": 1000000,
"notionalCap": 2000000,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2,
"info": {
"bracket": "6",
"initialLeverage": "2",
"notionalCap": "2000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.25",
"cum": "154500.0"
}
},
{
"tier": 7,
"notionalFloor": 2000000,
"notionalCap": 30000000,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1,
"info": {
"bracket": "7",
"initialLeverage": "1",
"notionalCap": "30000000",
"notionalFloor": "2000000",
"maintMarginRatio": "0.5",
"cum": "654500.0"
}
}
],
})
default_conf['dry_run'] = False
default_conf['trading_mode'] = TradingMode.FUTURES
default_conf['margin_mode'] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
exchange.fill_leverage_tiers()
assert exchange._leverage_tiers == {
'ADA/BUSD': [
{
"min": 0,
"max": 100000,
"mmr": 0.025,
"lev": 20,
"maintAmt": 0.0
},
{
"min": 100000,
"max": 500000,
"mmr": 0.05,
"lev": 10,
"maintAmt": 2500.0
},
{
"min": 500000,
"max": 1000000,
"mmr": 0.1,
"lev": 5,
"maintAmt": 27500.0
},
{
"min": 1000000,
"max": 2000000,
"mmr": 0.15,
"lev": 3,
"maintAmt": 77500.0
},
{
"min": 2000000,
"max": 5000000,
"mmr": 0.25,
"lev": 2,
"maintAmt": 277500.0
},
{
"min": 5000000,
"max": 30000000,
"mmr": 0.5,
"lev": 1,
"maintAmt": 1527500.0
}
],
"ZEC/USDT": [
{
'min': 0,
'max': 50000,
'mmr': 0.01,
'lev': 50,
'maintAmt': 0.0
},
{
'min': 50000,
'max': 150000,
'mmr': 0.025,
'lev': 20,
'maintAmt': 750.0
},
{
'min': 150000,
'max': 250000,
'mmr': 0.05,
'lev': 10,
'maintAmt': 4500.0
},
{
'min': 250000,
'max': 500000,
'mmr': 0.1,
'lev': 5,
'maintAmt': 17000.0
},
{
'min': 500000,
'max': 1000000,
'mmr': 0.125,
'lev': 4,
'maintAmt': 29500.0
},
{
'min': 1000000,
'max': 2000000,
'mmr': 0.25,
'lev': 2,
'maintAmt': 154500.0
},
{
'min': 2000000,
'max': 30000000,
'mmr': 0.5,
'lev': 1,
'maintAmt': 654500.0
},
]
}
api_mock = MagicMock()
api_mock.load_leverage_tiers = MagicMock()
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True})
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"binance",
"fill_leverage_tiers",
"fetch_leverage_tiers",
)
def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers):
api_mock = MagicMock()
default_conf['trading_mode'] = TradingMode.FUTURES
default_conf['margin_mode'] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
exchange.fill_leverage_tiers()
leverage_tiers = leverage_tiers
for key, value in leverage_tiers.items():
assert exchange._leverage_tiers[key] == value
def test__set_leverage_binance(mocker, default_conf):
api_mock = MagicMock()
api_mock.set_leverage = MagicMock()
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
default_conf['dry_run'] = False
exchange = get_patched_exchange(mocker, default_conf, id="binance")
exchange._set_leverage(3.0, trading_mode=TradingMode.MARGIN)
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"binance",
"_set_leverage",
"set_leverage",
pair="XRP/USDT",
leverage=5.0,
trading_mode=TradingMode.FUTURES
)
@pytest.mark.asyncio
async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog):
@pytest.mark.parametrize('candle_type', ['mark', ''])
async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog, candle_type):
ohlcv = [
[
int((datetime.now(timezone.utc).timestamp() - 1000) * 1000),
@@ -126,18 +538,55 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog):
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
pair = 'ETH/BTC'
respair, restf, res = await exchange._async_get_historic_ohlcv(
pair, "5m", 1500000000000, is_new_pair=False)
respair, restf, restype, res = await exchange._async_get_historic_ohlcv(
pair, "5m", 1500000000000, is_new_pair=False, candle_type=candle_type)
assert respair == pair
assert restf == '5m'
assert restype == candle_type
# 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, candle_type=candle_type)
# Called twice - one "init" call - and one to get the actual data.
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert res == ohlcv
assert log_has_re(r"Candle-data for ETH/BTC available starting with .*", caplog)
@pytest.mark.parametrize("trading_mode,margin_mode,config", [
("spot", "", {}),
("margin", "cross", {"options": {"defaultType": "margin"}}),
("futures", "isolated", {"options": {"defaultType": "future"}}),
])
def test__ccxt_config(default_conf, mocker, trading_mode, margin_mode, config):
default_conf['trading_mode'] = trading_mode
default_conf['margin_mode'] = margin_mode
exchange = get_patched_exchange(mocker, default_conf, id="binance")
assert exchange._ccxt_config == config
@pytest.mark.parametrize('pair,nominal_value,mm_ratio,amt', [
("BNB/BUSD", 0.0, 0.025, 0),
("BNB/USDT", 100.0, 0.0065, 0),
("BTC/USDT", 170.30, 0.004, 0),
("BNB/BUSD", 999999.9, 0.1, 27500.0),
("BNB/USDT", 5000000.0, 0.15, 233035.0),
("BTC/USDT", 600000000, 0.5, 1.997038E8),
])
def test_get_maintenance_ratio_and_amt_binance(
default_conf,
mocker,
leverage_tiers,
pair,
nominal_value,
mm_ratio,
amt,
):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
exchange = get_patched_exchange(mocker, default_conf, id="binance")
exchange._leverage_tiers = leverage_tiers
(result_ratio, result_amt) = exchange.get_maintenance_ratio_and_amt(pair, nominal_value)
assert (round(result_ratio, 8), round(result_amt, 8)) == (mm_ratio, amt)

View File

@@ -5,14 +5,16 @@ However, these tests should give a good idea to determine if a new exchange is
suitable to run with freqtrade.
"""
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from pathlib import Path
import pytest
from freqtrade.enums import CandleType
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import get_default_conf
from tests.conftest import get_default_conf_usdt
# Exchanges that should be tested
@@ -22,65 +24,91 @@ EXCHANGES = {
'stake_currency': 'USDT',
'hasQuoteVolume': False,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
'binance': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures': True,
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
'kraken': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
},
'ftx': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'pair': 'BTC/USD',
'stake_currency': 'USD',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures_pair': 'BTC/USD:USD',
'futures': False,
'leverage_tiers_public': False, # TODO: Set to True once implemented on CCXT
'leverage_in_spot_market': True,
},
'kucoin': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
},
'gateio': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
},
'okx': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures_pair': 'BTC/USDT:USDT',
'futures': True,
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
},
'huobi': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures': False,
},
'bitvavo': {
'pair': 'BTC/EUR',
'stake_currency': 'EUR',
'hasQuoteVolume': True,
'timeframe': '5m',
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
}
@pytest.fixture(scope="class")
def exchange_conf():
config = get_default_conf((Path(__file__).parent / "testdata").resolve())
config = get_default_conf_usdt((Path(__file__).parent / "testdata").resolve())
config['exchange']['pair_whitelist'] = []
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
config['dry_run'] = False
config['entry_pricing']['use_order_book'] = True
config['exit_pricing']['use_order_book'] = True
return config
@@ -93,6 +121,25 @@ def exchange(request, exchange_conf):
yield exchange, request.param
@pytest.fixture(params=EXCHANGES, scope="class")
def exchange_futures(request, exchange_conf, class_mocker):
if not EXCHANGES[request.param].get('futures') is True:
yield None, request.param
else:
exchange_conf = deepcopy(exchange_conf)
exchange_conf['exchange']['name'] = request.param
exchange_conf['trading_mode'] = 'futures'
exchange_conf['margin_mode'] = 'isolated'
exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency']
class_mocker.patch(
'freqtrade.exchange.binance.Binance.fill_leverage_tiers')
class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees')
exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True)
yield exchange, request.param
@pytest.mark.longrun
class TestCCXTExchange():
@@ -102,6 +149,20 @@ class TestCCXTExchange():
markets = exchange.markets
assert pair in markets
assert isinstance(markets[pair], dict)
assert exchange.market_is_spot(markets[pair])
def test_load_markets_futures(self, exchange_futures):
exchange, exchangename = exchange_futures
if not exchange:
# exchange_futures only returns values for supported exchanges
return
pair = EXCHANGES[exchangename]['pair']
pair = EXCHANGES[exchangename].get('futures_pair', pair)
markets = exchange.markets
assert pair in markets
assert isinstance(markets[pair], dict)
assert exchange.market_is_future(markets[pair])
def test_ccxt_fetch_tickers(self, exchange):
exchange, exchangename = exchange
@@ -161,7 +222,9 @@ class TestCCXTExchange():
exchange, exchangename = exchange
pair = EXCHANGES[exchangename]['pair']
timeframe = EXCHANGES[exchangename]['timeframe']
pair_tf = (pair, timeframe)
pair_tf = (pair, timeframe, CandleType.SPOT)
ohlcv = exchange.refresh_latest_ohlcv([pair_tf])
assert isinstance(ohlcv, dict)
assert len(ohlcv[pair_tf]) == len(exchange.klines(pair_tf))
@@ -172,6 +235,82 @@ class TestCCXTExchange():
now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2))
assert exchange.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now)
def test_ccxt_fetch_funding_rate_history(self, exchange_futures):
exchange, exchangename = exchange_futures
if not exchange:
# exchange_futures only returns values for supported exchanges
return
pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair'])
since = int((datetime.now(timezone.utc) - timedelta(days=5)).timestamp() * 1000)
timeframe_ff = exchange._ft_has.get('funding_fee_timeframe',
exchange._ft_has['mark_ohlcv_timeframe'])
pair_tf = (pair, timeframe_ff, CandleType.FUNDING_RATE)
funding_ohlcv = exchange.refresh_latest_ohlcv(
[pair_tf],
since_ms=since,
drop_incomplete=False)
assert isinstance(funding_ohlcv, dict)
rate = funding_ohlcv[pair_tf]
this_hour = timeframe_to_prev_date(timeframe_ff)
hour1 = timeframe_to_prev_date(timeframe_ff, this_hour - timedelta(minutes=1))
hour2 = timeframe_to_prev_date(timeframe_ff, hour1 - timedelta(minutes=1))
hour3 = timeframe_to_prev_date(timeframe_ff, hour2 - timedelta(minutes=1))
val0 = rate[rate['date'] == this_hour].iloc[0]['open']
val1 = rate[rate['date'] == hour1].iloc[0]['open']
val2 = rate[rate['date'] == hour2].iloc[0]['open']
val3 = rate[rate['date'] == hour3].iloc[0]['open']
# Test For last 4 hours
# Avoids random test-failure when funding-fees are 0 for a few hours.
assert val0 != 0.0 or val1 != 0.0 or val2 != 0.0 or val3 != 0.0
# We expect funding rates to be different from 0.0 - or moving around.
assert (
rate['open'].max() != 0.0 or rate['open'].min() != 0.0 or
(rate['open'].min() != rate['open'].max())
)
def test_ccxt_fetch_mark_price_history(self, exchange_futures):
exchange, exchangename = exchange_futures
if not exchange:
# exchange_futures only returns values for supported exchanges
return
pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair'])
since = int((datetime.now(timezone.utc) - timedelta(days=5)).timestamp() * 1000)
pair_tf = (pair, '1h', CandleType.MARK)
mark_ohlcv = exchange.refresh_latest_ohlcv(
[pair_tf],
since_ms=since,
drop_incomplete=False)
assert isinstance(mark_ohlcv, dict)
expected_tf = '1h'
mark_candles = mark_ohlcv[pair_tf]
this_hour = timeframe_to_prev_date(expected_tf)
prev_hour = timeframe_to_prev_date(expected_tf, this_hour - timedelta(minutes=1))
assert mark_candles[mark_candles['date'] == prev_hour].iloc[0]['open'] != 0.0
assert mark_candles[mark_candles['date'] == this_hour].iloc[0]['open'] != 0.0
def test_ccxt__calculate_funding_fees(self, exchange_futures):
exchange, exchangename = exchange_futures
if not exchange:
# exchange_futures only returns values for supported exchanges
return
pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair'])
since = datetime.now(timezone.utc) - timedelta(days=5)
funding_fee = exchange._fetch_and_calculate_funding_fees(
pair, 20, is_short=False, open_date=since)
assert isinstance(funding_fee, float)
# assert funding_fee > 0
# TODO: tests fetch_trades (?)
def test_ccxt_get_fee(self, exchange):
@@ -182,3 +321,110 @@ class TestCCXTExchange():
assert 0 < exchange.get_fee(pair, 'limit', 'sell') < threshold
assert 0 < exchange.get_fee(pair, 'market', 'buy') < threshold
assert 0 < exchange.get_fee(pair, 'market', 'sell') < threshold
def test_ccxt_get_max_leverage_spot(self, exchange):
spot, spot_name = exchange
if spot:
leverage_in_market_spot = EXCHANGES[spot_name].get('leverage_in_spot_market')
if leverage_in_market_spot:
spot_pair = EXCHANGES[spot_name].get('pair', EXCHANGES[spot_name]['pair'])
spot_leverage = spot.get_max_leverage(spot_pair, 20)
assert (isinstance(spot_leverage, float) or isinstance(spot_leverage, int))
assert spot_leverage >= 1.0
def test_ccxt_get_max_leverage_futures(self, exchange_futures):
futures, futures_name = exchange_futures
if futures:
leverage_tiers_public = EXCHANGES[futures_name].get('leverage_tiers_public')
if leverage_tiers_public:
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
futures_leverage = futures.get_max_leverage(futures_pair, 20)
assert (isinstance(futures_leverage, float) or isinstance(futures_leverage, int))
assert futures_leverage >= 1.0
def test_ccxt__get_contract_size(self, exchange_futures):
futures, futures_name = exchange_futures
if futures:
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
contract_size = futures._get_contract_size(futures_pair)
assert (isinstance(contract_size, float) or isinstance(contract_size, int))
assert contract_size >= 0.0
def test_ccxt_load_leverage_tiers(self, exchange_futures):
futures, futures_name = exchange_futures
if futures and EXCHANGES[futures_name].get('leverage_tiers_public'):
leverage_tiers = futures.load_leverage_tiers()
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
assert (isinstance(leverage_tiers, dict))
assert futures_pair in leverage_tiers
pair_tiers = leverage_tiers[futures_pair]
assert len(pair_tiers) > 0
oldLeverage = float('inf')
oldMaintenanceMarginRate = oldNotionalFloor = oldNotionalCap = -1
for tier in pair_tiers:
for key in [
'maintenanceMarginRate',
'notionalFloor',
'notionalCap',
'maxLeverage'
]:
assert key in tier
assert tier[key] >= 0.0
assert tier['notionalCap'] > tier['notionalFloor']
assert tier['maxLeverage'] <= oldLeverage
assert tier['maintenanceMarginRate'] >= oldMaintenanceMarginRate
assert tier['notionalFloor'] > oldNotionalFloor
assert tier['notionalCap'] > oldNotionalCap
oldLeverage = tier['maxLeverage']
oldMaintenanceMarginRate = tier['maintenanceMarginRate']
oldNotionalFloor = tier['notionalFloor']
oldNotionalCap = tier['notionalCap']
def test_ccxt_dry_run_liquidation_price(self, exchange_futures):
futures, futures_name = exchange_futures
if futures and EXCHANGES[futures_name].get('leverage_tiers_public'):
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
liquidation_price = futures.dry_run_liquidation_price(
futures_pair,
40000,
False,
100,
100,
)
assert (isinstance(liquidation_price, float))
assert liquidation_price >= 0.0
liquidation_price = futures.dry_run_liquidation_price(
futures_pair,
40000,
False,
100,
100,
)
assert (isinstance(liquidation_price, float))
assert liquidation_price >= 0.0
def test_ccxt_get_max_pair_stake_amount(self, exchange_futures):
futures, futures_name = exchange_futures
if futures:
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
max_stake_amount = futures.get_max_pair_stake_amount(futures_pair, 40000)
assert (isinstance(max_stake_amount, float))
assert max_stake_amount >= 0.0

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,11 @@ from .test_exchange import ccxt_exceptionhandlers
STOPLOSS_ORDERTYPE = 'stop'
def test_stoploss_order_ftx(default_conf, mocker):
@pytest.mark.parametrize('order_price,exchangelimitratio,side', [
(217.8, 1.05, "sell"),
(222.2, 0.95, "buy"),
])
def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitratio, side):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
@@ -32,12 +36,18 @@ def test_stoploss_order_ftx(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
# stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=190,
side=side,
order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio},
leverage=1.0
)
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
assert 'stopPrice' in api_mock.create_order.call_args_list[0][1]['params']
@@ -47,51 +57,79 @@ def test_stoploss_order_ftx(default_conf, mocker):
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={'stoploss': 'limit'})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={'stoploss': 'limit'}, side=side,
leverage=1.0
)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params']
assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == 217.8
assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == order_price
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_ftx(default_conf, mocker):
@pytest.mark.parametrize('side', [("sell"), ("buy")])
def test_stoploss_order_dry_run_ftx(default_conf, mocker, side):
api_mock = MagicMock()
default_conf['dry_run'] = True
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
@@ -101,7 +139,14 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker):
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
assert 'id' in order
assert 'info' in order
@@ -112,20 +157,24 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker):
assert order['amount'] == 1
def test_stoploss_adjust_ftx(mocker, default_conf):
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
(1501, 1499, 1501, "sell"),
(1499, 1501, 1499, "buy")
])
def test_stoploss_adjust_ftx(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
order = {
'type': STOPLOSS_ORDERTYPE,
'price': 1500,
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
assert exchange.stoploss_adjust(sl1, order, side=side)
assert not exchange.stoploss_adjust(sl2, order, side=side)
# Test with invalid order case ...
order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(sl3, order, side=side)
def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order):
def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order, limit_buy_order):
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123

View File

@@ -1,7 +1,9 @@
from datetime import datetime, timezone
from unittest.mock import MagicMock
import pytest
from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Gateio
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
@@ -15,13 +17,14 @@ def test_validate_order_types_gateio(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
exch = ExchangeResolver.load_exchange('gateio', default_conf, True)
assert isinstance(exch, Gateio)
default_conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
'entry': 'market',
'exit': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
@@ -57,11 +60,59 @@ def test_cancel_stoploss_order_gateio(default_conf, mocker):
assert cancel_order_mock.call_args_list[0][1]['params'] == {'stop': True}
def test_stoploss_adjust_gateio(mocker, default_conf):
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
(1501, 1499, 1501, "sell"),
(1499, 1501, 1499, "buy")
])
def test_stoploss_adjust_gateio(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='gateio')
order = {
'price': 1500,
'stopPrice': 1500,
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
assert exchange.stoploss_adjust(sl1, order, side)
assert not exchange.stoploss_adjust(sl2, order, side)
@pytest.mark.parametrize('takerormaker,rate,cost', [
('taker', 0.0005, 0.0001554325),
('maker', 0.0, 0.0),
])
def test_fetch_my_trades_gateio(mocker, default_conf, takerormaker, rate, cost):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
tick = {'ETH/USDT:USDT': {
'info': {'user_id': '',
'taker_fee': '0.0018',
'maker_fee': '0.0018',
'gt_discount': False,
'gt_taker_fee': '0',
'gt_maker_fee': '0',
'loan_fee': '0.18',
'point_type': '1',
'futures_taker_fee': '0.0005',
'futures_maker_fee': '0'},
'symbol': 'ETH/USDT:USDT',
'maker': 0.0,
'taker': 0.0005}
}
default_conf['dry_run'] = False
default_conf['trading_mode'] = TradingMode.FUTURES
default_conf['margin_mode'] = MarginMode.ISOLATED
api_mock = MagicMock()
api_mock.fetch_my_trades = MagicMock(return_value=[{
'fee': {'cost': None},
'price': 3108.65,
'cost': 0.310865,
'order': '22255',
'takerOrMaker': takerormaker,
'amount': 1, # 1 contract
}])
exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock, id='gateio')
exchange._trading_fees = tick
trades = exchange.get_trades_for_order('22255', 'ETH/USDT:USDT', datetime.now(timezone.utc))
trade = trades[0]
assert trade['fee']
assert trade['fee']['rate'] == rate
assert trade['fee']['currency'] == 'USDT'
assert trade['fee']['cost'] == cost

View File

@@ -9,12 +9,12 @@ from tests.conftest import get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers
@pytest.mark.parametrize('limitratio,expected', [
(None, 220 * 0.99),
(0.99, 220 * 0.99),
(0.98, 220 * 0.98),
@pytest.mark.parametrize('limitratio,expected,side', [
(None, 220 * 0.99, "sell"),
(0.99, 220 * 0.99, "sell"),
(0.98, 220 * 0.98, "sell"),
])
def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected):
def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'stop-limit'
@@ -33,11 +33,14 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected):
with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
side=side,
leverage=1.0)
api_mock.create_order.reset_mock()
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types)
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types,
side=side, leverage=1.0)
assert 'id' in order
assert 'info' in order
@@ -56,17 +59,20 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected):
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0)
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_huobi(default_conf, mocker):
@@ -80,11 +86,13 @@ def test_stoploss_order_dry_run_huobi(default_conf, mocker):
with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
side='sell', leverage=1.0)
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side='sell', leverage=1.0)
assert 'id' in order
assert 'info' in order
@@ -102,8 +110,8 @@ def test_stoploss_adjust_huobi(mocker, default_conf):
'price': 1500,
'stopPrice': '1500',
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
assert exchange.stoploss_adjust(1501, order, 'sell')
assert not exchange.stoploss_adjust(1499, order, 'sell')
# Test with invalid order case
order['type'] = 'stop_loss'
assert not exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1501, order, 'sell')

View File

@@ -21,6 +21,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
'info': {
'foo': 'bar'
}
@@ -31,8 +32,15 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
amount=1, rate=200, time_in_force=time_in_force)
order = exchange.create_order(
pair='ETH/BTC',
ordertype=order_type,
side="buy",
amount=1,
rate=200,
leverage=1.0,
time_in_force=time_in_force
)
assert 'id' in order
assert 'info' in order
@@ -53,6 +61,7 @@ def test_sell_kraken_trading_agreement(default_conf, mocker):
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
'info': {
'foo': 'bar'
}
@@ -64,7 +73,7 @@ def test_sell_kraken_trading_agreement(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
side="sell", amount=1, rate=200)
side="sell", amount=1, rate=200, leverage=1.0)
assert 'id' in order
assert 'info' in order
@@ -166,7 +175,11 @@ def test_get_balances_prod(default_conf, mocker):
@pytest.mark.parametrize('ordertype', ['market', 'limit'])
def test_stoploss_order_kraken(default_conf, mocker, ordertype):
@pytest.mark.parametrize('side,adjustedprice', [
("sell", 217.8),
("buy", 222.2),
])
def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
@@ -183,10 +196,17 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={'stoploss': ordertype,
'stoploss_on_exchange_limit_ratio': 0.99
})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
side=side,
order_types={
'stoploss': ordertype,
'stoploss_on_exchange_limit_ratio': 0.99
},
leverage=1.0
)
assert 'id' in order
assert 'info' in order
@@ -195,12 +215,14 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype):
if ordertype == 'limit':
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['params'] == {
'trading_agreement': 'agree', 'price2': 217.8}
'trading_agreement': 'agree',
'price2': adjustedprice
}
else:
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['params'] == {
'trading_agreement': 'agree'}
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
@@ -208,20 +230,36 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype):
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_kraken(default_conf, mocker):
@pytest.mark.parametrize('side', ['buy', 'sell'])
def test_stoploss_order_dry_run_kraken(default_conf, mocker, side):
api_mock = MagicMock()
default_conf['dry_run'] = True
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
@@ -231,7 +269,14 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker):
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
assert 'id' in order
assert 'info' in order
@@ -242,14 +287,18 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker):
assert order['amount'] == 1
def test_stoploss_adjust_kraken(mocker, default_conf):
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
(1501, 1499, 1501, "sell"),
(1499, 1501, 1499, "buy")
])
def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='kraken')
order = {
'type': STOPLOSS_ORDERTYPE,
'price': 1500,
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
assert exchange.stoploss_adjust(sl1, order, side=side)
assert not exchange.stoploss_adjust(sl2, order, side=side)
# Test with invalid order case ...
order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(sl3, order, side=side)

View File

@@ -10,12 +10,12 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
@pytest.mark.parametrize('order_type', ['market', 'limit'])
@pytest.mark.parametrize('limitratio,expected', [
(None, 220 * 0.99),
(0.99, 220 * 0.99),
(0.98, 220 * 0.98),
@pytest.mark.parametrize('limitratio,expected,side', [
(None, 220 * 0.99, "sell"),
(0.99, 220 * 0.99, "sell"),
(0.98, 220 * 0.98, "sell"),
])
def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order_type):
def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, order_type):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
@@ -35,13 +35,15 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={
'stoploss': order_type,
'stoploss_on_exchange_limit_ratio': 1.05})
'stoploss_on_exchange_limit_ratio': 1.05},
side=side, leverage=1.0)
api_mock.create_order.reset_mock()
order_types = {'stoploss': order_type}
if limitratio is not None:
order_types.update({'stoploss_on_exchange_limit_ratio': limitratio})
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types)
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types=order_types, side=side, leverage=1.0)
assert 'id' in order
assert 'info' in order
@@ -65,17 +67,20 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0)
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("kucoin Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kucoin",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
@@ -90,11 +95,13 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss': 'limit',
'stoploss_on_exchange_limit_ratio': 1.05})
'stoploss_on_exchange_limit_ratio': 1.05},
side='sell', leverage=1.0)
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side='sell', leverage=1.0)
assert 'id' in order
assert 'info' in order
@@ -113,8 +120,8 @@ def test_stoploss_adjust_kucoin(mocker, default_conf):
'stopPrice': 1500,
'info': {'stopPrice': 1500, 'stop': "limit"},
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
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)
assert not exchange.stoploss_adjust(1501, order, 'sell')

360
tests/exchange/test_okx.py Normal file
View File

@@ -0,0 +1,360 @@
from unittest.mock import MagicMock, PropertyMock
from freqtrade.enums import MarginMode, TradingMode
from tests.conftest import get_patched_exchange
def test_get_maintenance_ratio_and_amt_okx(
default_conf,
mocker,
):
api_mock = MagicMock()
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
default_conf['dry_run'] = False
mocker.patch.multiple(
'freqtrade.exchange.Okx',
exchange_has=MagicMock(return_value=True),
load_leverage_tiers=MagicMock(return_value={
'ETH/USDT:USDT': [
{
'tier': 1,
'notionalFloor': 0,
'notionalCap': 2000,
'maintenanceMarginRate': 0.01,
'maxLeverage': 75,
'info': {
'baseMaxLoan': '',
'imr': '0.013',
'instId': '',
'maxLever': '75',
'maxSz': '2000',
'minSz': '0',
'mmr': '0.01',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '1',
'uly': 'ETH-USDT'
}
},
{
'tier': 2,
'notionalFloor': 2001,
'notionalCap': 4000,
'maintenanceMarginRate': 0.015,
'maxLeverage': 50,
'info': {
'baseMaxLoan': '',
'imr': '0.02',
'instId': '',
'maxLever': '50',
'maxSz': '4000',
'minSz': '2001',
'mmr': '0.015',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '2',
'uly': 'ETH-USDT'
}
},
{
'tier': 3,
'notionalFloor': 4001,
'notionalCap': 8000,
'maintenanceMarginRate': 0.02,
'maxLeverage': 20,
'info': {
'baseMaxLoan': '',
'imr': '0.05',
'instId': '',
'maxLever': '20',
'maxSz': '8000',
'minSz': '4001',
'mmr': '0.02',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '3',
'uly': 'ETH-USDT'
}
},
],
'ADA/USDT:USDT': [
{
'tier': 1,
'notionalFloor': 0,
'notionalCap': 500,
'maintenanceMarginRate': 0.02,
'maxLeverage': 75,
'info': {
'baseMaxLoan': '',
'imr': '0.013',
'instId': '',
'maxLever': '75',
'maxSz': '500',
'minSz': '0',
'mmr': '0.01',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '1',
'uly': 'ADA-USDT'
}
},
{
'tier': 2,
'notionalFloor': 501,
'notionalCap': 1000,
'maintenanceMarginRate': 0.025,
'maxLeverage': 50,
'info': {
'baseMaxLoan': '',
'imr': '0.02',
'instId': '',
'maxLever': '50',
'maxSz': '1000',
'minSz': '501',
'mmr': '0.015',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '2',
'uly': 'ADA-USDT'
}
},
{
'tier': 3,
'notionalFloor': 1001,
'notionalCap': 2000,
'maintenanceMarginRate': 0.03,
'maxLeverage': 20,
'info': {
'baseMaxLoan': '',
'imr': '0.05',
'instId': '',
'maxLever': '20',
'maxSz': '2000',
'minSz': '1001',
'mmr': '0.02',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '3',
'uly': 'ADA-USDT'
}
},
]
})
)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx")
assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 2000) == (0.01, None)
assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 2001) == (0.015, None)
assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 4001) == (0.02, None)
assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 8000) == (0.02, None)
assert exchange.get_maintenance_ratio_and_amt('ADA/USDT:USDT', 1) == (0.02, None)
assert exchange.get_maintenance_ratio_and_amt('ADA/USDT:USDT', 2000) == (0.03, None)
def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers):
exchange = get_patched_exchange(mocker, default_conf, id="okx")
assert exchange.get_max_pair_stake_amount('BNB/BUSD', 1.0) == float('inf')
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
exchange = get_patched_exchange(mocker, default_conf, id="okx")
exchange._leverage_tiers = leverage_tiers
assert exchange.get_max_pair_stake_amount('BNB/BUSD', 1.0) == 30000000
assert exchange.get_max_pair_stake_amount('BNB/USDT', 1.0) == 50000000
assert exchange.get_max_pair_stake_amount('BTC/USDT', 1.0) == 1000000000
assert exchange.get_max_pair_stake_amount('BTC/USDT', 1.0, 10.0) == 100000000
assert exchange.get_max_pair_stake_amount('TTT/USDT', 1.0) == float('inf') # Not in tiers
def test_load_leverage_tiers_okx(default_conf, mocker, markets):
api_mock = MagicMock()
type(api_mock).has = PropertyMock(return_value={
'fetchLeverageTiers': False,
'fetchMarketLeverageTiers': True,
})
api_mock.fetch_market_leverage_tiers = MagicMock(side_effect=[
[
{
'tier': 1,
'notionalFloor': 0,
'notionalCap': 500,
'maintenanceMarginRate': 0.02,
'maxLeverage': 75,
'info': {
'baseMaxLoan': '',
'imr': '0.013',
'instId': '',
'maxLever': '75',
'maxSz': '500',
'minSz': '0',
'mmr': '0.01',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '1',
'uly': 'ADA-USDT'
}
},
{
'tier': 2,
'notionalFloor': 501,
'notionalCap': 1000,
'maintenanceMarginRate': 0.025,
'maxLeverage': 50,
'info': {
'baseMaxLoan': '',
'imr': '0.02',
'instId': '',
'maxLever': '50',
'maxSz': '1000',
'minSz': '501',
'mmr': '0.015',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '2',
'uly': 'ADA-USDT'
}
},
{
'tier': 3,
'notionalFloor': 1001,
'notionalCap': 2000,
'maintenanceMarginRate': 0.03,
'maxLeverage': 20,
'info': {
'baseMaxLoan': '',
'imr': '0.05',
'instId': '',
'maxLever': '20',
'maxSz': '2000',
'minSz': '1001',
'mmr': '0.02',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '3',
'uly': 'ADA-USDT'
}
},
],
[
{
'tier': 1,
'notionalFloor': 0,
'notionalCap': 2000,
'maintenanceMarginRate': 0.01,
'maxLeverage': 75,
'info': {
'baseMaxLoan': '',
'imr': '0.013',
'instId': '',
'maxLever': '75',
'maxSz': '2000',
'minSz': '0',
'mmr': '0.01',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '1',
'uly': 'ETH-USDT'
}
},
{
'tier': 2,
'notionalFloor': 2001,
'notionalCap': 4000,
'maintenanceMarginRate': 0.015,
'maxLeverage': 50,
'info': {
'baseMaxLoan': '',
'imr': '0.02',
'instId': '',
'maxLever': '50',
'maxSz': '4000',
'minSz': '2001',
'mmr': '0.015',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '2',
'uly': 'ETH-USDT'
}
},
{
'tier': 3,
'notionalFloor': 4001,
'notionalCap': 8000,
'maintenanceMarginRate': 0.02,
'maxLeverage': 20,
'info': {
'baseMaxLoan': '',
'imr': '0.05',
'instId': '',
'maxLever': '20',
'maxSz': '8000',
'minSz': '4001',
'mmr': '0.02',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '3',
'uly': 'ETH-USDT'
}
},
]
])
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
default_conf['stake_currency'] = 'USDT'
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx")
exchange.trading_mode = TradingMode.FUTURES
exchange.margin_mode = MarginMode.ISOLATED
exchange.markets = markets
# Initialization of load_leverage_tiers happens as part of exchange init.
assert exchange._leverage_tiers == {
'ADA/USDT:USDT': [
{
'min': 0,
'max': 500,
'mmr': 0.02,
'lev': 75,
'maintAmt': None
},
{
'min': 501,
'max': 1000,
'mmr': 0.025,
'lev': 50,
'maintAmt': None
},
{
'min': 1001,
'max': 2000,
'mmr': 0.03,
'lev': 20,
'maintAmt': None
},
],
'ETH/USDT:USDT': [
{
'min': 0,
'max': 2000,
'mmr': 0.01,
'lev': 75,
'maintAmt': None
},
{
'min': 2001,
'max': 4000,
'mmr': 0.015,
'lev': 50,
'maintAmt': None
},
{
'min': 4001,
'max': 8000,
'mmr': 0.02,
'lev': 20,
'maintAmt': None
},
],
}

View File

@@ -0,0 +1,27 @@
import pytest
from freqtrade.enums import CandleType
@pytest.mark.parametrize('input,expected', [
('', CandleType.SPOT),
('spot', CandleType.SPOT),
(CandleType.SPOT, CandleType.SPOT),
(CandleType.FUTURES, CandleType.FUTURES),
(CandleType.INDEX, CandleType.INDEX),
(CandleType.MARK, CandleType.MARK),
('futures', CandleType.FUTURES),
('mark', CandleType.MARK),
('premiumIndex', CandleType.PREMIUMINDEX),
])
def test_CandleType_from_string(input, expected):
assert CandleType.from_string(input) == expected
@pytest.mark.parametrize('input,expected', [
('futures', CandleType.FUTURES),
('spot', CandleType.SPOT),
('margin', CandleType.SPOT),
])
def test_CandleType_get_default(input, expected):
assert CandleType.get_default(input) == expected

View File

@@ -0,0 +1,38 @@
from decimal import Decimal
from math import isclose
import pytest
from freqtrade.leverage import interest
ten_mins = Decimal(1/6)
five_hours = Decimal(5.0)
twentyfive_hours = Decimal(25.0)
@pytest.mark.parametrize('exchange,interest_rate,hours,expected', [
('binance', 0.0005, ten_mins, 0.00125),
('binance', 0.00025, ten_mins, 0.000625),
('binance', 0.00025, five_hours, 0.003125),
('binance', 0.00025, twentyfive_hours, 0.015625),
# Kraken
('kraken', 0.0005, ten_mins, 0.06),
('kraken', 0.00025, ten_mins, 0.03),
('kraken', 0.00025, five_hours, 0.045),
('kraken', 0.00025, twentyfive_hours, 0.12),
# FTX
('ftx', 0.0005, ten_mins, 0.00125),
('ftx', 0.00025, ten_mins, 0.000625),
('ftx', 0.00025, five_hours, 0.003125),
('ftx', 0.00025, twentyfive_hours, 0.015625),
])
def test_interest(exchange, interest_rate, hours, expected):
borrowed = Decimal(60.0)
assert isclose(interest(
exchange_name=exchange,
borrowed=borrowed,
rate=Decimal(interest_rate),
hours=hours
), expected)

View File

@@ -3,7 +3,7 @@ from typing import Dict, List, NamedTuple, Optional
import arrow
from pandas import DataFrame
from freqtrade.enums import SellType
from freqtrade.enums import ExitType
from freqtrade.exchange import timeframe_to_minutes
@@ -15,10 +15,11 @@ class BTrade(NamedTuple):
"""
Minimalistic Trade result used for functional backtesting
"""
sell_reason: SellType
sell_reason: ExitType
open_tick: int
close_tick: int
buy_tag: Optional[str] = None
enter_tag: Optional[str] = None
is_short: bool = False
class BTContainer(NamedTuple):
@@ -38,6 +39,7 @@ class BTContainer(NamedTuple):
use_custom_stoploss: bool = False
custom_entry_price: Optional[float] = None
custom_exit_price: Optional[float] = None
leverage: float = 1.0
def _get_frame_time_from_offset(offset):
@@ -46,18 +48,18 @@ def _get_frame_time_from_offset(offset):
def _build_backtest_dataframe(data):
columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell']
columns = columns + ['buy_tag'] if len(data[0]) == 9 else columns
columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'enter_long', 'exit_long',
'enter_short', 'exit_short']
if len(data[0]) == 8:
# No short columns
data = [d + [0, 0] for d in data]
columns = columns + ['enter_tag'] if len(data[0]) == 11 else columns
frame = DataFrame.from_records(data, columns=columns)
frame['date'] = frame['date'].apply(_get_frame_time_from_offset)
# Ensure floats are in place
for column in ['open', 'high', 'low', 'close', 'volume']:
frame[column] = frame[column].astype('float64')
if 'buy_tag' not in columns:
frame['buy_tag'] = None
if 'exit_tag' not in columns:
frame['exit_tag'] = None
# Ensure all candles make kindof sense
assert all(frame['low'] <= frame['close'])

View File

@@ -5,7 +5,7 @@ from pathlib import Path
import pandas as pd
import pytest
from freqtrade.enums import RunMode, SellType
from freqtrade.enums import ExitType, RunMode
from freqtrade.optimize.hyperopt import Hyperopt
from tests.conftest import patch_exchange
@@ -44,7 +44,7 @@ def hyperopt_results():
'profit_abs': [-0.2, 0.4, -0.2, 0.6],
'trade_duration': [10, 30, 10, 10],
'amount': [0.1, 0.1, 0.1, 0.1],
'sell_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.STOP_LOSS, SellType.ROI],
'sell_reason': [ExitType.STOP_LOSS, ExitType.ROI, ExitType.STOP_LOSS, ExitType.ROI],
'open_date':
[
datetime(2019, 1, 1, 9, 15, 0),

View File

@@ -5,7 +5,7 @@ from unittest.mock import MagicMock
import pytest
from freqtrade.data.history import get_timerange
from freqtrade.enums import SellType
from freqtrade.enums import ExitType
from freqtrade.optimize.backtesting import Backtesting
from tests.conftest import patch_exchange
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
@@ -15,7 +15,7 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
# Test 0: Sell with signal sell in candle 3
# Test with Stop-loss at 1%
tc0 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0], # exit with stoploss hit
@@ -23,13 +23,13 @@ tc0 = BTContainer(data=[
[4, 5010, 5011, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 1: Stop-Loss Triggered 1% loss
# Test with Stop-loss at 1%
tc1 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4600, 4600, 6172, 0, 0], # exit with stoploss hit
@@ -37,14 +37,14 @@ tc1 = BTContainer(data=[
[4, 4977, 4995, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 2: Minus 4% Low, minus 1% close
# Test with Stop-Loss at 3%
tc2 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4962, 4975, 6172, 0, 0],
@@ -52,7 +52,7 @@ tc2 = BTContainer(data=[
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.03, roi={"0": 1}, profit_perc=-0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3)]
)
@@ -63,7 +63,7 @@ tc2 = BTContainer(data=[
# Trade-A: Stop-Loss Triggered 2% Loss
# Trade-B: Stop-Loss Triggered 2% Loss
tc3 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4800, 4975, 6172, 0, 0], # exit with stoploss hit
@@ -72,8 +72,8 @@ tc3 = BTContainer(data=[
[5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit
[6, 4950, 4975, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2),
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2),
BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=4, close_tick=5)]
)
# Test 4: Minus 3% / recovery +15%
@@ -81,7 +81,7 @@ tc3 = BTContainer(data=[
# Test with Stop-loss at 2% ROI 6%
# Stop-Loss Triggered 2% Loss
tc4 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5750, 4850, 5750, 6172, 0, 0], # Exit with stoploss hit
@@ -89,13 +89,13 @@ tc4 = BTContainer(data=[
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.06}, profit_perc=-0.02,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 5: Drops 0.5% Closes +20%, ROI triggers 3% Gain
# stop-loss: 1%, ROI: 3%
tc5 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4980, 4987, 6172, 1, 0],
[1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5025, 4975, 4987, 6172, 0, 0],
@@ -103,13 +103,13 @@ tc5 = BTContainer(data=[
[4, 4962, 4987, 4962, 4972, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 6: Drops 3% / Recovers 6% Positive / Closes 1% positve, Stop-Loss triggers 2% Loss
# stop-loss: 2% ROI: 5%
tc6 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5300, 4850, 5050, 6172, 0, 0], # Exit with stoploss
@@ -117,13 +117,13 @@ tc6 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 7: 6% Positive / 1% Negative / Close 1% Positve, ROI Triggers 3% Gain
# stop-loss: 2% ROI: 3%
tc7 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5050, 6172, 0, 0],
@@ -131,42 +131,42 @@ tc7 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)]
)
# Test 8: trailing_stop should raise so candle 3 causes a stoploss.
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted in candle 2
tc8 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5000, 6172, 0, 0],
[2, 5000, 5250, 4750, 4850, 6172, 0, 0],
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.055, trailing_stop=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 9: trailing_stop should raise - high and low in same candle.
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted in candle 3
tc9 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5000, 6172, 0, 0],
[2, 5000, 5050, 4950, 5000, 6172, 0, 0],
[3, 5000, 5200, 4550, 4850, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.064, trailing_stop=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 10: trailing_stop should raise so candle 3 causes a stoploss
# without applying trailing_stop_positive since stoploss_offset is at 10%.
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
tc10 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
@@ -175,14 +175,14 @@ tc10 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.1, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.10,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=4)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=4)]
)
# Test 11: trailing_stop should raise so candle 3 causes a stoploss
# applying a positive trailing stop of 3% since stop_positive_offset is reached.
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
tc11 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
@@ -191,14 +191,14 @@ tc11 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 12: trailing_stop should raise in candle 2 and cause a stoploss in the same candle
# applying a positive trailing stop of 3% since stop_positive_offset is reached.
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
tc12 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 4650, 5100, 6172, 0, 0],
@@ -207,55 +207,55 @@ tc12 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 13: Buy and sell ROI on same candle
# stop-loss: 10% (should not apply), ROI: 1%
tc13 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 4850, 5100, 6172, 0, 0],
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4750, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=1)]
)
# Test 14 - Buy and Stoploss on same candle
# stop-loss: 5%, ROI: 10% (should not apply)
tc14 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4600, 5100, 6172, 0, 0],
[2, 5100, 5251, 4850, 5100, 6172, 0, 0],
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 15 - Buy and ROI on same candle, followed by buy and Stoploss on next candle
# stop-loss: 5%, ROI: 10% (should not apply)
tc15 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4900, 5100, 6172, 1, 0],
[2, 5100, 5251, 4650, 5100, 6172, 0, 0],
[3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1),
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=1),
BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=2, close_tick=2)]
)
# Test 16: Buy, hold for 65 min, then forcesell using roi=-1
# Causes negative profit even though sell-reason is ROI.
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 65 minutes (limits trade duration)
tc16 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5050, 6172, 0, 0],
@@ -263,7 +263,7 @@ tc16 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 17: Buy, hold for 120 mins, then forcesell using roi=-1
@@ -271,7 +271,7 @@ tc16 = BTContainer(data=[
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration)
# Uses open as sell-rate (special case) - since the roi-time is a multiple of the timeframe.
tc17 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5050, 6172, 0, 0],
@@ -279,7 +279,7 @@ tc17 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
@@ -287,7 +287,7 @@ tc17 = BTContainer(data=[
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration)
# uses open_rate as sell-price
tc18 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5200, 6172, 0, 0],
@@ -295,14 +295,14 @@ tc18 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.04,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 19: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3.
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration)
# uses calculated ROI (1%) as sell rate, otherwise identical to tc18
tc19 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5200, 6172, 0, 0],
@@ -310,14 +310,14 @@ tc19 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4550, 4975, 4550, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 20: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3.
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration)
# uses calculated ROI (1%) as sell rate, otherwise identical to tc18
tc20 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5200, 6172, 0, 0],
@@ -325,7 +325,7 @@ tc20 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4925, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 21: trailing_stop ROI collision.
@@ -333,7 +333,7 @@ tc20 = BTContainer(data=[
# which cannot happen in reality
# stop-loss: 10%, ROI: 4%, Trailing stop adjusted at the sell candle
tc21 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 4650, 5100, 6172, 0, 0],
@@ -342,14 +342,14 @@ tc21 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)]
)
# Test 22: trailing_stop Raises in candle 2 - but ROI applies at the same time.
# applying a positive trailing stop of 3% - ROI should apply before trailing stop.
# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2
tc22 = BTContainer(data=[
# D O H L C V B S
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
@@ -358,17 +358,34 @@ tc22 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)]
)
# Test 23: trailing_stop Raises in candle 2 (does not trigger)
# Test 23: trailing_stop Raises in candle 2 - but ROI applies at the same time.
# applying a positive trailing stop of 3% - ROI should apply before trailing stop.
# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2
tc23 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
[1, 5000, 5050, 4900, 4900, 6172, 0, 0, 0, 0],
[2, 4900, 4900, 4749, 4900, 6172, 0, 0, 0, 0],
[3, 4850, 5050, 4650, 4750, 6172, 0, 0, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0]],
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2, is_short=True)]
)
# Test 24: trailing_stop Raises in candle 2 (does not trigger)
# applying a positive trailing stop of 3% since stop_positive_offset is reached.
# ROI is changed after this to 4%, dropping ROI below trailing_stop_positive, causing a sell
# in the candle after the raised stoploss candle with ROI reason.
# Stoploss would trigger in this candle too, but it's no longer relevant.
# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2, ROI adjusted in candle 3 (causing the sell)
tc23 = BTContainer(data=[
# D O H L C V B S
tc24 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
@@ -377,14 +394,14 @@ tc23 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.1, "119": 0.03}, profit_perc=0.03, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 24: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Test 25: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Stoploss at 1%.
# Stoploss wins over Sell-signal (because sell-signal is acted on in the next candle)
tc24 = BTContainer(data=[
# D O H L C V B S
tc25 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0],
@@ -392,14 +409,14 @@ tc24 = BTContainer(data=[
[4, 5010, 5010, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 25: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Test 26: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Stoploss at 1%.
# Sell-signal wins over stoploss
tc25 = BTContainer(data=[
# D O H L C V B S
tc26 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0],
@@ -407,14 +424,47 @@ tc25 = BTContainer(data=[
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 26: Sell with signal sell in candle 3 (ROI at signal candle)
# Test 27: (copy of test26 with leverage)
# Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Stoploss at 1%.
# Sell-signal wins over stoploss
tc27 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0],
[3, 5010, 5010, 4986, 5010, 6172, 0, 1],
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_sell_signal=True,
leverage=5.0,
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 28: (copy of test26 with leverage and as short)
# Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Stoploss at 1%.
# Sell-signal wins over stoploss
tc28 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 0, 0, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0, 0, 0],
[3, 5010, 5010, 4986, 5010, 6172, 0, 0, 0, 1],
[4, 4990, 5010, 4855, 4995, 6172, 0, 0, 0, 0], # Triggers stoploss + sellsignal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0, 0, 0]],
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_sell_signal=True,
leverage=5.0,
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4, is_short=True)]
)
# Test 29: Sell with signal sell in candle 3 (ROI at signal candle)
# Stoploss at 10% (irrelevant), ROI at 5% (will trigger)
# Sell-signal wins over stoploss
tc26 = BTContainer(data=[
# D O H L C V B S
tc29 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0],
@@ -422,13 +472,13 @@ tc26 = BTContainer(data=[
[4, 5010, 5010, 4855, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)]
)
# Test 27: Sell with signal sell in candle 3 (ROI at signal candle)
# Test 30: Sell with signal sell in candle 3 (ROI at signal candle)
# Stoploss at 10% (irrelevant), ROI at 5% (will trigger) - Wins over Sell-signal
tc27 = BTContainer(data=[
# D O H L C V B S
tc30 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4986, 6172, 0, 0],
@@ -436,15 +486,15 @@ tc27 = BTContainer(data=[
[4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 28: trailing_stop should raise so candle 3 causes a stoploss
# Test 31: trailing_stop should raise so candle 3 causes a stoploss
# Same case than tc11 - but candle 3 "gaps down" - the stoploss will be above the candle,
# therefore "open" will be used
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
tc28 = BTContainer(data=[
# D O H L C V B S
tc31 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5100, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
@@ -453,14 +503,33 @@ tc28 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 29: trailing_stop should be triggered by low of next candle, without adjusting stoploss using
# Test 32: (Short of test 31) trailing_stop should raise so candle 3 causes a stoploss
# Same case than tc11 - but candle 3 "gaps down" - the stoploss will be above the candle,
# therefore "open" will be used
# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
tc32 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
[1, 5000, 5050, 4890, 4890, 6172, 0, 0, 0, 0],
[2, 4890, 4890, 4749, 4890, 6172, 0, 0, 0, 0],
[3, 5150, 5350, 4950, 4950, 6172, 0, 0, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[
BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3, is_short=True)
]
)
# Test 33: trailing_stop should be triggered by low of next candle, without adjusting stoploss using
# high of stoploss candle.
# stop-loss: 10%, ROI: 10% (should not apply)
tc29 = BTContainer(data=[
# D O H L C V B S
tc33 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 5000, 5000, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4900, 5250, 4500, 5100, 6172, 0, 0], # Triggers trailing-stoploss
@@ -468,13 +537,13 @@ tc29 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 30: trailing_stop should be triggered immediately on trade open candle.
# Test 34: trailing_stop should be triggered immediately on trade open candle.
# stop-loss: 10%, ROI: 10% (should not apply)
tc30 = BTContainer(data=[
# D O H L C V B S
tc34 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4900, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
@@ -482,13 +551,13 @@ tc30 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_stop_positive=0.01,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 31: trailing_stop should be triggered immediately on trade open candle.
# Test 35: trailing_stop should be triggered immediately on trade open candle.
# stop-loss: 10%, ROI: 10% (should not apply)
tc31 = BTContainer(data=[
# D O H L C V B S
tc35 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4900, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
@@ -497,47 +566,68 @@ tc31 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 32: trailing_stop should be triggered immediately on trade open candle.
# Test 36: trailing_stop should be triggered immediately on trade open candle.
# stop-loss: 1%, ROI: 10% (should not apply)
tc32 = BTContainer(data=[
# D O H L C V B S
tc36 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # enter trade (signal on last candle) and stop
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # enter trade and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
)
# Test 33: trailing_stop should be triggered immediately on trade open candle.
# Test 37: trailing_stop should be triggered immediately on trade open candle.
# stop-loss: 1%, ROI: 10% (should not apply)
tc33 = BTContainer(data=[
# D O H L C V B S BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 'buy_signal_01'],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0, None], # enter trade (signal on last candle) and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, None],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, None],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, None]],
tc37 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 0, 0, 'buy_signal_01'],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0, 0, 0, None], # enter trade and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, 0, 0, None],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0, None],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0, None]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade(
sell_reason=SellType.TRAILING_STOP_LOSS,
sell_reason=ExitType.TRAILING_STOP_LOSS,
open_tick=1,
close_tick=1,
buy_tag='buy_signal_01'
enter_tag='buy_signal_01'
)]
)
# Test 38: trailing_stop should be triggered immediately on trade open candle.
# copy of Test37 using shorts.
# stop-loss: 1%, ROI: 10% (should not apply)
tc38 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0, 'short_signal_01'],
[1, 5000, 5049, 4500, 5000, 6172, 0, 0, 0, 0, None], # enter trade and stop
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, 0, 0, None],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0, None],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0, None]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade(
sell_reason=ExitType.TRAILING_STOP_LOSS,
open_tick=1,
close_tick=1,
enter_tag='short_signal_01',
is_short=True,
)]
)
# Test 34: Custom-entry-price below all candles should timeout - so no trade happens.
tc34 = BTContainer(data=[
# D O H L C V B S
# Test 39: Custom-entry-price below all candles should timeout - so no trade happens.
tc39 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # timeout
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
@@ -547,9 +637,9 @@ tc34 = BTContainer(data=[
custom_entry_price=4200, trades=[]
)
# Test 35: Custom-entry-price above all candles should have rate adjusted to "entry candle high"
tc35 = BTContainer(data=[
# D O H L C V B S
# Test 40: Custom-entry-price above all candles should have rate adjusted to "entry candle high"
tc40 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # Timeout
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
@@ -557,16 +647,30 @@ tc35 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01,
custom_entry_price=7200, trades=[
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)
BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)
])
# Test 41: Custom-entry-price above all candles should have rate adjusted to "entry candle high"
tc41 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0, 0, 0], # Timeout
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, 0, 0],
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01,
custom_entry_price=4000,
trades=[
BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1, is_short=True)
]
)
# Test 36: Custom-entry-price around candle low
# Test 42: Custom-entry-price around candle low
# Would cause immediate ROI exit, but since the trade was entered
# below open, we treat this as cheating, and delay the sell by 1 candle.
# details: https://github.com/freqtrade/freqtrade/issues/6261
tc36 = BTContainer(data=[
# D O H L C V B S BT
tc42 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 4999, 6172, 0, 0], # Enter and immediate ROI
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
@@ -574,14 +678,14 @@ tc36 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
custom_entry_price=4952,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)]
)
# Test 37: Custom-entry-price around candle low
# Test 43: Custom-entry-price around candle low
# Would cause immediate ROI exit below close
# details: https://github.com/freqtrade/freqtrade/issues/6261
tc37 = BTContainer(data=[
# D O H L C V B S BT
tc43 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5400, 5500, 4951, 5100, 6172, 0, 0], # Enter and immediate ROI
[2, 4900, 5250, 4500, 5100, 6172, 0, 0],
@@ -589,13 +693,13 @@ tc37 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
custom_entry_price=4952,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)]
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=1)]
)
# Test 38: Custom exit price below all candles
# Test 44: Custom exit price below all candles
# Price adjusted to candle Low.
tc38 = BTContainer(data=[
# D O H L C V B S BT
tc44 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0],
[2, 4900, 5250, 4900, 5100, 6172, 0, 1], # exit - but timeout
@@ -604,22 +708,50 @@ tc38 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01,
use_sell_signal=True,
custom_exit_price=4552,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=3)]
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=3)]
)
# Test 39: Custom exit price above all candles
# Test 45: Custom exit price above all candles
# causes sell signal timeout
tc39 = BTContainer(data=[
# D O H L C V B S BT
tc45 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0],
[2, 4900, 5250, 4900, 5100, 6172, 0, 1], # exit - but timeout
[2, 4950, 5250, 4900, 5100, 6172, 0, 1], # exit - entry timeout
[3, 5100, 5100, 4950, 4950, 6172, 0, 0],
[4, 5000, 5100, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
use_sell_signal=True,
custom_exit_price=6052,
trades=[BTrade(sell_reason=SellType.FORCE_SELL, open_tick=1, close_tick=4)]
trades=[BTrade(sell_reason=ExitType.FORCE_SELL, open_tick=1, close_tick=4)]
)
# Test 46: (Short of tc45) Custom short exit price above below candles
# causes sell signal timeout
tc46 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
[1, 5000, 5000, 4951, 5000, 6172, 0, 0, 0, 0],
[2, 4910, 5150, 4910, 5100, 6172, 0, 0, 0, 1], # exit - entry timeout
[3, 5100, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
[4, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
use_sell_signal=True,
custom_exit_price=4700,
trades=[BTrade(sell_reason=ExitType.FORCE_SELL, open_tick=1, close_tick=4, is_short=True)]
)
# Test 47: Colliding long and short signal
tc47 = BTContainer(data=[
# D O H L C V EL XL ES Xs BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0, 0, 0],
[2, 4900, 5250, 4900, 5100, 6172, 0, 0, 0, 0],
[3, 5100, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
[4, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
use_sell_signal=True,
trades=[]
)
@@ -664,6 +796,14 @@ TESTS = [
tc37,
tc38,
tc39,
tc40,
tc41,
tc42,
tc43,
tc44,
tc45,
tc46,
tc47,
]
@@ -685,18 +825,23 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0)
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'))
mocker.patch("freqtrade.exchange.Binance.get_max_leverage", return_value=100)
patch_exchange(mocker)
frame = _build_backtest_dataframe(data.data)
backtesting = Backtesting(default_conf)
# TODO: Should we initialize this properly??
backtesting._can_short = True
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.required_startup = 0
backtesting.strategy.advise_buy = lambda a, m: frame
backtesting.strategy.advise_sell = lambda a, m: frame
backtesting.strategy.advise_entry = lambda a, m: frame
backtesting.strategy.advise_exit = lambda a, m: frame
if data.custom_entry_price:
backtesting.strategy.custom_entry_price = MagicMock(return_value=data.custom_entry_price)
if data.custom_exit_price:
backtesting.strategy.custom_exit_price = MagicMock(return_value=data.custom_exit_price)
backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss
backtesting.strategy.leverage = lambda **kwargs: data.leverage
caplog.set_level(logging.DEBUG)
pair = "UNITTEST/BTC"
@@ -715,8 +860,9 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3)
for c, trade in enumerate(data.trades):
res = results.iloc[c]
res: BTrade = results.iloc[c]
assert res.sell_reason == trade.sell_reason.value
assert res.buy_tag == trade.buy_tag
assert res.enter_tag == trade.enter_tag
assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick)
assert res.is_short == trade.is_short

View File

@@ -19,27 +19,27 @@ from freqtrade.data.btanalysis import BT_DATA_COLUMNS, evaluate_result_multi
from freqtrade.data.converter import clean_ohlcv_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import get_timerange
from freqtrade.enums import RunMode, SellType
from freqtrade.enums import ExitType, RunMode
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.exchange.exchange import timeframe_to_next_date
from freqtrade.misc import get_strategy_run_id
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence import LocalTrade
from freqtrade.resolvers import StrategyResolver
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
ORDER_TYPES = [
{
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
},
{
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}]
@@ -134,12 +134,14 @@ def _trend(signals, buy_value, sell_value):
n = len(signals['low'])
buy = np.zeros(n)
sell = np.zeros(n)
for i in range(0, len(signals['buy'])):
for i in range(0, len(signals['date'])):
if random.random() > 0.5: # Both buy and sell signals at same timeframe
buy[i] = buy_value
sell[i] = sell_value
signals['buy'] = buy
signals['sell'] = sell
signals['enter_long'] = buy
signals['exit_long'] = sell
signals['enter_short'] = 0
signals['exit_short'] = 0
return signals
@@ -154,8 +156,10 @@ def _trend_alternate(dataframe=None, metadata=None):
buy[i] = 1
else:
sell[i] = 1
signals['buy'] = buy
signals['sell'] = sell
signals['enter_long'] = buy
signals['exit_long'] = sell
signals['enter_short'] = 0
signals['exit_short'] = 0
return dataframe
@@ -166,7 +170,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--export', 'none'
]
@@ -201,7 +205,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar',
'--timeframe', '1m',
'--enable-position-stacking',
@@ -251,7 +255,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1',
'--starting-balance', '2'
]
@@ -262,7 +266,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1',
'--starting-balance', '0.5'
]
@@ -280,7 +284,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
pargs = get_args(args)
start_backtesting(pargs)
@@ -302,8 +306,8 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
assert backtesting.config == default_conf
assert backtesting.timeframe == '5m'
assert callable(backtesting.strategy.advise_all_indicators)
assert callable(backtesting.strategy.advise_buy)
assert callable(backtesting.strategy.advise_sell)
assert callable(backtesting.strategy.advise_entry)
assert callable(backtesting.strategy.advise_exit)
assert isinstance(backtesting.strategy.dp, DataProvider)
get_fee.assert_called()
assert backtesting.fee == 0.5
@@ -313,7 +317,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
patch_exchange(mocker)
del default_conf['timeframe']
default_conf['strategy_list'] = ['StrategyTestV2',
default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY,
'HyperoptableStrategy']
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
@@ -350,7 +354,6 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
assert len(processed['UNITTEST/BTC']) == 102
# Load strategy to compare the result between Backtesting function and strategy are the same
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
processed2 = strategy.advise_all_indicators(data)
@@ -494,7 +497,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
Backtesting(default_conf)
# Multiple strategies
default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1']
default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'TestStrategyLegacyV1']
with pytest.raises(OperationalException,
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
Backtesting(default_conf)
@@ -504,6 +507,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
default_conf['use_sell_signal'] = False
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'))
patch_exchange(mocker)
default_conf['stake_amount'] = 'unlimited'
default_conf['max_open_trades'] = 2
@@ -520,7 +524,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
0.0012, # High
'', # Buy Signal Name
]
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert isinstance(trade, LocalTrade)
assert trade.stake_amount == 495
@@ -528,35 +532,115 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
LocalTrade.trades_open.append(trade)
LocalTrade.trades_open.append(trade)
backtesting.wallets.update()
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade is None
LocalTrade.trades_open.pop()
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade is not None
backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5
backtesting.wallets.update()
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade
assert trade.stake_amount == 123.5
# In case of error - use proposed stake
backtesting.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade
assert trade.stake_amount == 495
assert trade.is_short is False
trade = backtesting._enter_trade(pair, row=row, direction='short')
assert trade
assert trade.stake_amount == 495
assert trade.is_short is True
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=300.0)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade
assert trade.stake_amount == 300.0
def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None:
default_conf_usdt['use_sell_signal'] = False
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'))
mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=100)
patch_exchange(mocker)
default_conf_usdt['stake_amount'] = 300
default_conf_usdt['max_open_trades'] = 2
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
default_conf_usdt['stake_currency'] = 'USDT'
default_conf_usdt['exchange']['pair_whitelist'] = ['.*']
backtesting = Backtesting(default_conf_usdt)
backtesting._set_strategy(backtesting.strategylist[0])
pair = 'UNITTEST/USDT:USDT'
row = [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
0.001, # Open
0.0012, # High
0.00099, # Low
0.0011, # Close
1, # enter_long
0, # exit_long
1, # enter_short
0, # exit_hsort
'', # Long Signal Name
'', # Short Signal Name
'', # Exit Signal Name
]
backtesting.strategy.leverage = MagicMock(return_value=5.0)
mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt",
return_value=(0.01, 0.01))
# leverage = 5
# ep1(trade.open_rate) = 0.001
# position(trade.amount) = 1500000
# stake_amount = 300 -> wb = 300 / 5 = 60
# mmr = 0.01
# cum_b = 0.01
# side_1: -1 if is_short else 1
# liq_buffer = 0.05
#
# Binance, Long
# liquidation_price
# = ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
# = ((300 + 0.01) - (1 * 1500000 * 0.001)) / ((1500000 * 0.01) - (1 * 1500000))
# = 0.0008080740740740741
# freqtrade_liquidation_price = liq + (abs(open_rate - liq) * liq_buffer * side_1)
# = 0.0008080740740740741 + ((0.001 - 0.0008080740740740741) * 0.05 * 1)
# = 0.0008176703703703704
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert pytest.approx(trade.liquidation_price) == 0.00081767037
# Binance, Short
# liquidation_price
# = ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
# = ((300 + 0.01) - ((-1) * 1500000 * 0.001)) / ((1500000 * 0.01) - ((-1) * 1500000))
# = 0.0011881254125412541
# freqtrade_liquidation_price = liq + (abs(open_rate - liq) * liq_buffer * side_1)
# = 0.0011881254125412541 + (abs(0.001 - 0.0011881254125412541) * 0.05 * -1)
# = 0.0011787191419141915
trade = backtesting._enter_trade(pair, row=row, direction='short')
assert pytest.approx(trade.liquidation_price) == 0.0011787191
# Stake-amount too high!
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0)
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade is None
# Stake-amount throwing error
mocker.patch("freqtrade.wallets.Wallets.get_trade_stake_amount",
side_effect=DependencyException)
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade is None
@@ -564,6 +648,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
default_conf['use_sell_signal'] = False
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'))
patch_exchange(mocker)
default_conf['timeframe_detail'] = '1m'
default_conf['max_open_trades'] = 2
@@ -572,63 +657,72 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
pair = 'UNITTEST/BTC'
row = [
pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=55, tzinfo=timezone.utc),
1, # Buy
200, # Open
201, # Close
0, # Sell
195, # Low
201.5, # High
'', # Buy Signal Name
195, # Low
201, # Close
1, # enter_long
0, # exit_long
0, # enter_short
0, # exit_hsort
'', # Long Signal Name
'', # Short Signal Name
'', # Exit Signal Name
]
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert isinstance(trade, LocalTrade)
row_sell = [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc),
0, # Buy
200, # Open
201, # Close
0, # Sell
195, # Low
210.5, # High
'', # Buy Signal Name
195, # Low
201, # Close
0, # enter_long
0, # exit_long
0, # enter_short
0, # exit_short
'', # long Signal Name
'', # Short Signal Name
'', # Exit Signal Name
]
row_detail = pd.DataFrame(
[
[
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc),
1, 200, 199, 0, 197, 200.1, '', '',
200, 200.1, 197, 199, 1, 0, 0, 0, '', '', '',
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=1, tzinfo=timezone.utc),
0, 199, 199.5, 0, 199, 199.7, '', '',
199, 199.7, 199, 199.5, 0, 0, 0, 0, '', '', '',
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=2, tzinfo=timezone.utc),
0, 199.5, 200.5, 0, 199, 200.8, '', '',
199.5, 200.8, 199, 200.9, 0, 0, 0, 0, '', '', '',
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=3, tzinfo=timezone.utc),
0, 200.5, 210.5, 0, 193, 210.5, '', '', # ROI sell (?)
200.5, 210.5, 193, 210.5, 0, 0, 0, 0, '', '', '', # ROI sell (?)
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=4, tzinfo=timezone.utc),
0, 200, 199, 0, 193, 200.1, '', '',
200, 200.1, 193, 199, 0, 0, 0, 0, '', '', '',
],
], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag", "exit_tag"]
], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag']
)
# No data available.
res = backtesting._get_sell_trade_entry(trade, row_sell)
assert res is not None
assert res.sell_reason == SellType.ROI.value
assert res.sell_reason == ExitType.ROI.value
assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc)
# Enter new trade
trade = backtesting._enter_trade(pair, row=row)
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert isinstance(trade, LocalTrade)
# Assign empty ... no result.
backtesting.detail_data[pair] = pd.DataFrame(
[], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag", "exit_tag"])
[], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag'])
res = backtesting._get_sell_trade_entry(trade, row)
assert res is None
@@ -638,7 +732,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
res = backtesting._get_sell_trade_entry(trade, row_sell)
assert res is not None
assert res.sell_reason == SellType.ROI.value
assert res.sell_reason == ExitType.ROI.value
# Sell at minute 3 (not available above!)
assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc)
sell_order = res.select_order('sell', True)
@@ -649,6 +743,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_sell_signal'] = False
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'))
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
@@ -686,7 +781,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'trade_duration': [235, 40],
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'sell_reason': [SellType.ROI.value, SellType.ROI.value],
'sell_reason': [ExitType.ROI.value, ExitType.ROI.value],
'initial_stop_loss_abs': [0.0940005, 0.09272236],
'initial_stop_loss_ratio': [-0.1, -0.1],
'stop_loss_abs': [0.0940005, 0.09272236],
@@ -694,7 +789,8 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'min_rate': [0.10370188, 0.10300000000000001],
'max_rate': [0.10501, 0.1038888],
'is_open': [False, False],
'buy_tag': [None, None]
'enter_tag': [None, None],
"is_short": [False, False],
})
pd.testing.assert_frame_equal(results, expected)
data_pair = processed[pair]
@@ -714,6 +810,7 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
default_conf['use_sell_signal'] = False
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'))
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
@@ -735,6 +832,36 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
assert len(results['results']) == 1
def test_backtest_trim_no_data_left(default_conf, fee, mocker, testdatadir) -> None:
default_conf['use_sell_signal'] = False
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'))
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
timerange = TimeRange('date', None, 1517227800, 0)
backtesting.required_startup = 100
backtesting.timerange = timerange
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
df = data['UNITTEST/BTC']
df.loc[:, 'date'] = df.loc[:, 'date'] - timedelta(days=1)
# Trimming 100 candles, so after 2nd trimming, no candle is left.
df = df.iloc[:100]
data['XRP/USDT'] = df
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
backtesting.backtest(
processed=deepcopy(processed),
start_date=min_date,
end_date=max_date,
max_open_trades=10,
position_stacking=False,
)
def test_processed(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
@@ -754,6 +881,7 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
default_conf['use_sell_signal'] = False
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=100000)
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
@@ -770,7 +898,7 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
dp = backtesting.strategy.dp
df, _ = dp.get_analyzed_dataframe(pair, backtesting.strategy.timeframe)
current_candle = df.iloc[-1].squeeze()
assert current_candle['buy'] == 1
assert current_candle['enter_long'] == 1
candle_date = timeframe_to_next_date(backtesting.strategy.timeframe, current_candle['date'])
assert candle_date == current_time
@@ -802,6 +930,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
default_conf['enable_protections'] = True
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'))
tests = [
['sine', 9],
['raise', 10],
@@ -809,7 +938,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
['sine', 9],
['raise', 10],
]
# While buy-signals are unrealistic, running backtesting
# 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
@@ -836,14 +965,15 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
default_conf['enable_protections'] = True
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'))
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
# While buy-signals are unrealistic, running backtesting
# 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
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
# Override the default buy trend function in our StrategyTestV2
# Override the default buy trend function in our StrategyTest
def fun(dataframe=None, pair=None):
buy_value = 1
sell_value = 1
@@ -852,14 +982,14 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override
backtesting.strategy.advise_entry = fun # Override
backtesting.strategy.advise_exit = fun # Override
result = backtesting.backtest(**backtest_conf)
assert result['results'].empty
def test_backtest_only_sell(mocker, default_conf, testdatadir):
# Override the default buy trend function in our StrategyTestV2
# Override the default buy trend function in our StrategyTest
def fun(dataframe=None, pair=None):
buy_value = 0
sell_value = 1
@@ -868,14 +998,15 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir):
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override
backtesting.strategy.advise_entry = fun # Override
backtesting.strategy.advise_exit = fun # Override
result = backtesting.backtest(**backtest_conf)
assert result['results'].empty
def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
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'))
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
pair='UNITTEST/BTC', datadir=testdatadir)
@@ -883,8 +1014,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
backtesting = Backtesting(default_conf)
backtesting.required_startup = 0
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate # Override
backtesting.strategy.advise_sell = _trend_alternate # Override
backtesting.strategy.advise_entry = _trend_alternate # Override
backtesting.strategy.advise_exit = _trend_alternate # Override
result = backtesting.backtest(**backtest_conf)
# 200 candles in backtest data
# won't buy on first (shifted by 1)
@@ -915,11 +1046,14 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
multi = 20
else:
multi = 18
dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
dataframe['enter_long'] = np.where(dataframe.index % multi == 0, 1, 0)
dataframe['exit_long'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
dataframe['enter_short'] = 0
dataframe['exit_short'] = 0
return dataframe
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'))
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker)
@@ -935,8 +1069,8 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate_hold # Override
backtesting.strategy.advise_sell = _trend_alternate_hold # Override
backtesting.strategy.advise_entry = _trend_alternate_hold # Override
backtesting.strategy.advise_exit = _trend_alternate_hold # Override
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
@@ -959,8 +1093,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
offset = 1 if tres == 0 else 0
removed_candles = len(data[pair]) - offset - backtesting.strategy.startup_candle_count
assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, '5m')[0]) == removed_candles
assert len(backtesting.dataprovider.get_analyzed_dataframe(
'NXT/BTC', '5m')[0]) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count
assert len(
backtesting.dataprovider.get_analyzed_dataframe('NXT/BTC', '5m')[0]
) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count
backtest_conf = {
'processed': deepcopy(processed),
@@ -986,7 +1121,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', str(testdatadir),
'--timeframe', '1m',
'--timerange', '1510694220-1510700340',
@@ -1059,7 +1194,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'--enable-position-stacking',
'--disable-max-market-positions',
'--strategy-list',
'StrategyTestV2',
CURRENT_TEST_STRATEGY,
'TestStrategyLegacyV1',
]
args = get_args(args)
@@ -1082,7 +1217,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'Backtesting with data from 2017-11-14 21:17:00 '
'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy StrategyTestV2',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
'Running backtesting for Strategy TestStrategyLegacyV1',
]
@@ -1112,7 +1247,9 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
'sell_reason': [SellType.ROI, SellType.ROI]
"is_short": [False, False],
'sell_reason': [ExitType.ROI, ExitType.ROI]
})
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1],
@@ -1129,7 +1266,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
"is_short": [False, False, False],
'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
})
backtestmock = MagicMock(side_effect=[
{
@@ -1168,7 +1306,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'--disable-max-market-positions',
'--breakdown', 'day',
'--strategy-list',
'StrategyTestV2',
CURRENT_TEST_STRATEGY,
'TestStrategyLegacyV1',
]
args = get_args(args)
@@ -1185,7 +1323,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'Backtesting with data from 2017-11-14 21:17:00 '
'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy StrategyTestV2',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
'Running backtesting for Strategy TestStrategyLegacyV1',
]
@@ -1194,13 +1332,119 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
captured = capsys.readouterr()
assert 'BACKTESTING REPORT' in captured.out
assert 'SELL REASON STATS' in captured.out
assert 'EXIT REASON STATS' in captured.out
assert 'DAY BREAKDOWN' in captured.out
assert 'LEFT OPEN TRADES REPORT' in captured.out
assert '2017-11-14 21:17:00 -> 2017-11-14 22:58:00 | Max open trades : 1' in captured.out
assert 'STRATEGY SUMMARY' in captured.out
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
caplog, testdatadir, capsys):
# Tests detail-data loading
default_conf_usdt.update({
"trading_mode": "futures",
"margin_mode": "isolated",
"use_sell_signal": True,
"sell_profit_only": False,
"sell_profit_offset": 0.0,
"ignore_roi_if_buy_signal": False,
"strategy": CURRENT_TEST_STRATEGY,
})
patch_exchange(mocker)
result1 = pd.DataFrame({'pair': ['XRP/USDT', 'XRP/USDT'],
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'open_date': pd.to_datetime(['2021-11-18 18:00:00',
'2021-11-18 03:00:00', ], utc=True
),
'close_date': pd.to_datetime(['2021-11-18 20:00:00',
'2021-11-18 05:00:00', ], utc=True),
'trade_duration': [235, 40],
'is_open': [False, False],
'is_short': [False, False],
'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
'sell_reason': [ExitType.ROI, ExitType.ROI]
})
result2 = pd.DataFrame({'pair': ['XRP/USDT', 'XRP/USDT', 'XRP/USDT'],
'profit_ratio': [0.03, 0.01, 0.1],
'profit_abs': [0.01, 0.02, 0.2],
'open_date': pd.to_datetime(['2021-11-19 18:00:00',
'2021-11-19 03:00:00',
'2021-11-19 05:00:00'], utc=True
),
'close_date': pd.to_datetime(['2021-11-19 20:00:00',
'2021-11-19 05:00:00',
'2021-11-19 08:00:00'], utc=True),
'trade_duration': [47, 40, 20],
'is_open': [False, False, False],
'is_short': [False, False, False],
'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
})
backtestmock = MagicMock(side_effect=[
{
'results': result1,
'config': default_conf_usdt,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
},
{
'results': result2,
'config': default_conf_usdt,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
}
])
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['XRP/USDT']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
patched_configuration_load_config_file(mocker, default_conf_usdt)
args = [
'backtesting',
'--config', 'config.json',
'--datadir', str(testdatadir),
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
'--timeframe', '1h',
]
args = get_args(args)
start_backtesting(args)
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--timeframe detected ... Using timeframe: 1h ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2021-11-17 01:00:00 '
'up to 2021-11-21 03:00:00 (4 days).',
'Backtesting with data from 2021-11-17 21:00:00 '
'up to 2021-11-21 03:00:00 (3 days).',
'XRP/USDT, funding_rate, 8h, data starts at 2021-11-18 00:00:00',
'XRP/USDT, mark, 8h, data starts at 2021-11-18 00:00:00',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
]
for line in exists:
assert log_has(line, caplog)
captured = capsys.readouterr()
assert 'BACKTESTING REPORT' in captured.out
assert 'EXIT REASON STATS' in captured.out
assert 'LEFT OPEN TRADES REPORT' in captured.out
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
caplog, testdatadir, capsys):
@@ -1222,10 +1466,11 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'2018-01-30 05:35:00', ], utc=True),
'trade_duration': [235, 40],
'is_open': [False, False],
'is_short': [False, False],
'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
'sell_reason': [SellType.ROI, SellType.ROI]
'sell_reason': [ExitType.ROI, ExitType.ROI]
})
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1],
@@ -1239,10 +1484,11 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'2018-01-30 08:30:00'], utc=True),
'trade_duration': [47, 40, 20],
'is_open': [False, False, False],
'is_short': [False, False, False],
'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
})
backtestmock = MagicMock(side_effect=[
{
@@ -1278,7 +1524,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'--timeframe', '5m',
'--timeframe-detail', '1m',
'--strategy-list',
'StrategyTestV2'
CURRENT_TEST_STRATEGY
]
args = get_args(args)
start_backtesting(args)
@@ -1292,7 +1538,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'up to 2019-10-13 11:10:00 (2 days).',
'Backtesting with data from 2019-10-11 01:40:00 '
'up to 2019-10-13 11:10:00 (2 days).',
'Running backtesting for Strategy StrategyTestV2',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
]
for line in exists:
@@ -1300,7 +1546,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
captured = capsys.readouterr()
assert 'BACKTESTING REPORT' in captured.out
assert 'SELL REASON STATS' in captured.out
assert 'EXIT REASON STATS' in captured.out
assert 'LEFT OPEN TRADES REPORT' in captured.out

View File

@@ -8,7 +8,7 @@ from arrow import Arrow
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.history import get_timerange
from freqtrade.enums import SellType
from freqtrade.enums import ExitType
from freqtrade.optimize.backtesting import Backtesting
from tests.conftest import patch_exchange
@@ -17,6 +17,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
default_conf['use_sell_signal'] = False
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'))
patch_exchange(mocker)
default_conf.update({
"stake_amount": 100.0,
@@ -59,7 +60,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
'trade_duration': [200, 40],
'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'sell_reason': [SellType.ROI.value, SellType.ROI.value],
'sell_reason': [ExitType.ROI.value, ExitType.ROI.value],
'initial_stop_loss_abs': [0.0940005, 0.09272236],
'initial_stop_loss_ratio': [-0.1, -0.1],
'stop_loss_abs': [0.0940005, 0.09272236],
@@ -67,7 +68,8 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
'min_rate': [0.10370188, 0.10300000000000001],
'max_rate': [0.10481985, 0.1038888],
'is_open': [False, False],
'buy_tag': [None, None],
'enter_tag': [None, None],
'is_short': [False, False],
})
pd.testing.assert_frame_equal(results, expected)
data_pair = processed[pair]

View File

@@ -6,7 +6,8 @@ from unittest.mock import MagicMock
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge
from freqtrade.enums import RunMode
from freqtrade.optimize.edge_cli import EdgeCli
from tests.conftest import get_args, log_has, patch_exchange, patched_configuration_load_config_file
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, patch_exchange,
patched_configuration_load_config_file)
def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None:
@@ -15,7 +16,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
args = [
'edge',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
config = setup_optimize_configuration(get_args(args), RunMode.EDGE)
@@ -44,7 +45,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N
args = [
'edge',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar',
'--timeframe', '1m',
'--timerange', ':100',
@@ -78,7 +79,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
args = [
'edge',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
pargs = get_args(args)
start_edge(pargs)

View File

@@ -10,7 +10,7 @@ from filelock import Timeout
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt
from freqtrade.data.history import load_data
from freqtrade.enums import RunMode, SellType
from freqtrade.enums import ExitType, RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
@@ -18,31 +18,31 @@ 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.hyper import IntParameter
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
def generate_result_metrics():
return {
'trade_count': 1,
'total_trades': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 0.01,
'duration': 20.0,
'wins': 1,
'draws': 0,
'losses': 0,
'profit_mean': 0.01,
'profit_total_abs': 0.001,
'profit_total': 0.01,
'holding_avg': timedelta(minutes=20),
'max_drawdown': 0.001,
'max_drawdown_abs': 0.001,
'loss': 0.001,
'is_initial_point': 0.001,
'is_best': 1,
}
'trade_count': 1,
'total_trades': 1,
'avg_profit': 0.1,
'total_profit': 0.001,
'profit': 0.01,
'duration': 20.0,
'wins': 1,
'draws': 0,
'losses': 0,
'profit_mean': 0.01,
'profit_total_abs': 0.001,
'profit_total': 0.01,
'holding_avg': timedelta(minutes=20),
'max_drawdown': 0.001,
'max_drawdown_abs': 0.001,
'loss': 0.001,
'is_initial_point': 0.001,
'is_best': 1,
}
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
@@ -144,7 +144,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
args = [
'hyperopt',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1',
'--starting-balance', '0.5'
]
@@ -329,8 +329,8 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
# Should be called for historical candle data
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
@@ -355,9 +355,10 @@ def test_hyperopt_format_results(hyperopt):
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
"sell_reason": [ExitType.ROI, ExitType.STOP_LOSS,
ExitType.ROI, ExitType.FORCE_SELL]
}),
'config': hyperopt.config,
'locks': [],
@@ -425,9 +426,10 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
"sell_reason": [ExitType.ROI, ExitType.STOP_LOSS,
ExitType.ROI, ExitType.FORCE_SELL]
}),
'config': hyperopt_conf,
'locks': [],
@@ -686,8 +688,8 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
@@ -759,8 +761,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
assert dumper.called
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
@@ -801,8 +803,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
assert dumper.called
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
@@ -849,6 +851,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
'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)

View File

@@ -10,7 +10,7 @@ import rapidjson
from freqtrade.constants import FTHYPT_FILEVERSION
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer
from tests.conftest import log_has, log_has_re
from tests.conftest import CURRENT_TEST_STRATEGY, log_has, log_has_re
# Functions for recurrent object patching
@@ -168,9 +168,9 @@ def test__pprint_dict():
def test_get_strategy_filename(default_conf):
x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV2')
x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV3')
assert isinstance(x, Path)
assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v2.py'
assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v3.py'
x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy')
assert x is None
@@ -178,7 +178,7 @@ def test_get_strategy_filename(default_conf):
def test_export_params(tmpdir):
filename = Path(tmpdir) / "StrategyTestV2.json"
filename = Path(tmpdir) / f"{CURRENT_TEST_STRATEGY}.json"
assert not filename.is_file()
params = {
"params_details": {
@@ -206,13 +206,13 @@ def test_export_params(tmpdir):
}
}
HyperoptTools.export_params(params, "StrategyTestV2", filename)
HyperoptTools.export_params(params, CURRENT_TEST_STRATEGY, filename)
assert filename.is_file()
with filename.open('r') as f:
content = rapidjson.load(f)
assert content['strategy_name'] == 'StrategyTestV2'
assert content['strategy_name'] == CURRENT_TEST_STRATEGY
assert 'params' in content
assert "buy" in content["params"]
assert "sell" in content["params"]
@@ -225,7 +225,7 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
default_conf['disableparamexport'] = False
export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params")
filename = Path(tmpdir) / "StrategyTestV2.json"
filename = Path(tmpdir) / f"{CURRENT_TEST_STRATEGY}.json"
assert not filename.is_file()
params = {
"params_details": {
@@ -254,17 +254,17 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
FTHYPT_FILEVERSION: 2,
}
HyperoptTools.try_export_params(default_conf, "StrategyTestV222", params)
HyperoptTools.try_export_params(default_conf, "StrategyTestVXXX", params)
assert log_has("Strategy not found, not exporting parameter file.", caplog)
assert export_mock.call_count == 0
caplog.clear()
HyperoptTools.try_export_params(default_conf, "StrategyTestV2", params)
HyperoptTools.try_export_params(default_conf, CURRENT_TEST_STRATEGY, params)
assert export_mock.call_count == 1
assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2'
assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json'
assert export_mock.call_args_list[0][0][1] == CURRENT_TEST_STRATEGY
assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v3.json'
def test_params_print(capsys):

View File

@@ -12,7 +12,7 @@ from freqtrade.data import history
from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data,
load_backtest_stats)
from freqtrade.edge import PairInfo
from freqtrade.enums import SellType
from freqtrade.enums import ExitType
from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats,
generate_daily_stats, generate_edge_table,
generate_pair_metrics,
@@ -21,8 +21,9 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene
generate_strategy_comparison,
generate_trading_stats, show_sorted_pairlist,
store_backtest_stats, text_table_bt_results,
text_table_sell_reason, text_table_strategy)
text_table_exit_reason, text_table_strategy)
from freqtrade.resolvers.strategy_resolver import StrategyResolver
from tests.conftest import CURRENT_TEST_STRATEGY
from tests.data.test_history import _backup_file, _clean_test_file
@@ -54,7 +55,7 @@ def test_text_table_bt_results():
def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
default_conf.update({'strategy': 'StrategyTestV2'})
default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
StrategyResolver.load_strategy(default_conf)
results = {'DefStrat': {
@@ -74,9 +75,10 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL]
"sell_reason": [ExitType.ROI, ExitType.STOP_LOSS,
ExitType.ROI, ExitType.FORCE_SELL]
}),
'config': default_conf,
'locks': [],
@@ -125,9 +127,10 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
"close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
"trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.ROI,
SellType.STOP_LOSS, SellType.FORCE_SELL]
"sell_reason": [ExitType.ROI, ExitType.ROI,
ExitType.STOP_LOSS, ExitType.FORCE_SELL]
}),
'config': default_conf,
'locks': [],
@@ -273,12 +276,12 @@ def test_text_table_sell_reason():
'wins': [2, 0, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
}
)
result_str = (
'| Sell Reason | Sells | Win Draws Loss Win% | Avg Profit % | Cum Profit % |'
'| Exit Reason | Exits | Win Draws Loss Win% | Avg Profit % | Cum Profit % |'
' Tot Profit BTC | Tot Profit % |\n'
'|---------------+---------+--------------------------+----------------+----------------+'
'------------------+----------------|\n'
@@ -290,7 +293,7 @@ def test_text_table_sell_reason():
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
results=results)
assert text_table_sell_reason(sell_reason_stats=sell_reason_stats,
assert text_table_exit_reason(sell_reason_stats=sell_reason_stats,
stake_currency='BTC') == result_str
@@ -305,7 +308,7 @@ def test_generate_sell_reason_stats():
'wins': [2, 0, 0],
'draws': [0, 0, 0],
'losses': [0, 0, 1],
'sell_reason': [SellType.ROI.value, SellType.ROI.value, SellType.STOP_LOSS.value]
'sell_reason': [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value]
}
)
@@ -397,6 +400,6 @@ def test_show_sorted_pairlist(testdatadir, default_conf, capsys):
show_sorted_pairlist(default_conf, bt_data)
out, err = capsys.readouterr()
assert 'Pairs for Strategy StrategyTestV2: \n[' in out
assert 'Pairs for Strategy StrategyTestV3: \n[' in out
assert 'TOTAL' not in out
assert '"ETH/BTC", // ' in out

View File

@@ -9,7 +9,7 @@ import pytest
import time_machine
from freqtrade.constants import AVAILABLE_PAIRLISTS
from freqtrade.enums import RunMode
from freqtrade.enums import CandleType, RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.persistence import Trade
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
@@ -491,11 +491,11 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
ohlcv_history_high_vola.loc[ohlcv_history_high_vola.index == 1, 'close'] = 0.00090
ohlcv_data = {
('ETH/BTC', '1d'): ohlcv_history,
('TKN/BTC', '1d'): ohlcv_history,
('LTC/BTC', '1d'): pd.concat([ohlcv_history, ohlcv_history]),
('XRP/BTC', '1d'): ohlcv_history,
('HOT/BTC', '1d'): ohlcv_history_high_vola,
('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history,
('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history,
('LTC/BTC', '1d', CandleType.SPOT): pd.concat([ohlcv_history, ohlcv_history]),
('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history,
('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history_high_vola,
}
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
@@ -587,10 +587,10 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
"lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
"BTC", "binance", ['LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC', 'HOT/BTC']),
# expecting pairs from default tickers, because 1h candles are not available
# expecting pairs as input, because 1h candles are not available
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
"lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}],
"BTC", "binance", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']),
"BTC", "binance", ['ETH/BTC', 'LTC/BTC', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']),
# ftx data is already in Quote currency, therefore won't require conversion
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
"lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
@@ -619,11 +619,11 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers,
mocker.patch('freqtrade.exchange.ftx.Ftx.market_is_tradable', return_value=True)
ohlcv_data = {
('ETH/BTC', '1d'): ohlcv_history,
('TKN/BTC', '1d'): ohlcv_history,
('LTC/BTC', '1d'): ohlcv_history_medium_volume,
('XRP/BTC', '1d'): ohlcv_history_high_vola,
('HOT/BTC', '1d'): ohlcv_history_high_volume,
('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history,
('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history,
('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history_medium_volume,
('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history_high_vola,
('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history_high_volume,
}
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
@@ -937,9 +937,9 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick
def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, ohlcv_history):
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
ohlcv_data = {
('ETH/BTC', '1d'): ohlcv_history,
('TKN/BTC', '1d'): ohlcv_history,
('LTC/BTC', '1d'): ohlcv_history,
('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history,
('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history,
('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history,
}
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -961,10 +961,10 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 2
ohlcv_data = {
('ETH/BTC', '1d'): ohlcv_history,
('TKN/BTC', '1d'): ohlcv_history,
('LTC/BTC', '1d'): ohlcv_history,
('XRP/BTC', '1d'): ohlcv_history.iloc[[0]],
('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history,
('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history,
('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history,
('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history.iloc[[0]],
}
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
freqtrade.pairlists.refresh_pairlist()
@@ -982,10 +982,10 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o
t.move_to("2021-09-03 01:00:00 +00:00")
# Called once for XRP/BTC
ohlcv_data = {
('ETH/BTC', '1d'): ohlcv_history,
('TKN/BTC', '1d'): ohlcv_history,
('LTC/BTC', '1d'): ohlcv_history,
('XRP/BTC', '1d'): ohlcv_history,
('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history,
('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history,
('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history,
('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history,
}
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
freqtrade.pairlists.refresh_pairlist()
@@ -1046,12 +1046,12 @@ def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, oh
get_tickers=tickers
)
ohlcv_data = {
('ETH/BTC', '1d'): ohlcv_history,
('TKN/BTC', '1d'): ohlcv_history,
('LTC/BTC', '1d'): ohlcv_history,
('XRP/BTC', '1d'): ohlcv_history,
('HOT/BTC', '1d'): ohlcv_history,
('BLK/BTC', '1d'): ohlcv_history,
('ETH/BTC', '1d', CandleType.SPOT): ohlcv_history,
('TKN/BTC', '1d', CandleType.SPOT): ohlcv_history,
('LTC/BTC', '1d', CandleType.SPOT): ohlcv_history,
('XRP/BTC', '1d', CandleType.SPOT): ohlcv_history,
('HOT/BTC', '1d', CandleType.SPOT): ohlcv_history,
('BLK/BTC', '1d', CandleType.SPOT): ohlcv_history,
}
mocker.patch.multiple(
'freqtrade.exchange.Exchange',

View File

@@ -4,14 +4,14 @@ from datetime import datetime, timedelta
import pytest
from freqtrade import constants
from freqtrade.enums import SellType
from freqtrade.enums import ExitType
from freqtrade.persistence import PairLocks, Trade
from freqtrade.plugins.protectionmanager import ProtectionManager
from tests.conftest import get_patched_freqtradebot, log_has_re
def generate_mock_trade(pair: str, fee: float, is_open: bool,
sell_reason: str = SellType.SELL_SIGNAL,
sell_reason: str = ExitType.SELL_SIGNAL,
min_ago_open: int = None, min_ago_close: int = None,
profit_rate: float = 0.9
):
@@ -91,7 +91,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
caplog.clear()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30,
))
@@ -100,12 +100,12 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
caplog.clear()
# This trade does not count, as it's closed too long ago
Trade.query.session.add(generate_mock_trade(
'BCH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'BCH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100,
))
Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'ETH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30,
))
# 3 Trades closed - but the 2nd has been closed too long ago.
@@ -114,7 +114,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
caplog.clear()
Trade.query.session.add(generate_mock_trade(
'LTC/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'LTC/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30,
))
@@ -148,7 +148,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
caplog.clear()
Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
pair, fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, profit_rate=0.9,
))
@@ -158,12 +158,12 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
caplog.clear()
# This trade does not count, as it's closed too long ago
Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
pair, fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100, profit_rate=0.9,
))
# Trade does not count for per pair stop as it's the wrong pair.
Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'ETH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30, profit_rate=0.9,
))
# 3 Trades closed - but the 2nd has been closed too long ago.
@@ -178,7 +178,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
# 2nd Trade that counts with correct pair
Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
pair, fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30, profit_rate=0.9,
))
@@ -203,7 +203,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
caplog.clear()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30,
))
@@ -213,7 +213,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock()
Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
'ETH/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value,
min_ago_open=205, min_ago_close=35,
))
@@ -242,7 +242,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
caplog.clear()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=800, min_ago_close=450, profit_rate=0.9,
))
@@ -253,7 +253,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=120, profit_rate=0.9,
))
@@ -265,14 +265,14 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
# Add positive trade
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value,
min_ago_open=20, min_ago_close=10, profit_rate=1.15,
))
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
assert not PairLocks.is_pair_locked('XRP/BTC')
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=110, min_ago_close=20, profit_rate=0.8,
))
@@ -300,15 +300,15 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
caplog.clear()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
))
Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'ETH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
))
Trade.query.session.add(generate_mock_trade(
'NEO/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'NEO/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
))
# No losing trade yet ... so max_drawdown will raise exception
@@ -316,7 +316,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=500, min_ago_close=400, profit_rate=0.9,
))
# Not locked with one trade
@@ -326,7 +326,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock()
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=1200, min_ago_close=1100, profit_rate=0.5,
))
@@ -339,7 +339,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
# Winning trade ... (should not lock, does not change drawdown!)
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value,
min_ago_open=320, min_ago_close=410, profit_rate=1.5,
))
assert not freqtrade.protections.global_stop()
@@ -349,7 +349,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
# Add additional negative trade, causing a loss of > 15%
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value,
'XRP/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value,
min_ago_open=20, min_ago_close=10, profit_rate=0.8,
))
assert not freqtrade.protections.stop_per_pair('XRP/BTC')

View File

@@ -137,7 +137,7 @@ def test_fiat_too_many_requests_response(mocker, caplog):
assert len(fiat_convert._coinlistings) == 0
assert fiat_convert._backoff > datetime.datetime.now().timestamp()
assert log_has(
'Too many requests for Coingecko API, backing off and trying again later.',
'Too many requests for CoinGecko API, backing off and trying again later.',
caplog
)
@@ -156,7 +156,7 @@ def test_fiat_multiple_coins(mocker, caplog):
assert fiat_convert._get_gekko_id('hnt') is None
assert fiat_convert._get_gekko_id('eth') == 'ethereum'
assert log_has('Found multiple mappings in goingekko for hnt.', caplog)
assert log_has('Found multiple mappings in CoinGecko for hnt.', caplog)
def test_fiat_invalid_response(mocker, caplog):

View File

@@ -8,7 +8,7 @@ import pytest
from numpy import isnan
from freqtrade.edge import PairInfo
from freqtrade.enums import State
from freqtrade.enums import SignalDirection, State, TradingMode
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
from freqtrade.persistence import Trade
from freqtrade.persistence.models import Order
@@ -71,6 +71,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'max_rate': ANY,
'strategy': ANY,
'buy_tag': ANY,
'enter_tag': ANY,
'timeframe': 5,
'open_order_id': ANY,
'close_date': None,
@@ -109,13 +110,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None,
'exchange': 'binance',
'leverage': 1.0,
'interest_rate': 0.0,
'liquidation_price': None,
'is_short': False,
'funding_fees': 0.0,
'trading_mode': TradingMode.SPOT,
'orders': [{
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY,
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY,
'remaining': ANY, 'status': ANY}],
'remaining': ANY, 'status': ANY, 'ft_is_entry': True,
}],
}
mocker.patch('freqtrade.exchange.Exchange.get_rate',
@@ -145,6 +153,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'max_rate': ANY,
'strategy': ANY,
'buy_tag': ANY,
'enter_tag': ANY,
'timeframe': ANY,
'open_order_id': ANY,
'close_date': None,
@@ -183,13 +192,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None,
'exchange': 'binance',
'leverage': 1.0,
'interest_rate': 0.0,
'liquidation_price': None,
'is_short': False,
'funding_fees': 0.0,
'trading_mode': TradingMode.SPOT,
'orders': [{
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY,
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY,
'remaining': ANY, 'status': ANY}],
'remaining': ANY, 'status': ANY, 'ft_is_entry': True,
}],
}
@@ -304,7 +320,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency)
def test_rpc_trade_history(mocker, default_conf, markets, fee):
@pytest.mark.parametrize('is_short', [True, False])
def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -312,7 +329,7 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee):
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
create_mock_trades(fee)
create_mock_trades(fee, is_short)
rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter()
trades = rpc._rpc_trade_history(2)
@@ -329,7 +346,8 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee):
assert trades['trades'][0]['pair'] == 'XRP/BTC'
def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog):
@pytest.mark.parametrize('is_short', [True, False])
def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
@@ -342,7 +360,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog):
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
freqtradebot.strategy.order_types['stoploss_on_exchange'] = True
create_mock_trades(fee)
create_mock_trades(fee, is_short)
rpc = RPC(freqtradebot)
with pytest.raises(RPCException, match='invalid argument'):
rpc._rpc_delete('200')
@@ -584,6 +602,30 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
'used': 5.0,
}
}
mock_pos = [
{
"symbol": "ETH/USDT:USDT",
"timestamp": None,
"datetime": None,
"initialMargin": 0.0,
"initialMarginPercentage": None,
"maintenanceMargin": 0.0,
"maintenanceMarginPercentage": 0.005,
"entryPrice": 0.0,
"notional": 100.0,
"leverage": 5.0,
"unrealizedPnl": 0.0,
"contracts": 100.0,
"contractSize": 1,
"marginRatio": None,
"liquidationPrice": 0.0,
"markPrice": 2896.41,
"collateral": 20,
"marginType": "isolated",
"side": 'short',
"percentage": None
}
]
mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
@@ -593,47 +635,77 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_trading_mode_and_margin_mode=MagicMock(),
get_balances=MagicMock(return_value=mock_balance),
fetch_positions=MagicMock(return_value=mock_pos),
get_tickers=tickers,
get_valid_pair_combination=MagicMock(
side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}")
)
default_conf['dry_run'] = False
default_conf['trading_mode'] = 'futures'
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter()
result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
assert prec_satoshi(result['total'], 12.30909624)
assert prec_satoshi(result['value'], 184636.443606915)
assert prec_satoshi(result['total'], 30.30909624)
assert prec_satoshi(result['value'], 454636.44360691)
assert tickers.call_count == 1
assert tickers.call_args_list[0][1]['cached'] is True
assert 'USD' == result['symbol']
assert result['currencies'] == [
{'currency': 'BTC',
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'est_stake': 12.0,
'stake': 'BTC',
},
{'free': 1.0,
'balance': 5.0,
'currency': 'ETH',
'est_stake': 0.30794,
'used': 4.0,
'stake': 'BTC',
},
{'free': 5.0,
'balance': 10.0,
'currency': 'USDT',
'est_stake': 0.0011562404610161968,
'used': 5.0,
'stake': 'BTC',
}
{
'currency': 'BTC',
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'est_stake': 10.0, # In futures mode, "free" is used here.
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
},
{
'free': 1.0,
'balance': 5.0,
'currency': 'ETH',
'est_stake': 0.30794,
'used': 4.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
},
{
'free': 5.0,
'balance': 10.0,
'currency': 'USDT',
'est_stake': 0.0011562404610161968,
'used': 5.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
},
{
'free': 0.0,
'balance': 0.0,
'currency': 'ETH/USDT:USDT',
'est_stake': 20,
'used': 0,
'stake': 'BTC',
'is_position': True,
'leverage': 5.0,
'position': 1000.0,
'side': 'short',
}
]
assert result['total'] == 12.309096240461017
def test_rpc_start(mocker, default_conf) -> None:
@@ -697,7 +769,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
assert freqtradebot.config['max_open_trades'] == 0
def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
def test_rpc_forceexit(default_conf, ticker, fee, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
cancel_order_mock = MagicMock()
@@ -724,29 +796,29 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell(None)
rpc._rpc_forceexit(None)
freqtradebot.state = State.RUNNING
with pytest.raises(RPCException, match=r'.*invalid argument*'):
rpc._rpc_forcesell(None)
rpc._rpc_forceexit(None)
msg = rpc._rpc_forcesell('all')
msg = rpc._rpc_forceexit('all')
assert msg == {'result': 'Created sell orders for all open trades.'}
freqtradebot.enter_positions()
msg = rpc._rpc_forcesell('all')
msg = rpc._rpc_forceexit('all')
assert msg == {'result': 'Created sell orders for all open trades.'}
freqtradebot.enter_positions()
msg = rpc._rpc_forcesell('2')
msg = rpc._rpc_forceexit('2')
assert msg == {'result': 'Created sell order for trade 2.'}
freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell(None)
rpc._rpc_forceexit(None)
with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell('all')
rpc._rpc_forceexit('all')
freqtradebot.state = State.RUNNING
assert cancel_order_mock.call_count == 0
@@ -775,7 +847,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
)
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
# and trade amount is updated
rpc._rpc_forcesell('3')
rpc._rpc_forceexit('3')
assert cancel_order_mock.call_count == 1
assert trade.amount == filled_amount
@@ -803,7 +875,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
}
)
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
msg = rpc._rpc_forcesell('4')
msg = rpc._rpc_forceexit('4')
assert msg == {'result': 'Created sell order for trade 4.'}
assert cancel_order_mock.call_count == 2
assert trade.amount == amount
@@ -820,7 +892,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
'filled': 0.0
}
)
msg = rpc._rpc_forcesell('3')
msg = rpc._rpc_forceexit('3')
assert msg == {'result': 'Created sell order for trade 3.'}
# status quo, no exchange calls
assert cancel_order_mock.call_count == 3
@@ -862,8 +934,8 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
limit_sell_order, mocker) -> None:
def test_enter_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
limit_sell_order, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -891,23 +963,23 @@ def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
trade.close_date = datetime.utcnow()
trade.is_open = False
res = rpc._rpc_buy_tag_performance(None)
res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 1
assert res[0]['buy_tag'] == 'Other'
assert res[0]['enter_tag'] == 'Other'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.buy_tag = "TEST_TAG"
res = rpc._rpc_buy_tag_performance(None)
trade.enter_tag = "TEST_TAG"
res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 1
assert res[0]['buy_tag'] == 'TEST_TAG'
assert res[0]['enter_tag'] == 'TEST_TAG'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee):
def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -918,21 +990,21 @@ def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee):
create_mock_trades(fee)
rpc = RPC(freqtradebot)
res = rpc._rpc_buy_tag_performance(None)
res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 2
assert res[0]['buy_tag'] == 'TEST1'
assert res[0]['enter_tag'] == 'TEST1'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 0.5)
assert res[1]['buy_tag'] == 'Other'
assert res[1]['enter_tag'] == 'Other'
assert res[1]['count'] == 1
assert prec_satoshi(res[1]['profit_pct'], 1.0)
# Test for a specific pair
res = rpc._rpc_buy_tag_performance('ETC/BTC')
res = rpc._rpc_enter_tag_performance('ETC/BTC')
assert len(res) == 1
assert res[0]['count'] == 1
assert res[0]['buy_tag'] == 'TEST1'
assert res[0]['enter_tag'] == 'TEST1'
assert prec_satoshi(res[0]['profit_pct'], 0.5)
@@ -1046,7 +1118,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.buy_tag = "TESTBUY"
trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL"
res = rpc._rpc_mix_tag_performance(None)
@@ -1108,7 +1180,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
assert counts["current"] == 1
def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None:
def test_rpc_forceentry(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None:
default_conf['forcebuy_enable'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
buy_mm = MagicMock(return_value=limit_buy_order_open)
@@ -1124,16 +1196,16 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
trade = rpc._rpc_forcebuy(pair, None)
trade = rpc._rpc_force_entry(pair, None)
assert isinstance(trade, Trade)
assert trade.pair == pair
assert trade.open_rate == ticker()['bid']
# Test buy duplicate
with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'):
rpc._rpc_forcebuy(pair, 0.0001)
rpc._rpc_force_entry(pair, 0.0001)
pair = 'XRP/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit')
trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit')
assert isinstance(trade, Trade)
assert trade.pair == pair
assert trade.open_rate == 0.0001
@@ -1141,11 +1213,11 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
# Test buy pair not with stakes
with pytest.raises(RPCException,
match=r'Wrong pair selected. Only pairs with stake-currency.*'):
rpc._rpc_forcebuy('LTC/ETH', 0.0001)
rpc._rpc_force_entry('LTC/ETH', 0.0001)
# Test with defined stake_amount
pair = 'LTC/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05)
trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit', stake_amount=0.05)
assert trade.stake_amount == 0.05
assert trade.buy_tag == 'forceentry'
@@ -1156,11 +1228,11 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
pair = 'TKN/BTC'
trade = rpc._rpc_forcebuy(pair, None)
trade = rpc._rpc_force_entry(pair, None)
assert trade is None
def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
def test_rpc_forceentry_stopped(mocker, default_conf) -> None:
default_conf['forcebuy_enable'] = True
default_conf['initial_state'] = 'stopped'
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -1170,18 +1242,30 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
with pytest.raises(RPCException, match=r'trader is not running'):
rpc._rpc_forcebuy(pair, None)
rpc._rpc_force_entry(pair, None)
def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
def test_rpc_forceentry_disabled(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
with pytest.raises(RPCException, match=r'Forcebuy not enabled.'):
rpc._rpc_forcebuy(pair, None)
with pytest.raises(RPCException, match=r'Forceentry not enabled.'):
rpc._rpc_force_entry(pair, None)
def test_rpc_forceentry_wrong_mode(mocker, default_conf) -> None:
default_conf['forcebuy_enable'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
with pytest.raises(RPCException, match="Can't go short on Spot markets."):
rpc._rpc_force_entry(pair, None, order_side=SignalDirection.SHORT)
@pytest.mark.usefixtures("init_persistence")

View File

@@ -17,7 +17,7 @@ from numpy import isnan
from requests.auth import _basic_auth_str
from freqtrade.__init__ import __version__
from freqtrade.enums import RunMode, State
from freqtrade.enums import CandleType, RunMode, State, TradingMode
from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException
from freqtrade.loggers import setup_logging, setup_logging_pre
from freqtrade.persistence import PairLocks, Trade
@@ -25,8 +25,8 @@ from freqtrade.rpc import RPC
from freqtrade.rpc.api_server import ApiServer
from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token
from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer
from tests.conftest import (create_mock_trades, get_mock_coro, get_patched_freqtradebot, log_has,
log_has_re, patch_get_signal)
from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_mock_coro,
get_patched_freqtradebot, log_has, log_has_re, patch_get_signal)
BASE_URI = "/api/v1"
@@ -452,6 +452,10 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
'used': 0.0,
'est_stake': 12.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
}
assert 'starting_capital' in response
assert 'starting_capital_fiat' in response
@@ -459,7 +463,8 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
assert 'starting_capital_ratio' in response
def test_api_count(botclient, mocker, ticker, fee, markets):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_count(botclient, mocker, ticker, fee, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
mocker.patch.multiple(
@@ -476,7 +481,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets):
assert rc.json()["max"] == 1
# Create some test data
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/count")
assert_response(rc)
assert rc.json()["current"] == 4
@@ -527,21 +532,23 @@ def test_api_show_config(botclient):
rc = client_get(client, f"{BASE_URI}/show_config")
assert_response(rc)
assert 'dry_run' in rc.json()
assert rc.json()['exchange'] == 'binance'
assert rc.json()['timeframe'] == '5m'
assert rc.json()['timeframe_ms'] == 300000
assert rc.json()['timeframe_min'] == 5
assert rc.json()['state'] == 'running'
assert rc.json()['bot_name'] == 'freqtrade'
assert rc.json()['strategy_version'] is None
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()
assert 'api_version' in rc.json()
assert 1.1 <= rc.json()['api_version'] <= 1.2
response = rc.json()
assert 'dry_run' in response
assert response['exchange'] == 'binance'
assert response['timeframe'] == '5m'
assert response['timeframe_ms'] == 300000
assert response['timeframe_min'] == 5
assert response['state'] == 'running'
assert response['bot_name'] == 'freqtrade'
assert response['trading_mode'] == 'spot'
assert response['strategy_version'] is None
assert not response['trailing_stop']
assert 'entry_pricing' in response
assert 'exit_pricing' in response
assert 'unfilledtimeout' in response
assert 'version' in response
assert 'api_version' in response
assert 2.1 <= response['api_version'] <= 2.2
def test_api_daily(botclient, mocker, ticker, fee, markets):
@@ -562,7 +569,8 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
assert rc.json()['data'][0]['date'] == str(datetime.utcnow().date())
def test_api_trades(botclient, mocker, fee, markets):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_trades(botclient, mocker, fee, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
mocker.patch.multiple(
@@ -575,13 +583,15 @@ def test_api_trades(botclient, mocker, fee, markets):
assert rc.json()['trades_count'] == 0
assert rc.json()['total_trades'] == 0
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
Trade.query.session.flush()
rc = client_get(client, f"{BASE_URI}/trades")
assert_response(rc)
assert len(rc.json()['trades']) == 2
assert rc.json()['trades_count'] == 2
assert rc.json()['total_trades'] == 2
assert rc.json()['trades'][0]['is_short'] == is_short
rc = client_get(client, f"{BASE_URI}/trades?limit=1")
assert_response(rc)
assert len(rc.json()['trades']) == 1
@@ -589,9 +599,10 @@ def test_api_trades(botclient, mocker, fee, markets):
assert rc.json()['total_trades'] == 2
def test_api_trade_single(botclient, mocker, fee, ticker, markets):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets),
@@ -601,16 +612,19 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets):
assert_response(rc, 404)
assert rc.json()['detail'] == 'Trade not found.'
create_mock_trades(fee)
Trade.query.session.rollback()
create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/trade/3")
assert_response(rc)
assert rc.json()['trade_id'] == 3
assert rc.json()['is_short'] == is_short
def test_api_delete_trade(botclient, mocker, fee, markets):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
mocker.patch.multiple(
@@ -619,11 +633,8 @@ def test_api_delete_trade(botclient, mocker, fee, markets):
cancel_order=cancel_mock,
cancel_stoploss_order=stoploss_mock,
)
rc = client_delete(client, f"{BASE_URI}/trades/1")
# Error - trade won't exist yet.
assert_response(rc, 502)
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
ftbot.strategy.order_types['stoploss_on_exchange'] = True
trades = Trade.query.all()
@@ -650,6 +661,10 @@ def test_api_delete_trade(botclient, mocker, fee, markets):
assert len(trades) - 2 == len(Trade.query.all())
assert stoploss_mock.call_count == 1
rc = client_delete(client, f"{BASE_URI}/trades/502")
# Error - trade won't exist.
assert_response(rc, 502)
def test_api_logs(botclient):
ftbot, client = botclient
@@ -698,7 +713,48 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
assert rc.json() == {"error": "Error querying /api/v1/edge: Edge is not enabled."}
def test_api_profit(botclient, mocker, ticker, fee, markets):
@pytest.mark.parametrize('is_short,expected', [
(
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_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_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,
'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2}
),
(
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_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_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,
'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0}
),
(
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_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_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,
'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1}
)
])
def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected):
ftbot, client = botclient
patch_get_signal(ftbot)
mocker.patch.multiple(
@@ -713,45 +769,48 @@ def test_api_profit(botclient, mocker, ticker, fee, markets):
assert_response(rc, 200)
assert rc.json()['trade_count'] == 0
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
# Simulate fulfilled LIMIT_BUY order for trade
rc = client_get(client, f"{BASE_URI}/profit")
assert_response(rc)
assert rc.json() == {'avg_duration': ANY,
'best_pair': 'XRP/BTC',
'best_rate': 1.0,
'best_pair_profit_ratio': 0.01,
'first_trade_date': ANY,
'first_trade_timestamp': ANY,
'latest_trade_date': '5 minutes ago',
'latest_trade_timestamp': ANY,
'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,
'profit_all_ratio': -0.044063014216106644,
'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,
'profit_closed_percent': 0.0,
'trade_count': 6,
'closed_trade_count': 2,
'winning_trades': 2,
'losing_trades': 0,
}
# raise ValueError(rc.json())
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,
'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'],
'trade_count': 6,
'closed_trade_count': 2,
'winning_trades': expected['winning_trades'],
'losing_trades': expected['losing_trades'],
}
def test_api_stats(botclient, mocker, ticker, fee, markets,):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_stats(botclient, mocker, ticker, fee, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
@@ -765,7 +824,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
assert 'durations' in rc.json()
assert 'sell_reasons' in rc.json()
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200)
@@ -825,7 +884,12 @@ def test_api_performance(botclient, fee):
'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}]
def test_api_status(botclient, mocker, ticker, fee, markets):
@pytest.mark.parametrize(
'is_short,current_rate,open_order_id,open_trade_value',
[(True, 1.098e-05, 'dry_run_buy_short_12345', 15.0911775),
(False, 1.099e-05, 'dry_run_buy_long_12345', 15.1668225)])
def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
current_rate, open_order_id, open_trade_value):
ftbot, client = botclient
patch_get_signal(ftbot)
mocker.patch.multiple(
@@ -840,7 +904,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
rc = client_get(client, f"{BASE_URI}/status")
assert_response(rc, 200)
assert rc.json() == []
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/status")
assert_response(rc)
@@ -861,7 +925,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'profit_pct': ANY,
'profit_abs': ANY,
'profit_fiat': ANY,
'current_rate': 1.099e-05,
'current_rate': current_rate,
'open_date': ANY,
'open_timestamp': ANY,
'open_order': None,
@@ -891,19 +955,24 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'fee_open_cost': None,
'fee_open_currency': None,
'is_open': True,
"is_short": is_short,
'max_rate': ANY,
'min_rate': ANY,
'open_order_id': 'dry_run_buy_12345',
'open_order_id': open_order_id,
'open_rate_requested': ANY,
'open_trade_value': 15.1668225,
'open_trade_value': open_trade_value,
'sell_reason': None,
'sell_order_status': None,
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None,
'enter_tag': None,
'timeframe': 5,
'exchange': 'binance',
'leverage': 1.0,
'interest_rate': 0.0,
'funding_fees': None,
'trading_mode': ANY,
'orders': [ANY],
}
mocker.patch('freqtrade.exchange.Exchange.get_rate',
@@ -977,8 +1046,8 @@ def test_api_blacklist(botclient, mocker):
"NOTHING/BTC": {
"error_msg": "Pair NOTHING/BTC is not in the current blacklist."
}
},
}
},
}
rc = client_delete(
client,
f"{BASE_URI}/blacklist?pairs_to_delete=HOT/BTC&pairs_to_delete=ETH/BTC")
@@ -1003,23 +1072,27 @@ def test_api_whitelist(botclient):
}
def test_api_forcebuy(botclient, mocker, fee):
@pytest.mark.parametrize('endpoint', [
'forcebuy',
'forceenter',
])
def test_api_forceentry(botclient, mocker, fee, endpoint):
ftbot, client = botclient
rc = client_post(client, f"{BASE_URI}/forcebuy",
rc = client_post(client, f"{BASE_URI}/{endpoint}",
data='{"pair": "ETH/BTC"}')
assert_response(rc, 502)
assert rc.json() == {"error": "Error querying /api/v1/forcebuy: Forcebuy not enabled."}
assert rc.json() == {"error": f"Error querying /api/v1/{endpoint}: Forceentry not enabled."}
# enable forcebuy
ftbot.config['forcebuy_enable'] = True
fbuy_mock = MagicMock(return_value=None)
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
rc = client_post(client, f"{BASE_URI}/forcebuy",
mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock)
rc = client_post(client, f"{BASE_URI}/{endpoint}",
data='{"pair": "ETH/BTC"}')
assert_response(rc)
assert rc.json() == {"status": "Error buying pair ETH/BTC."}
assert rc.json() == {"status": "Error entering long trade for pair ETH/BTC."}
# Test creating trade
fbuy_mock = MagicMock(return_value=Trade(
@@ -1032,21 +1105,23 @@ def test_api_forcebuy(botclient, mocker, fee):
open_order_id="123456",
open_date=datetime.utcnow(),
is_open=False,
is_short=False,
fee_close=fee.return_value,
fee_open=fee.return_value,
close_rate=0.265441,
id=22,
timeframe=5,
strategy="StrategyTestV2"
strategy=CURRENT_TEST_STRATEGY,
trading_mode=TradingMode.SPOT
))
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock)
rc = client_post(client, f"{BASE_URI}/forcebuy",
rc = client_post(client, f"{BASE_URI}/{endpoint}",
data='{"pair": "ETH/BTC"}')
assert_response(rc)
assert rc.json() == {
'amount': 1,
'amount_requested': 1,
'amount': 1.0,
'amount_requested': 1.0,
'trade_id': 22,
'close_date': None,
'close_timestamp': None,
@@ -1080,6 +1155,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'fee_open_cost': None,
'fee_open_currency': None,
'is_open': False,
'is_short': False,
'max_rate': None,
'min_rate': None,
'open_order_id': '123456',
@@ -1087,10 +1163,15 @@ def test_api_forcebuy(botclient, mocker, fee):
'open_trade_value': 0.24605460,
'sell_reason': None,
'sell_order_status': None,
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None,
'enter_tag': None,
'timeframe': 5,
'exchange': 'binance',
'leverage': None,
'interest_rate': None,
'funding_fees': None,
'trading_mode': 'spot',
'orders': [],
}
@@ -1146,17 +1227,19 @@ def test_api_pair_candles(botclient, ohlcv_history):
assert 'data_stop_ts' in rc.json()
assert len(rc.json()['data']) == 0
ohlcv_history['sma'] = ohlcv_history['close'].rolling(2).mean()
ohlcv_history['buy'] = 0
ohlcv_history.loc[1, 'buy'] = 1
ohlcv_history['sell'] = 0
ohlcv_history['enter_long'] = 0
ohlcv_history.loc[1, 'enter_long'] = 1
ohlcv_history['exit_long'] = 0
ohlcv_history['enter_short'] = 0
ohlcv_history['exit_short'] = 0
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history)
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT)
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
assert 'strategy' in rc.json()
assert rc.json()['strategy'] == 'StrategyTestV2'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert 'columns' in rc.json()
assert 'data_start_ts' in rc.json()
assert 'data_start' in rc.json()
@@ -1167,9 +1250,12 @@ def test_api_pair_candles(botclient, ohlcv_history):
assert rc.json()['data_stop'] == '2017-11-26 09:00:00+00:00'
assert rc.json()['data_stop_ts'] == 1511686800000
assert isinstance(rc.json()['columns'], list)
assert rc.json()['columns'] == ['date', 'open', 'high',
'low', 'close', 'volume', 'sma', 'buy', 'sell',
'__date_ts', '_buy_signal_close', '_sell_signal_close']
assert set(rc.json()['columns']) == {
'date', 'open', 'high', 'low', 'close', 'volume',
'sma', 'enter_long', 'exit_long', 'enter_short', 'exit_short', '__date_ts',
'_enter_long_signal_close', '_exit_long_signal_close',
'_enter_short_signal_close', '_exit_short_signal_close'
}
assert 'pair' in rc.json()
assert rc.json()['pair'] == 'XRP/BTC'
@@ -1178,31 +1264,32 @@ def test_api_pair_candles(botclient, ohlcv_history):
assert (rc.json()['data'] ==
[['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],
None, 0, 0, 0, 0, 1511686200000, None, None, 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.893e-05,
None],
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 0, 0, 1511686500000, 8.893e-05,
None, None, 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]
0.7039405, 8.885e-05, 0, 0, 0, 0, 1511686800000, None, None, None, None]
])
ohlcv_history['sell'] = ohlcv_history['sell'].astype('float64')
ohlcv_history.at[0, 'sell'] = float('inf')
ohlcv_history['exit_long'] = ohlcv_history['exit_long'].astype('float64')
ohlcv_history.at[0, 'exit_long'] = float('inf')
ohlcv_history['date1'] = ohlcv_history['date']
ohlcv_history.at[0, 'date1'] = pd.NaT
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history)
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT)
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
assert (rc.json()['data'] ==
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
None, 0, None, None, 1511686200000, None, None],
None, 0, None, 0, 0, None, 1511686200000, None, None, 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.0, '2017-11-26 08:55:00',
1511686500000, 8.893e-05, None],
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0.0, 0, 0, '2017-11-26 08:55:00',
1511686500000, 8.893e-05, None, None, 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.0, '2017-11-26 09:00:00', 1511686800000, None, None]
0.7039405, 8.885e-05, 0, 0.0, 0, 0, '2017-11-26 09:00:00', 1511686800000,
None, None, None, None]
])
@@ -1213,19 +1300,19 @@ def test_api_pair_history(botclient, ohlcv_history):
# No pair
rc = client_get(client,
f"{BASE_URI}/pair_history?timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=StrategyTestV2")
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422)
# No Timeframe
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
"&timerange=20180111-20180112&strategy=StrategyTestV2")
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422)
# No timerange
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&strategy=StrategyTestV2")
f"&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422)
# No strategy
@@ -1237,14 +1324,14 @@ def test_api_pair_history(botclient, ohlcv_history):
# Working
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=StrategyTestV2")
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 200)
assert rc.json()['length'] == 289
assert len(rc.json()['data']) == rc.json()['length']
assert 'columns' in rc.json()
assert 'data' in rc.json()
assert rc.json()['pair'] == 'UNITTEST/BTC'
assert rc.json()['strategy'] == 'StrategyTestV2'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
assert rc.json()['data_start_ts'] == 1515628800000
assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00'
@@ -1253,7 +1340,7 @@ def test_api_pair_history(botclient, ohlcv_history):
# No data found
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20200111-20200112&strategy=StrategyTestV2")
f"&timerange=20200111-20200112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 502)
assert rc.json()['error'] == ("Error querying /api/v1/pair_history: "
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
@@ -1294,19 +1381,21 @@ def test_api_strategies(botclient):
'HyperoptableStrategy',
'InformativeDecoratorTest',
'StrategyTestV2',
'TestStrategyLegacyV1'
'StrategyTestV3',
'StrategyTestV3Futures',
'TestStrategyLegacyV1',
]}
def test_api_strategy(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/strategy/StrategyTestV2")
rc = client_get(client, f"{BASE_URI}/strategy/{CURRENT_TEST_STRATEGY}")
assert_response(rc)
assert rc.json()['strategy'] == 'StrategyTestV2'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v2.py").read_text()
data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v3.py").read_text()
assert rc.json()['code'] == data
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
@@ -1338,6 +1427,20 @@ def test_list_available_pairs(botclient):
assert rc.json()['pairs'] == ['XRP/ETH']
assert len(rc.json()['pair_interval']) == 1
ftbot.config['trading_mode'] = 'futures'
rc = client_get(
client, f"{BASE_URI}/available_pairs?timeframe=1h")
assert_response(rc)
assert rc.json()['length'] == 1
assert rc.json()['pairs'] == ['XRP/USDT']
rc = client_get(
client, f"{BASE_URI}/available_pairs?timeframe=1h&candletype=mark")
assert_response(rc)
assert rc.json()['length'] == 2
assert rc.json()['pairs'] == ['UNITTEST/USDT', 'XRP/USDT']
assert len(rc.json()['pair_interval']) == 2
def test_sysinfo(botclient):
ftbot, client = botclient
@@ -1383,7 +1486,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir):
# start backtesting
data = {
"strategy": "StrategyTestV2",
"strategy": CURRENT_TEST_STRATEGY,
"timeframe": "5m",
"timerange": "20180110-20180111",
"max_open_trades": 3,

View File

@@ -18,7 +18,7 @@ from telegram.error import BadRequest, NetworkError, TelegramError
from freqtrade import __version__
from freqtrade.constants import CANCEL_REASON
from freqtrade.edge import PairInfo
from freqtrade.enums import RPCMessageType, RunMode, SellType, State
from freqtrade.enums import ExitType, RPCMessageType, RunMode, SignalDirection, State
from freqtrade.exceptions import OperationalException
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.loggers import setup_logging
@@ -27,8 +27,8 @@ from freqtrade.persistence.models import Order
from freqtrade.rpc import RPC
from freqtrade.rpc.rpc import RPCException
from freqtrade.rpc.telegram import Telegram, authorized_only
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, log_has_re,
patch_exchange, patch_get_signal, patch_whitelist)
from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_patched_freqtradebot,
log_has, log_has_re, patch_exchange, patch_get_signal, patch_whitelist)
class DummyCls(Telegram):
@@ -94,8 +94,10 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
assert start_polling.start_polling.call_count == 1
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
"['delete'], ['performance'], ['buys'], ['sells'], ['mix_tags'], "
"['balance'], ['start'], ['stop'], "
"['forcesell', 'forceexit'], ['forcebuy', 'forcelong'], ['forceshort'], "
"['trades'], ['delete'], ['performance'], "
"['buys', 'entries'], ['sells'], ['mix_tags'], "
"['stats'], ['daily'], ['weekly'], ['monthly'], "
"['count'], ['locks'], ['unlock', 'delete_locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
@@ -191,6 +193,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'amount': 90.99181074,
'stake_amount': 90.99181074,
'buy_tag': None,
'enter_tag': None,
'close_profit_ratio': None,
'profit': -0.0059,
'profit_ratio': -0.0059,
@@ -203,6 +206,8 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'stop_loss_ratio': -0.0001,
'open_order': '(limit buy rem=0.00000000)',
'is_open': True,
'is_short': False,
'filled_entry_orders': [],
'orders': []
}]),
)
@@ -393,7 +398,8 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
assert int(fields[0]) == 1
assert 'ETH/BTC' in fields[1]
assert 'L' in fields[1]
assert 'ETH/BTC' in fields[2]
assert msg_mock.call_count == 1
@@ -623,7 +629,7 @@ def test_weekly_wrong_input(default_conf, update, ticker, mocker) -> None:
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]
in msg_mock.call_args_list[0][0][0]
def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
@@ -809,8 +815,9 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0]
@pytest.mark.parametrize('is_short', [True, False])
def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee,
limit_buy_order, limit_sell_order, mocker) -> None:
limit_buy_order, limit_sell_order, mocker, is_short) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -826,7 +833,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee,
msg_mock.reset_mock()
# Create some test data
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
telegram._stats(update=update, context=MagicMock())
assert msg_mock.call_count == 1
@@ -900,6 +907,10 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
'balance': i,
'est_stake': 1,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
})
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={
'currencies': balances,
@@ -1024,7 +1035,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
# /forcesell 1
context = MagicMock()
context.args = ["1"]
telegram._forcesell(update=update, context=context)
telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 4
last_msg = msg_mock.call_args_list[-2][0][0]
@@ -1034,18 +1045,21 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'profit',
'leverage': 1.0,
'limit': 1.173e-05,
'amount': 91.07468123,
'order_type': 'limit',
'open_rate': 1.098e-05,
'current_rate': 1.173e-05,
'direction': 'Long',
'profit_amount': 6.314e-05,
'profit_ratio': 0.0629778,
'stake_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'enter_tag': ANY,
'sell_reason': ExitType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@@ -1088,7 +1102,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
# /forcesell 1
context = MagicMock()
context.args = ["1"]
telegram._forcesell(update=update, context=context)
telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 4
@@ -1099,18 +1113,21 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'loss',
'leverage': 1.0,
'limit': 1.043e-05,
'amount': 91.07468123,
'order_type': 'limit',
'open_rate': 1.098e-05,
'current_rate': 1.043e-05,
'direction': 'Long',
'profit_amount': -5.497e-05,
'profit_ratio': -0.05482878,
'stake_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'enter_tag': ANY,
'sell_reason': ExitType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@@ -1143,7 +1160,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
# /forcesell all
context = MagicMock()
context.args = ["all"]
telegram._forcesell(update=update, context=context)
telegram._forceexit(update=update, context=context)
# Called for each trade 2 times
assert msg_mock.call_count == 8
@@ -1154,18 +1171,21 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'loss',
'leverage': 1.0,
'limit': 1.099e-05,
'amount': 91.07468123,
'order_type': 'limit',
'open_rate': 1.098e-05,
'current_rate': 1.099e-05,
'direction': 'Long',
'profit_amount': -4.09e-06,
'profit_ratio': -0.00408133,
'stake_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'enter_tag': ANY,
'sell_reason': ExitType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@@ -1184,7 +1204,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
# /forcesell 1
context = MagicMock()
context.args = ["1"]
telegram._forcesell(update=update, context=context)
telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 1
assert 'not running' in msg_mock.call_args_list[0][0][0]
@@ -1194,36 +1214,37 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
# /forcesell 123456
context = MagicMock()
context.args = ["123456"]
telegram._forcesell(update=update, context=context)
telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 1
assert 'invalid argument' in msg_mock.call_args_list[0][0][0]
def test_forcebuy_handle(default_conf, update, mocker) -> None:
def test_forceenter_handle(default_conf, update, mocker) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
fbuy_mock = MagicMock(return_value=None)
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
# /forcebuy ETH/BTC
# /forcelong ETH/BTC
context = MagicMock()
context.args = ["ETH/BTC"]
telegram._forcebuy(update=update, context=context)
telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG)
assert fbuy_mock.call_count == 1
assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC'
assert fbuy_mock.call_args_list[0][0][1] is None
assert fbuy_mock.call_args_list[0][1]['order_side'] == SignalDirection.LONG
# Reset and retry with specified price
fbuy_mock = MagicMock(return_value=None)
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
# /forcebuy ETH/BTC 0.055
mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
# /forcelong ETH/BTC 0.055
context = MagicMock()
context.args = ["ETH/BTC", "0.055"]
telegram._forcebuy(update=update, context=context)
telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG)
assert fbuy_mock.call_count == 1
assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC'
@@ -1231,24 +1252,24 @@ def test_forcebuy_handle(default_conf, update, mocker) -> None:
assert fbuy_mock.call_args_list[0][0][1] == 0.055
def test_forcebuy_handle_exception(default_conf, update, mocker) -> None:
def test_forceenter_handle_exception(default_conf, update, mocker) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
update.message.text = '/forcebuy ETH/Nonepair'
telegram._forcebuy(update=update, context=MagicMock())
telegram._forceenter(update=update, context=MagicMock(), order_side=SignalDirection.LONG)
assert msg_mock.call_count == 1
assert msg_mock.call_args_list[0][0][0] == 'Forcebuy not enabled.'
assert msg_mock.call_args_list[0][0][0] == 'Forceentry not enabled.'
def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
def test_forceenter_no_pair(default_conf, update, mocker) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
fbuy_mock = MagicMock(return_value=None)
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1256,7 +1277,7 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
context = MagicMock()
context.args = []
telegram._forcebuy(update=update, context=context)
telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG)
assert fbuy_mock.call_count == 0
assert msg_mock.call_count == 1
@@ -1267,8 +1288,8 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5
update = MagicMock()
update.callback_query = MagicMock()
update.callback_query.data = 'XRP/USDT'
telegram._forcebuy_inline(update, None)
update.callback_query.data = 'XRP/USDT_||_long'
telegram._forceenter_inline(update, None)
assert fbuy_mock.call_count == 1
@@ -1318,12 +1339,12 @@ def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee,
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
trade.buy_tag = "TESTBUY"
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
trade.enter_tag = "TESTBUY"
# Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
@@ -1331,19 +1352,19 @@ def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee,
trade.close_date = datetime.utcnow()
trade.is_open = False
context = MagicMock()
telegram._buy_tag_performance(update=update, context=context)
telegram._enter_tag_performance(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Buy Tag Performance' in msg_mock.call_args_list[0][0][0]
assert '<code>TESTBUY\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0]
context.args = [trade.pair]
telegram._buy_tag_performance(update=update, context=context)
telegram._enter_tag_performance(update=update, context=context)
assert msg_mock.call_count == 2
msg_mock.reset_mock()
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_buy_tag_performance',
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_enter_tag_performance',
side_effect=RPCException('Error'))
telegram._buy_tag_performance(update=update, context=MagicMock())
telegram._enter_tag_performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert "Error" in msg_mock.call_args_list[0][0][0]
@@ -1407,7 +1428,8 @@ def test_telegram_mix_tag_performance_handle(default_conf, update, ticker, fee,
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
trade.buy_tag = "TESTBUY"
trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL"
# Simulate fulfilled LIMIT_BUY order for trade
@@ -1633,7 +1655,10 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
assert 'Winrate' not in msg_mock.call_args_list[0][0][0]
def test_telegram_trades(mocker, update, default_conf, fee):
@pytest.mark.parametrize('is_short,regex_pattern',
[(True, r"just now[ ]*XRP\/BTC \(#3\) -1.00% \("),
(False, r"just now[ ]*XRP\/BTC \(#3\) 1.00% \(")])
def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_pattern):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1651,7 +1676,7 @@ def test_telegram_trades(mocker, update, default_conf, fee):
assert "<pre>" not in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
context = MagicMock()
context.args = [5]
@@ -1661,11 +1686,11 @@ def test_telegram_trades(mocker, update, default_conf, fee):
assert "Profit (" in msg_mock.call_args_list[0][0][0]
assert "Close Date" in msg_mock.call_args_list[0][0][0]
assert "<pre>" in msg_mock.call_args_list[0][0][0]
assert bool(re.search(r"just now[ ]*XRP\/BTC \(#3\) 1.00% \(",
msg_mock.call_args_list[0][0][0]))
assert bool(re.search(regex_pattern, msg_mock.call_args_list[0][0][0]))
def test_telegram_delete_trade(mocker, update, default_conf, fee):
@pytest.mark.parametrize('is_short', [True, False])
def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
context = MagicMock()
@@ -1675,7 +1700,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee):
assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
context = MagicMock()
context.args = [1]
@@ -1720,7 +1745,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
assert msg_mock.call_count == 1
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0]
assert f'*Strategy:* `{CURRENT_TEST_STRATEGY}`' in msg_mock.call_args_list[0][0][0]
assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -1729,18 +1754,25 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
assert msg_mock.call_count == 1
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0]
assert f'*Strategy:* `{CURRENT_TEST_STRATEGY}`' in msg_mock.call_args_list[0][0][0]
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
(RPCMessageType.BUY, 'Long', 'long_signal_01', None),
(RPCMessageType.BUY, 'Long', 'long_signal_01', 1.0),
(RPCMessageType.BUY, 'Long', 'long_signal_01', 5.0),
(RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)])
def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
enter, enter_signal, leverage) -> None:
msg = {
'type': RPCMessageType.BUY,
'type': message_type,
'trade_id': 1,
'buy_tag': 'buy_signal_01',
'enter_tag': enter_signal,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': leverage,
'limit': 1.099e-05,
'order_type': 'limit',
'stake_amount': 0.01465333,
@@ -1754,13 +1786,17 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg(msg)
assert msg_mock.call_args[0][0] \
== '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \
'*Buy Tag:* `buy_signal_01`\n' \
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \
'*Current Rate:* `0.00001099`\n' \
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
f'\N{LARGE BLUE CIRCLE} *Binance:* {enter} ETH/BTC (#1)\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n'
f'{leverage_text}'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
)
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
caplog.clear()
@@ -1778,20 +1814,23 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
msg_mock.call_args_list[0][1]['disable_notification'] is True
def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
@pytest.mark.parametrize('message_type,enter_signal', [
(RPCMessageType.BUY_CANCEL, 'long_signal_01'),
(RPCMessageType.SHORT_CANCEL, 'short_signal_01')])
def test_send_msg_buy_cancel_notification(default_conf, mocker, message_type, enter_signal) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
'type': RPCMessageType.BUY_CANCEL,
'buy_tag': 'buy_signal_01',
'type': message_type,
'enter_tag': enter_signal,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'reason': CANCEL_REASON['TIMEOUT']
})
assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Binance:* '
'Cancelling open buy Order for ETH/BTC (#1). '
'Cancelling enter Order for ETH/BTC (#1). '
'Reason: cancelled due to timeout.')
@@ -1823,17 +1862,24 @@ def test_send_msg_protection_notification(default_conf, mocker, time_machine) ->
"*All pairs* will be locked until `2021-09-01 06:45:00`.")
def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
@pytest.mark.parametrize('message_type,entered,enter_signal,leverage', [
(RPCMessageType.BUY_FILL, 'Longed', 'long_signal_01', 1.0),
(RPCMessageType.BUY_FILL, 'Longed', 'long_signal_02', 2.0),
(RPCMessageType.SHORT_FILL, 'Shorted', 'short_signal_01', 2.0),
])
def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, entered,
enter_signal, leverage) -> None:
default_conf['telegram']['notification_settings']['buy_fill'] = 'on'
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
'type': RPCMessageType.BUY_FILL,
'type': message_type,
'trade_id': 1,
'buy_tag': 'buy_signal_01',
'enter_tag': enter_signal,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': leverage,
'stake_amount': 0.01465333,
# 'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
@@ -1842,13 +1888,15 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1)
})
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.01465333 BTC, 180.895 USD)`'
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
f'\N{CHECK MARK} *Binance:* {entered} 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:
@@ -1862,6 +1910,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
'leverage': 1.0,
'direction': 'Long',
'gain': 'loss',
'limit': 3.201e-05,
'amount': 1333.3333333333335,
@@ -1872,22 +1922,23 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'enter_tag': 'buy_signal1',
'sell_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-1),
'close_date': arrow.utcnow(),
})
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\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'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Close Rate:* `0.00003201`'
)
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
'*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`'
)
msg_mock.reset_mock()
telegram.send_msg({
@@ -1895,6 +1946,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
'direction': 'Long',
'gain': 'loss',
'limit': 3.201e-05,
'amount': 1333.3333333333335,
@@ -1904,22 +1956,23 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'enter_tag': 'buy_signal1',
'sell_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(),
})
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\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'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Close Rate:* `0.00003201`'
)
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41%`\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`'
)
# Reset singleton function to avoid random breaks
telegram._rpc._fiat_converter.convert_amount = old_convamount
@@ -1937,9 +1990,9 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
'pair': 'KEY/ETH',
'reason': 'Cancelled on exchange'
})
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
' Reason: Cancelled on exchange.')
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Cancelling exit Order for KEY/ETH (#1).'
' Reason: Cancelled on exchange.')
msg_mock.reset_mock()
telegram.send_msg({
@@ -1949,14 +2002,19 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
'pair': 'KEY/ETH',
'reason': 'timeout'
})
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
' Reason: timeout.')
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Cancelling exit Order for KEY/ETH (#1). Reason: timeout.')
# Reset singleton function to avoid random breaks
telegram._rpc._fiat_converter.convert_amount = old_convamount
def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
@pytest.mark.parametrize('direction,enter_signal,leverage', [
('Long', 'long_signal_01', None),
('Long', 'long_signal_01', 1.0),
('Long', 'long_signal_01', 5.0),
('Short', 'short_signal_01', 2.0)])
def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
enter_signal, leverage) -> None:
default_conf['telegram']['notification_settings']['sell_fill'] = 'on'
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1966,6 +2024,8 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
'leverage': leverage,
'direction': direction,
'gain': 'loss',
'limit': 3.201e-05,
'amount': 1333.3333333333335,
@@ -1975,21 +2035,25 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'enter_tag': enter_signal,
'sell_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(),
})
assert msg_mock.call_args[0][0] \
== ('\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'
'*Open Rate:* `0.00007500`\n'
'*Close Rate:* `0.00003201`'
)
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Exited KEY/ETH (#1)\n'
'*Profit:* `-57.41%`\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`'
)
def test_send_msg_status_notification(default_conf, mocker) -> None:
@@ -2028,16 +2092,22 @@ def test_send_msg_unknown_type(default_conf, mocker) -> None:
})
def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
(RPCMessageType.BUY, 'Long', 'long_signal_01', None),
(RPCMessageType.BUY, 'Long', 'long_signal_01', 2.0),
(RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)])
def test_send_msg_buy_notification_no_fiat(
default_conf, mocker, message_type, enter, enter_signal, leverage) -> None:
del default_conf['fiat_display_currency']
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
'type': RPCMessageType.BUY,
'buy_tag': 'buy_signal_01',
'type': message_type,
'enter_tag': enter_signal,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': leverage,
'limit': 1.099e-05,
'order_type': 'limit',
'stake_amount': 0.01465333,
@@ -2048,15 +2118,27 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1)
})
assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n'
'*Buy Tag:* `buy_signal_01`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC)`')
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
f'\N{LARGE BLUE CIRCLE} *Binance:* {enter} ETH/BTC (#1)\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n'
f'{leverage_text}'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC)`'
)
def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
@pytest.mark.parametrize('direction,enter_signal,leverage', [
('Long', 'long_signal_01', None),
('Long', 'long_signal_01', 1.0),
('Long', 'long_signal_01', 5.0),
('Short', 'short_signal_01', 2.0),
])
def test_send_msg_sell_notification_no_fiat(
default_conf, mocker, direction, enter_signal, leverage) -> None:
del default_conf['fiat_display_currency']
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -2066,6 +2148,8 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'exchange': 'Binance',
'pair': 'KEY/ETH',
'gain': 'loss',
'leverage': leverage,
'direction': direction,
'limit': 3.201e-05,
'amount': 1333.3333333333335,
'order_type': 'limit',
@@ -2075,21 +2159,26 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'enter_tag': enter_signal,
'sell_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3),
'close_date': arrow.utcnow(),
})
assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41%`\n'
'*Buy Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n'
'*Duration:* `2:35:03 (155.1 min)`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Close Rate:* `0.00003201`'
)
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41%`\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`'
)
@pytest.mark.parametrize('msg,expected', [

View File

@@ -5,7 +5,7 @@ from unittest.mock import MagicMock
import pytest
from requests import RequestException
from freqtrade.enums import RPCMessageType, SellType
from freqtrade.enums import ExitType, RPCMessageType
from freqtrade.rpc import RPC
from freqtrade.rpc.webhook import Webhook
from tests.conftest import get_patched_freqtradebot, log_has
@@ -18,17 +18,23 @@ def get_webhook_dict() -> dict:
"webhookbuy": {
"value1": "Buying {pair}",
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
"value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
"value5": "direction {direction}"
},
"webhookbuycancel": {
"value1": "Cancelling Open Buy Order for {pair}",
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
"value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
"value5": "direction {direction}"
},
"webhookbuyfill": {
"value1": "Buy Order for {pair} filled",
"value2": "at {open_rate:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
"value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
"value5": "direction {direction}"
},
"webhooksell": {
"value1": "Selling {pair}",
@@ -71,6 +77,8 @@ def test_send_msg_webhook(default_conf, mocker):
'type': RPCMessageType.BUY,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 1.0,
'direction': 'Long',
'limit': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
@@ -85,6 +93,37 @@ def test_send_msg_webhook(default_conf, mocker):
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuy"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuy"]["value5"].format(**msg))
# Test short
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SHORT,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 2.0,
'direction': 'Short',
'limit': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
'stake_currency': 'BTC',
'fiat_currency': 'EUR'
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert (msg_mock.call_args[0][0]["value1"] ==
default_conf["webhook"]["webhookbuy"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuy"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuy"]["value5"].format(**msg))
# Test buy cancel
msg_mock.reset_mock()
@@ -92,6 +131,8 @@ def test_send_msg_webhook(default_conf, mocker):
'type': RPCMessageType.BUY_CANCEL,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 1.0,
'direction': 'Long',
'limit': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
@@ -106,6 +147,33 @@ def test_send_msg_webhook(default_conf, mocker):
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
# Test short cancel
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SHORT_CANCEL,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 2.0,
'direction': 'Short',
'limit': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
'stake_currency': 'BTC',
'fiat_currency': 'EUR'
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert (msg_mock.call_args[0][0]["value1"] ==
default_conf["webhook"]["webhookbuycancel"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
# Test buy fill
msg_mock.reset_mock()
@@ -113,6 +181,8 @@ def test_send_msg_webhook(default_conf, mocker):
'type': RPCMessageType.BUY_FILL,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 1.0,
'direction': 'Long',
'open_rate': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
@@ -127,8 +197,40 @@ def test_send_msg_webhook(default_conf, mocker):
default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
# Test short fill
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SHORT_FILL,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 2.0,
'direction': 'Short',
'open_rate': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
'stake_currency': 'BTC',
'fiat_currency': 'EUR'
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert (msg_mock.call_args[0][0]["value1"] ==
default_conf["webhook"]["webhookbuyfill"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
# Test sell
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SELL,
'exchange': 'Binance',
@@ -142,7 +244,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001,
'profit_ratio': 0.20,
'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value
'sell_reason': ExitType.STOP_LOSS.value
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
@@ -167,7 +269,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001,
'profit_ratio': 0.20,
'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value
'sell_reason': ExitType.STOP_LOSS.value
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
@@ -192,7 +294,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001,
'profit_ratio': 0.20,
'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value
'sell_reason': ExitType.STOP_LOSS.value
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1

View File

@@ -0,0 +1,49 @@
"""
The strategies here are minimal strategies designed to fail loading in certain conditions.
They are not operational, and don't aim to be.
"""
from datetime import datetime
from pandas import DataFrame
from freqtrade.strategy.interface import IStrategy
class TestStrategyNoImplements(IStrategy):
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return super().populate_indicators(dataframe, metadata)
class TestStrategyNoImplementSell(TestStrategyNoImplements):
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return super().populate_entry_trend(dataframe, metadata)
class TestStrategyImplementCustomSell(TestStrategyNoImplementSell):
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return super().populate_exit_trend(dataframe, metadata)
def custom_sell(self, pair: str, trade, current_time: datetime,
current_rate: float, current_profit: float,
**kwargs):
return False
class TestStrategyImplementBuyTimeout(TestStrategyNoImplementSell):
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return super().populate_exit_trend(dataframe, metadata)
def check_buy_timeout(self, pair: str, trade, order: dict,
current_time: datetime, **kwargs) -> bool:
return False
class TestStrategyImplementSellTimeout(TestStrategyNoImplementSell):
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return super().populate_exit_trend(dataframe, metadata)
def check_sell_timeout(self, pair: str, trade, order: dict,
current_time: datetime, **kwargs) -> bool:
return False

View File

@@ -85,7 +85,7 @@ class HyperoptableStrategy(StrategyTestV2):
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
:return: DataFrame with sell column
"""
dataframe.loc[
(

View File

@@ -2,8 +2,7 @@
from pandas import DataFrame
from freqtrade.strategy import informative, merge_informative_pair
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import IStrategy, informative, merge_informative_pair
class InformativeDecoratorTest(IStrategy):
@@ -20,7 +19,12 @@ class InformativeDecoratorTest(IStrategy):
startup_candle_count: int = 20
def informative_pairs(self):
return [('NEO/USDT', '5m')]
# Intentionally return 2 tuples, must be converted to 3 in compatibility code
return [
('NEO/USDT', '5m'),
('NEO/USDT', '15m', ''),
('NEO/USDT', '2h', 'futures'),
]
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['buy'] = 0
@@ -44,7 +48,7 @@ class InformativeDecoratorTest(IStrategy):
return dataframe
# Quote currency different from stake currency test.
@informative('1h', 'ETH/BTC')
@informative('1h', 'ETH/BTC', candle_type='spot')
def populate_indicators_eth_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = 14
return dataframe
@@ -68,7 +72,7 @@ class InformativeDecoratorTest(IStrategy):
dataframe['rsi_less'] = dataframe['rsi'] < dataframe['rsi_1h']
# Mixing manual informative pairs with decorators.
informative = self.dp.get_pair_dataframe('NEO/USDT', '5m')
informative = self.dp.get_pair_dataframe('NEO/USDT', '5m', '')
informative['rsi'] = 14
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, '5m', ffill=True)

View File

@@ -4,7 +4,7 @@
import talib.abstract as ta
from pandas import DataFrame
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import IStrategy
# --------------------------------

View File

@@ -36,8 +36,8 @@ class StrategyTestV2(IStrategy):
# Optional order type mapping
order_types = {
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
}
@@ -47,8 +47,8 @@ class StrategyTestV2(IStrategy):
# Optional time in force for orders
order_time_in_force = {
'buy': 'gtc',
'sell': 'gtc',
'entry': 'gtc',
'exit': 'gtc',
}
# By default this strategy does not use Position Adjustments

View File

@@ -0,0 +1,193 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from datetime import datetime
import talib.abstract as ta
from pandas import DataFrame
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.persistence import Trade
from freqtrade.strategy import (BooleanParameter, DecimalParameter, IntParameter, IStrategy,
RealParameter)
class StrategyTestV3(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
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'] = 1
dataframe.loc[
(
qtpylib.crossed_below(dataframe['rsi'], self.sell_rsi.value)
),
'enter_short'] = 1
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'] = 1
dataframe.loc[
(
qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)
),
'exit_short'] = 1
return dataframe
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, side: str,
**kwargs) -> float:
# Return 3.0 in all cases.
# Bot-logic must make sure it's an allowed leverage and eventually adjust accordingly.
return 3.0
def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, min_stake: float, max_stake: float, **kwargs):
if current_profit < -0.0075:
orders = trade.select_filled_orders(trade.enter_side)
return round(orders[0].cost, 0)
return None
class StrategyTestV3Futures(StrategyTestV3):
can_short = True

View File

@@ -1,23 +1,28 @@
from datetime import datetime
import pytest
from pandas import DataFrame
from freqtrade.persistence.models import Trade
from .strats.strategy_test_v2 import StrategyTestV2
from .strats.strategy_test_v3 import StrategyTestV3
def test_strategy_test_v2_structure():
assert hasattr(StrategyTestV2, 'minimal_roi')
assert hasattr(StrategyTestV2, 'stoploss')
assert hasattr(StrategyTestV2, 'timeframe')
assert hasattr(StrategyTestV2, 'populate_indicators')
assert hasattr(StrategyTestV2, 'populate_buy_trend')
assert hasattr(StrategyTestV2, 'populate_sell_trend')
def test_strategy_test_v3_structure():
assert hasattr(StrategyTestV3, 'minimal_roi')
assert hasattr(StrategyTestV3, 'stoploss')
assert hasattr(StrategyTestV3, 'timeframe')
assert hasattr(StrategyTestV3, 'populate_indicators')
assert hasattr(StrategyTestV3, 'populate_entry_trend')
assert hasattr(StrategyTestV3, 'populate_exit_trend')
def test_strategy_test_v2(result, fee):
strategy = StrategyTestV2({})
@pytest.mark.parametrize('is_short,side', [
(True, 'short'),
(False, 'long'),
])
def test_strategy_test_v3(result, fee, is_short, side):
strategy = StrategyTestV3({})
metadata = {'pair': 'ETH/BTC'}
assert type(strategy.minimal_roi) is dict
@@ -32,15 +37,19 @@ def test_strategy_test_v2(result, fee):
open_rate=19_000,
amount=0.1,
pair='ETH/BTC',
fee_open=fee.return_value
fee_open=fee.return_value,
is_short=is_short
)
assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc',
current_time=datetime.utcnow(), entry_tag=None) is True
current_time=datetime.utcnow(),
side=side, entry_tag=None) is True
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc', sell_reason='roi',
current_time=datetime.utcnow()) is True
rate=20000, time_in_force='gtc', exit_reason='roi',
sell_reason='roi',
current_time=datetime.utcnow(),
side=side) is True
assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(),
current_rate=20_000, current_profit=0.05) == strategy.stoploss

View File

@@ -11,22 +11,21 @@ from pandas import DataFrame
from freqtrade.configuration import TimeRange
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import load_data
from freqtrade.enums import SellType
from freqtrade.enums import ExitCheckTuple, ExitType, SignalDirection
from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.optimize.space import SKDecimal
from freqtrade.persistence import PairLocks, Trade
from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.hyper import (BaseParameter, BooleanParameter, CategoricalParameter,
DecimalParameter, IntParameter, RealParameter)
from freqtrade.strategy.interface import SellCheckTuple
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from tests.conftest import log_has, log_has_re
from tests.conftest import CURRENT_TEST_STRATEGY, TRADE_SIDES, log_has, log_has_re
from .strats.strategy_test_v2 import StrategyTestV2
from .strats.strategy_test_v3 import StrategyTestV3
# Avoid to reinit the same object again and again
_STRATEGY = StrategyTestV2(config={})
_STRATEGY = StrategyTestV3(config={})
_STRATEGY.dp = DataProvider({}, None, None)
@@ -34,47 +33,72 @@ def test_returns_latest_signal(ohlcv_history):
ohlcv_history.loc[1, 'date'] = arrow.utcnow()
# Take a copy to correctly modify the call
mocked_history = ohlcv_history.copy()
mocked_history['sell'] = 0
mocked_history['buy'] = 0
mocked_history['enter_long'] = 0
mocked_history['exit_long'] = 0
mocked_history['enter_short'] = 0
mocked_history['exit_short'] = 0
# Set tags in lines that don't matter to test nan in the sell line
mocked_history.loc[0, 'buy_tag'] = 'wrong_line'
mocked_history.loc[0, 'enter_tag'] = 'wrong_line'
mocked_history.loc[0, 'exit_tag'] = 'wrong_line'
mocked_history.loc[1, 'exit_long'] = 1
mocked_history.loc[1, 'sell'] = 1
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, True, None)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False, None)
mocked_history.loc[1, 'exit_long'] = 0
mocked_history.loc[1, 'enter_long'] = 1
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None, None)
mocked_history.loc[1, 'sell'] = 0
mocked_history.loc[1, 'buy'] = 1
assert _STRATEGY.get_entry_signal(
'ETH/BTC', '5m', mocked_history) == (SignalDirection.LONG, None)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (True, False, None)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False, None)
mocked_history.loc[1, 'exit_long'] = 0
mocked_history.loc[1, 'enter_long'] = 0
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None, None)
mocked_history.loc[1, 'sell'] = 0
mocked_history.loc[1, 'buy'] = 0
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False, None)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False, None)
mocked_history.loc[1, 'exit_long'] = 0
mocked_history.loc[1, 'enter_long'] = 1
mocked_history.loc[1, 'enter_tag'] = 'buy_signal_01'
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None, None)
mocked_history.loc[1, 'sell'] = 0
mocked_history.loc[1, 'buy'] = 1
mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01'
assert _STRATEGY.get_entry_signal(
'ETH/BTC', '5m', mocked_history) == (SignalDirection.LONG, 'buy_signal_01')
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (True, False, None)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False, None)
assert _STRATEGY.get_signal(
'ETH/BTC',
'5m',
mocked_history) == (
True,
False,
'buy_signal_01',
None)
mocked_history.loc[1, 'exit_long'] = 0
mocked_history.loc[1, 'enter_long'] = 0
mocked_history.loc[1, 'enter_short'] = 1
mocked_history.loc[1, 'exit_short'] = 0
mocked_history.loc[1, 'enter_tag'] = 'sell_signal_01'
mocked_history.loc[1, 'buy_tag'] = None
mocked_history.loc[1, 'exit_tag'] = 'sell_signal_01'
# Don't provide short signal while in spot mode
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
assert _STRATEGY.get_signal(
'ETH/BTC',
'5m',
mocked_history) == (
True,
False,
None,
'sell_signal_01')
_STRATEGY.config['trading_mode'] = 'futures'
# Short signal get's ignored as can_short is not set.
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
_STRATEGY.can_short = True
assert _STRATEGY.get_entry_signal(
'ETH/BTC', '5m', mocked_history) == (SignalDirection.SHORT, 'sell_signal_01')
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False, None)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (True, False, None)
mocked_history.loc[1, 'enter_short'] = 0
mocked_history.loc[1, 'exit_short'] = 1
mocked_history.loc[1, 'exit_tag'] = 'sell_signal_02'
assert _STRATEGY.get_entry_signal(
'ETH/BTC', '5m', mocked_history) == (None, None)
assert _STRATEGY.get_exit_signal(
'ETH/BTC', '5m', mocked_history) == (False, False, 'sell_signal_02')
assert _STRATEGY.get_exit_signal(
'ETH/BTC', '5m', mocked_history, True) == (False, True, 'sell_signal_02')
_STRATEGY.can_short = False
_STRATEGY.config['trading_mode'] = 'spot'
def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
@@ -90,25 +114,18 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
assert log_has('Empty dataframe for pair ETH/BTC', caplog)
def test_get_signal_empty(default_conf, mocker, caplog):
assert (False, False, None, None) == _STRATEGY.get_signal(
def test_get_signal_empty(default_conf, caplog):
assert (None, None) == _STRATEGY.get_latest_candle(
'foo', default_conf['timeframe'], DataFrame()
)
assert log_has('Empty candle (OHLCV) data for pair foo', caplog)
caplog.clear()
assert (
False,
False,
None,
None) == _STRATEGY.get_signal(
'bar',
default_conf['timeframe'],
None)
assert (None, None) == _STRATEGY.get_latest_candle('bar', default_conf['timeframe'], None)
assert log_has('Empty candle (OHLCV) data for pair bar', caplog)
caplog.clear()
assert (False, False, None, None) == _STRATEGY.get_signal(
assert (None, None) == _STRATEGY.get_latest_candle(
'baz',
default_conf['timeframe'],
DataFrame([])
@@ -116,7 +133,7 @@ def test_get_signal_empty(default_conf, mocker, caplog):
assert log_has('Empty candle (OHLCV) data for pair baz', caplog)
def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ohlcv_history):
def test_get_signal_exception_valueerror(mocker, caplog, ohlcv_history):
caplog.set_level(logging.INFO)
mocker.patch.object(_STRATEGY.dp, 'ohlcv', return_value=ohlcv_history)
mocker.patch.object(
@@ -141,14 +158,14 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history):
ohlcv_history.loc[1, 'date'] = arrow.utcnow().shift(minutes=-16)
# Take a copy to correctly modify the call
mocked_history = ohlcv_history.copy()
mocked_history['sell'] = 0
mocked_history['buy'] = 0
mocked_history.loc[1, 'buy'] = 1
mocked_history['exit_long'] = 0
mocked_history['enter_long'] = 0
mocked_history.loc[1, 'enter_long'] = 1
caplog.set_level(logging.INFO)
mocker.patch.object(_STRATEGY, 'assert_df')
assert (False, False, None, None) == _STRATEGY.get_signal(
assert (None, None) == _STRATEGY.get_latest_candle(
'xyz',
default_conf['timeframe'],
mocked_history
@@ -164,13 +181,13 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
mocked_history = ohlcv_history.copy()
# Intentionally don't set sell column
# mocked_history['sell'] = 0
mocked_history['buy'] = 0
mocked_history.loc[1, 'buy'] = 1
mocked_history['enter_long'] = 0
mocked_history.loc[1, 'enter_long'] = 1
caplog.set_level(logging.INFO)
mocker.patch.object(_STRATEGY, 'assert_df')
assert (True, False, None, None) == _STRATEGY.get_signal(
assert (SignalDirection.LONG, None) == _STRATEGY.get_entry_signal(
'xyz',
default_conf['timeframe'],
mocked_history
@@ -178,7 +195,6 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
def test_ignore_expired_candle(default_conf):
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.ignore_buying_expired_candle_after = 60
@@ -186,17 +202,21 @@ def test_ignore_expired_candle(default_conf):
# Add 1 candle length as the "latest date" defines candle open.
current_time = latest_date + timedelta(seconds=80 + 300)
assert strategy.ignore_expired_candle(latest_date=latest_date,
current_time=current_time,
timeframe_seconds=300,
buy=True) is True
assert strategy.ignore_expired_candle(
latest_date=latest_date,
current_time=current_time,
timeframe_seconds=300,
enter=True
) is True
current_time = latest_date + timedelta(seconds=30 + 300)
assert not strategy.ignore_expired_candle(latest_date=latest_date,
current_time=current_time,
timeframe_seconds=300,
buy=True) is True
assert not strategy.ignore_expired_candle(
latest_date=latest_date,
current_time=current_time,
timeframe_seconds=300,
enter=True
) is True
def test_assert_df_raise(mocker, caplog, ohlcv_history):
@@ -221,8 +241,8 @@ def test_assert_df_raise(mocker, caplog, ohlcv_history):
def test_assert_df(ohlcv_history, caplog):
df_len = len(ohlcv_history) - 1
ohlcv_history.loc[:, 'buy'] = 0
ohlcv_history.loc[:, 'sell'] = 0
ohlcv_history.loc[:, 'enter_long'] = 0
ohlcv_history.loc[:, 'exit_long'] = 0
# Ensure it's running when passed correctly
_STRATEGY.assert_df(ohlcv_history, len(ohlcv_history),
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[df_len, 'date'])
@@ -245,8 +265,8 @@ def test_assert_df(ohlcv_history, caplog):
_STRATEGY.assert_df(None, len(ohlcv_history),
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date'])
with pytest.raises(StrategyError,
match="Buy column not set"):
_STRATEGY.assert_df(ohlcv_history.drop('buy', axis=1), len(ohlcv_history),
match="enter_long/buy column not set."):
_STRATEGY.assert_df(ohlcv_history.drop('enter_long', axis=1), len(ohlcv_history),
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date'])
_STRATEGY.disable_dataframe_checks = True
@@ -259,7 +279,6 @@ def test_assert_df(ohlcv_history, caplog):
def test_advise_all_indicators(default_conf, testdatadir) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
timerange = TimeRange.parse_timerange('1510694220-1510700340')
@@ -270,7 +289,6 @@ def test_advise_all_indicators(default_conf, testdatadir) -> None:
def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators')
timerange = TimeRange.parse_timerange('1510694220-1510700340')
@@ -288,7 +306,6 @@ def test_min_roi_reached(default_conf, fee) -> None:
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
{0: 0.1, 20: 0.05, 55: 0.01}]
for roi in min_roi_list:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = roi
trade = Trade(
@@ -327,7 +344,6 @@ def test_min_roi_reached2(default_conf, fee) -> None:
},
]
for roi in min_roi_list:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = roi
trade = Trade(
@@ -362,7 +378,6 @@ def test_min_roi_reached3(default_conf, fee) -> None:
30: 0.05,
55: 0.30,
}
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = min_roi
trade = Trade(
@@ -394,29 +409,27 @@ def test_min_roi_reached3(default_conf, fee) -> None:
'profit,adjusted,expected,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, SellType.NONE, False, False, 0.3, 0.9, SellType.NONE, None),
(0.2, 0.9, SellType.NONE, False, False, -0.2, 0.9, SellType.STOP_LOSS, None),
(0.2, 1.14, SellType.NONE, True, False, 0.05, 1.14, SellType.TRAILING_STOP_LOSS, None),
(0.01, 0.96, SellType.NONE, True, False, 0.05, 1, SellType.NONE, None),
(0.05, 1, SellType.NONE, True, False, -0.01, 1, SellType.TRAILING_STOP_LOSS, None),
(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),
# Default custom case - trails with 10%
(0.05, 0.95, SellType.NONE, False, True, -0.02, 0.95, SellType.NONE, None),
(0.05, 0.95, SellType.NONE, False, True, -0.06, 0.95, SellType.TRAILING_STOP_LOSS, None),
(0.05, 1, SellType.NONE, False, True, -0.06, 1, SellType.TRAILING_STOP_LOSS,
(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,
lambda **kwargs: -0.05),
(0.05, 1, SellType.NONE, False, True, 0.09, 1.04, SellType.NONE,
(0.05, 1, ExitType.NONE, False, True, 0.09, 1.04, ExitType.NONE,
lambda **kwargs: -0.05),
(0.05, 0.95, SellType.NONE, False, True, 0.09, 0.98, SellType.NONE,
(0.05, 0.95, ExitType.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, SellType.NONE, False, True, 0.09, 0.9, SellType.NONE,
(0.05, 0.9, ExitType.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,
profit2, adjusted2, expected2, custom_stop) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
trade = Trade(
pair='ETH/BTC',
@@ -441,31 +454,29 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
sl_flag = strategy.stop_loss_reached(current_rate=current_rate, trade=trade,
current_time=now, current_profit=profit,
force_stoploss=0, high=None)
assert isinstance(sl_flag, SellCheckTuple)
assert sl_flag.sell_type == expected
if expected == SellType.NONE:
assert sl_flag.sell_flag is False
assert isinstance(sl_flag, ExitCheckTuple)
assert sl_flag.exit_type == expected
if expected == ExitType.NONE:
assert sl_flag.exit_flag is False
else:
assert sl_flag.sell_flag is True
assert sl_flag.exit_flag is True
assert round(trade.stop_loss, 2) == adjusted
current_rate2 = trade.open_rate * (1 + profit2)
sl_flag = strategy.stop_loss_reached(current_rate=current_rate2, trade=trade,
current_time=now, current_profit=profit2,
force_stoploss=0, high=None)
assert sl_flag.sell_type == expected2
if expected2 == SellType.NONE:
assert sl_flag.sell_flag is False
assert sl_flag.exit_type == expected2
if expected2 == ExitType.NONE:
assert sl_flag.exit_flag is False
else:
assert sl_flag.sell_flag is True
assert sl_flag.exit_flag is True
assert round(trade.stop_loss, 2) == adjusted2
strategy.custom_stoploss = original_stopvalue
def test_custom_sell(default_conf, fee, caplog) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
def test_custom_exit(default_conf, fee, caplog) -> None:
strategy = StrategyResolver.load_strategy(default_conf)
trade = Trade(
@@ -480,50 +491,84 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
)
now = arrow.utcnow().datetime
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res.sell_flag is False
assert res.sell_type == SellType.NONE
assert res.exit_flag is False
assert res.exit_type == ExitType.NONE
strategy.custom_sell = MagicMock(return_value=True)
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
assert res.sell_flag is True
assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_reason == 'custom_sell'
strategy.custom_exit = MagicMock(return_value=True)
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res.exit_flag is True
assert res.exit_type == ExitType.CUSTOM_SELL
assert res.exit_reason == 'custom_sell'
strategy.custom_sell = MagicMock(return_value='hello world')
strategy.custom_exit = MagicMock(return_value='hello world')
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_flag is True
assert res.sell_reason == 'hello world'
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res.exit_type == ExitType.CUSTOM_SELL
assert res.exit_flag is True
assert res.exit_reason == 'hello world'
caplog.clear()
strategy.custom_sell = MagicMock(return_value='h' * 100)
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_flag is True
assert res.sell_reason == 'h' * 64
assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog)
strategy.custom_exit = MagicMock(return_value='h' * 100)
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res.exit_type == ExitType.CUSTOM_SELL
assert res.exit_flag is True
assert res.exit_reason == 'h' * 64
assert log_has_re('Custom sell reason returned from custom_exit is too long.*', caplog)
@pytest.mark.parametrize('side', TRADE_SIDES)
def test_leverage_callback(default_conf, side) -> None:
default_conf['strategy'] = 'StrategyTestV2'
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.leverage(
pair='XRP/USDT',
current_time=datetime.now(timezone.utc),
current_rate=2.2,
proposed_leverage=1.0,
max_leverage=5.0,
side=side,
) == 1
default_conf['strategy'] = CURRENT_TEST_STRATEGY
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.leverage(
pair='XRP/USDT',
current_time=datetime.now(timezone.utc),
current_rate=2.2,
proposed_leverage=1.0,
max_leverage=5.0,
side=side,
) == 3
def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x)
buy_mock = MagicMock(side_effect=lambda x, meta: x)
sell_mock = MagicMock(side_effect=lambda x, meta: x)
entry_mock = MagicMock(side_effect=lambda x, meta: x)
exit_mock = MagicMock(side_effect=lambda x, meta: x)
mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy',
advise_indicators=ind_mock,
advise_buy=buy_mock,
advise_sell=sell_mock,
advise_entry=entry_mock,
advise_exit=exit_mock,
)
strategy = StrategyTestV2({})
strategy = StrategyTestV3({})
strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
assert ind_mock.call_count == 1
assert buy_mock.call_count == 1
assert buy_mock.call_count == 1
assert entry_mock.call_count == 1
assert entry_mock.call_count == 1
assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
@@ -532,8 +577,8 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
# No analysis happens as process_only_new_candles is true
assert ind_mock.call_count == 2
assert buy_mock.call_count == 2
assert buy_mock.call_count == 2
assert entry_mock.call_count == 2
assert entry_mock.call_count == 2
assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
@@ -541,16 +586,16 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> None:
caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x)
buy_mock = MagicMock(side_effect=lambda x, meta: x)
sell_mock = MagicMock(side_effect=lambda x, meta: x)
entry_mock = MagicMock(side_effect=lambda x, meta: x)
exit_mock = MagicMock(side_effect=lambda x, meta: x)
mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy',
advise_indicators=ind_mock,
advise_buy=buy_mock,
advise_sell=sell_mock,
advise_entry=entry_mock,
advise_exit=exit_mock,
)
strategy = StrategyTestV2({})
strategy = StrategyTestV3({})
strategy.dp = DataProvider({}, None, None)
strategy.process_only_new_candles = True
@@ -560,8 +605,8 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
assert 'close' in ret.columns
assert isinstance(ret, DataFrame)
assert ind_mock.call_count == 1
assert buy_mock.call_count == 1
assert buy_mock.call_count == 1
assert entry_mock.call_count == 1
assert entry_mock.call_count == 1
assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
caplog.clear()
@@ -569,20 +614,19 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
ret = strategy._analyze_ticker_internal(ohlcv_history, {'pair': 'ETH/BTC'})
# No analysis happens as process_only_new_candles is true
assert ind_mock.call_count == 1
assert buy_mock.call_count == 1
assert buy_mock.call_count == 1
assert entry_mock.call_count == 1
assert entry_mock.call_count == 1
# only skipped analyze adds buy and sell columns, otherwise it's all mocked
assert 'buy' in ret.columns
assert 'sell' in ret.columns
assert ret['buy'].sum() == 0
assert ret['sell'].sum() == 0
assert 'enter_long' in ret.columns
assert 'exit_long' in ret.columns
assert ret['enter_long'].sum() == 0
assert ret['exit_long'].sum() == 0
assert not log_has('TA Analysis Launched', caplog)
assert log_has('Skipping TA Analysis for already analyzed candle', caplog)
@pytest.mark.usefixtures("init_persistence")
def test_is_pair_locked(default_conf):
default_conf.update({'strategy': 'StrategyTestV2'})
PairLocks.timeframe = default_conf['timeframe']
PairLocks.use_db = True
strategy = StrategyResolver.load_strategy(default_conf)

View File

@@ -5,6 +5,8 @@ import pandas as pd
import pytest
from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import CandleType
from freqtrade.resolvers.strategy_resolver import StrategyResolver
from freqtrade.strategy import (merge_informative_pair, stoploss_from_absolute, stoploss_from_open,
timeframe_to_minutes)
from tests.conftest import get_patched_exchange
@@ -108,84 +110,123 @@ def test_stoploss_from_open():
[1, 100, 30],
[100, 10000, 30],
]
current_profit_range = [-0.99, 2, 30]
# profit range for long is [-1, inf] while for shorts is [-inf, 1]
current_profit_range_dict = {'long': [-0.99, 2, 30], 'short': [-2.0, 0.99, 30]}
desired_stop_range = [-0.50, 0.50, 30]
for open_range in open_price_ranges:
for open_price in np.linspace(*open_range):
for desired_stop in np.linspace(*desired_stop_range):
for side, current_profit_range in current_profit_range_dict.items():
for open_range in open_price_ranges:
for open_price in np.linspace(*open_range):
for desired_stop in np.linspace(*desired_stop_range):
# -1 is not a valid current_profit, should return 1
assert stoploss_from_open(desired_stop, -1) == 1
for current_profit in np.linspace(*current_profit_range):
current_price = open_price * (1 + current_profit)
expected_stop_price = open_price * (1 + desired_stop)
stoploss = stoploss_from_open(desired_stop, current_profit)
assert stoploss >= 0
assert stoploss <= 1
stop_price = current_price * (1 - stoploss)
# there is no correct answer if the expected stop price is above
# the current price
if expected_stop_price > current_price:
assert stoploss == 0
if side == 'long':
# -1 is not a valid current_profit, should return 1
assert stoploss_from_open(desired_stop, -1) == 1
else:
assert isclose(stop_price, expected_stop_price, rel_tol=0.00001)
# 1 is not a valid current_profit for shorts, should return 1
assert stoploss_from_open(desired_stop, 1, True) == 1
for current_profit in np.linspace(*current_profit_range):
if side == 'long':
current_price = open_price * (1 + current_profit)
expected_stop_price = open_price * (1 + desired_stop)
stoploss = stoploss_from_open(desired_stop, current_profit)
stop_price = current_price * (1 - stoploss)
else:
current_price = open_price * (1 - current_profit)
expected_stop_price = open_price * (1 - desired_stop)
stoploss = stoploss_from_open(desired_stop, current_profit, True)
stop_price = current_price * (1 + stoploss)
assert stoploss >= 0
# Technically the formula can yield values greater than 1 for shorts
# eventhough it doesn't make sense because the position would be liquidated
if side == 'long':
assert stoploss <= 1
# there is no correct answer if the expected stop price is above
# the current price
if ((side == 'long' and expected_stop_price > current_price)
or (side == 'short' and expected_stop_price < current_price)):
assert stoploss == 0
else:
assert isclose(stop_price, expected_stop_price, rel_tol=0.00001)
def test_stoploss_from_absolute():
assert stoploss_from_absolute(90, 100) == 1 - (90 / 100)
assert stoploss_from_absolute(100, 100) == 0
assert stoploss_from_absolute(110, 100) == 0
assert stoploss_from_absolute(100, 0) == 1
assert stoploss_from_absolute(0, 100) == 1
assert pytest.approx(stoploss_from_absolute(90, 100)) == 1 - (90 / 100)
assert pytest.approx(stoploss_from_absolute(90, 100)) == 0.1
assert pytest.approx(stoploss_from_absolute(95, 100)) == 0.05
assert pytest.approx(stoploss_from_absolute(100, 100)) == 0
assert pytest.approx(stoploss_from_absolute(110, 100)) == 0
assert pytest.approx(stoploss_from_absolute(100, 0)) == 1
assert pytest.approx(stoploss_from_absolute(0, 100)) == 1
assert pytest.approx(stoploss_from_absolute(90, 100, True)) == 0
assert pytest.approx(stoploss_from_absolute(100, 100, True)) == 0
assert pytest.approx(stoploss_from_absolute(110, 100, True)) == -(1 - (110/100))
assert pytest.approx(stoploss_from_absolute(110, 100, True)) == 0.1
assert pytest.approx(stoploss_from_absolute(105, 100, True)) == 0.05
assert pytest.approx(stoploss_from_absolute(100, 0, True)) == 1
assert pytest.approx(stoploss_from_absolute(0, 100, True)) == 0
assert pytest.approx(stoploss_from_absolute(100, 1, True)) == 1
def test_informative_decorator(mocker, default_conf):
@pytest.mark.parametrize('trading_mode', ['futures', 'spot'])
def test_informative_decorator(mocker, default_conf_usdt, trading_mode):
candle_def = CandleType.get_default(trading_mode)
default_conf_usdt['candle_type_def'] = candle_def
test_data_5m = generate_test_data('5m', 40)
test_data_30m = generate_test_data('30m', 40)
test_data_1h = generate_test_data('1h', 40)
data = {
('XRP/USDT', '5m'): test_data_5m,
('XRP/USDT', '30m'): test_data_30m,
('XRP/USDT', '1h'): test_data_1h,
('LTC/USDT', '5m'): test_data_5m,
('LTC/USDT', '30m'): test_data_30m,
('LTC/USDT', '1h'): test_data_1h,
('NEO/USDT', '30m'): test_data_30m,
('NEO/USDT', '5m'): test_data_5m,
('NEO/USDT', '1h'): test_data_1h,
('ETH/USDT', '1h'): test_data_1h,
('ETH/USDT', '30m'): test_data_30m,
('ETH/BTC', '1h'): test_data_1h,
('XRP/USDT', '5m', candle_def): test_data_5m,
('XRP/USDT', '30m', candle_def): test_data_30m,
('XRP/USDT', '1h', candle_def): test_data_1h,
('LTC/USDT', '5m', candle_def): test_data_5m,
('LTC/USDT', '30m', candle_def): test_data_30m,
('LTC/USDT', '1h', candle_def): test_data_1h,
('NEO/USDT', '30m', candle_def): test_data_30m,
('NEO/USDT', '5m', CandleType.SPOT): test_data_5m, # Explicit request with '' as candletype
('NEO/USDT', '15m', candle_def): test_data_5m, # Explicit request with '' as candletype
('NEO/USDT', '1h', candle_def): test_data_1h,
('ETH/USDT', '1h', candle_def): test_data_1h,
('ETH/USDT', '30m', candle_def): test_data_30m,
('ETH/BTC', '1h', CandleType.SPOT): test_data_1h, # Explicitly selected as spot
}
from .strats.informative_decorator_strategy import InformativeDecoratorTest
default_conf['stake_currency'] = 'USDT'
strategy = InformativeDecoratorTest(config=default_conf)
exchange = get_patched_exchange(mocker, default_conf)
default_conf_usdt['strategy'] = 'InformativeDecoratorTest'
strategy = StrategyResolver.load_strategy(default_conf_usdt)
exchange = get_patched_exchange(mocker, default_conf_usdt)
strategy.dp = DataProvider({}, exchange, None)
mocker.patch.object(strategy.dp, 'current_whitelist', return_value=[
'XRP/USDT', 'LTC/USDT', 'NEO/USDT'
])
assert len(strategy._ft_informative) == 6 # Equal to number of decorators used
informative_pairs = [('XRP/USDT', '1h'), ('LTC/USDT', '1h'), ('XRP/USDT', '30m'),
('LTC/USDT', '30m'), ('NEO/USDT', '1h'), ('NEO/USDT', '30m'),
('NEO/USDT', '5m'), ('ETH/BTC', '1h'), ('ETH/USDT', '30m')]
informative_pairs = [
('XRP/USDT', '1h', candle_def),
('LTC/USDT', '1h', candle_def),
('XRP/USDT', '30m', candle_def),
('LTC/USDT', '30m', candle_def),
('NEO/USDT', '1h', candle_def),
('NEO/USDT', '30m', candle_def),
('NEO/USDT', '5m', candle_def),
('NEO/USDT', '15m', candle_def),
('NEO/USDT', '2h', CandleType.FUTURES),
('ETH/BTC', '1h', CandleType.SPOT), # One candle remains as spot
('ETH/USDT', '30m', candle_def)]
for inf_pair in informative_pairs:
assert inf_pair in strategy.gather_informative_pairs()
def test_historic_ohlcv(pair, timeframe):
return data[(pair, timeframe or strategy.timeframe)].copy()
def test_historic_ohlcv(pair, timeframe, candle_type):
return data[
(pair, timeframe or strategy.timeframe, CandleType.from_string(candle_type))].copy()
mocker.patch('freqtrade.data.dataprovider.DataProvider.historic_ohlcv',
side_effect=test_historic_ohlcv)
analyzed = strategy.advise_all_indicators(
{p: data[(p, strategy.timeframe)] for p in ('XRP/USDT', 'LTC/USDT')})
{p: data[(p, strategy.timeframe, candle_def)] for p in ('XRP/USDT', 'LTC/USDT')})
expected_columns = [
'rsi_1h', 'rsi_30m', # Stacked informative decorators
'neo_usdt_rsi_1h', # NEO 1h informative

View File

@@ -10,7 +10,7 @@ from pandas import DataFrame
from freqtrade.exceptions import OperationalException
from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.interface import IStrategy
from tests.conftest import log_has, log_has_re
from tests.conftest import CURRENT_TEST_STRATEGY, log_has, log_has_re
def test_search_strategy():
@@ -18,7 +18,7 @@ def test_search_strategy():
s, _ = StrategyResolver._search_object(
directory=default_location,
object_name='StrategyTestV2',
object_name=CURRENT_TEST_STRATEGY,
add_source=True,
)
assert issubclass(s, IStrategy)
@@ -35,7 +35,7 @@ def test_search_all_strategies_no_failed():
directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
assert isinstance(strategies, list)
assert len(strategies) == 4
assert len(strategies) == 6
assert isinstance(strategies[0], dict)
@@ -43,10 +43,10 @@ def test_search_all_strategies_with_failed():
directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
assert isinstance(strategies, list)
assert len(strategies) == 5
assert len(strategies) == 7
# with enum_failed=True search_all_objects() shall find 2 good strategies
# and 1 which fails to load
assert len([x for x in strategies if x['class'] is not None]) == 4
assert len([x for x in strategies if x['class'] is not None]) == 6
assert len([x for x in strategies if x['class'] is None]) == 1
@@ -74,10 +74,10 @@ def test_load_strategy_base64(result, caplog, default_conf):
def test_load_strategy_invalid_directory(result, caplog, default_conf):
default_conf['strategy'] = 'StrategyTestV2'
default_conf['strategy'] = 'StrategyTestV3'
extra_dir = Path.cwd() / 'some/path'
with pytest.raises(OperationalException):
StrategyResolver._load_strategy('StrategyTestV2', config=default_conf,
StrategyResolver._load_strategy(CURRENT_TEST_STRATEGY, config=default_conf,
extra_dir=extra_dir)
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
@@ -99,8 +99,10 @@ def test_load_strategy_noname(default_conf):
StrategyResolver.load_strategy(default_conf)
def test_strategy(result, default_conf):
default_conf.update({'strategy': 'StrategyTestV2'})
@pytest.mark.filterwarnings("ignore:deprecated")
@pytest.mark.parametrize('strategy_name', ['StrategyTestV2', 'TestStrategyLegacyV1'])
def test_strategy_pre_v3(result, default_conf, strategy_name):
default_conf.update({'strategy': strategy_name})
strategy = StrategyResolver.load_strategy(default_conf)
metadata = {'pair': 'ETH/BTC'}
@@ -116,17 +118,45 @@ def test_strategy(result, default_conf):
df_indicators = strategy.advise_indicators(result, metadata=metadata)
assert 'adx' in df_indicators
dataframe = strategy.advise_buy(df_indicators, metadata=metadata)
assert 'buy' in dataframe.columns
dataframe = strategy.advise_entry(df_indicators, metadata=metadata)
assert 'buy' not in dataframe.columns
assert 'enter_long' in dataframe.columns
dataframe = strategy.advise_sell(df_indicators, metadata=metadata)
assert 'sell' in dataframe.columns
dataframe = strategy.advise_exit(df_indicators, metadata=metadata)
assert 'sell' not in dataframe.columns
assert 'exit_long' in dataframe.columns
def test_strategy_can_short(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': CURRENT_TEST_STRATEGY,
})
strat = StrategyResolver.load_strategy(default_conf)
assert isinstance(strat, IStrategy)
default_conf['strategy'] = 'StrategyTestV3Futures'
with pytest.raises(ImportError, match=""):
StrategyResolver.load_strategy(default_conf)
default_conf['trading_mode'] = 'futures'
strat = StrategyResolver.load_strategy(default_conf)
assert isinstance(strat, IStrategy)
def test_strategy_implements_populate_entry(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': "StrategyTestV2",
})
default_conf['trading_mode'] = 'futures'
with pytest.raises(OperationalException, match="`populate_entry_trend` must be implemented."):
StrategyResolver.load_strategy(default_conf)
def test_strategy_override_minimal_roi(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'minimal_roi': {
"20": 0.1,
"0": 0.5
@@ -143,7 +173,7 @@ def test_strategy_override_minimal_roi(caplog, default_conf):
def test_strategy_override_stoploss(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'stoploss': -0.5
})
strategy = StrategyResolver.load_strategy(default_conf)
@@ -155,7 +185,7 @@ def test_strategy_override_stoploss(caplog, default_conf):
def test_strategy_override_trailing_stop(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'trailing_stop': True
})
strategy = StrategyResolver.load_strategy(default_conf)
@@ -168,7 +198,7 @@ def test_strategy_override_trailing_stop(caplog, default_conf):
def test_strategy_override_trailing_stop_positive(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'trailing_stop_positive': -0.1,
'trailing_stop_positive_offset': -0.2
@@ -188,7 +218,7 @@ def test_strategy_override_timeframe(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'timeframe': 60,
'stake_currency': 'ETH'
})
@@ -204,7 +234,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'process_only_new_candles': True
})
strategy = StrategyResolver.load_strategy(default_conf)
@@ -218,32 +248,32 @@ def test_strategy_override_order_types(caplog, default_conf):
caplog.set_level(logging.INFO)
order_types = {
'buy': 'market',
'sell': 'limit',
'entry': 'market',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True,
}
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'order_types': order_types
})
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.order_types
for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']:
for method in ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']:
assert strategy.order_types[method] == order_types[method]
assert log_has("Override strategy 'order_types' with value in config file:"
" {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit',"
" {'entry': 'market', 'exit': 'limit', 'stoploss': 'limit',"
" 'stoploss_on_exchange': True}.", caplog)
default_conf.update({
'strategy': 'StrategyTestV2',
'order_types': {'buy': 'market'}
'strategy': CURRENT_TEST_STRATEGY,
'order_types': {'exit': 'market'}
})
# Raise error for invalid configuration
with pytest.raises(ImportError,
match=r"Impossible to load Strategy 'StrategyTestV2'. "
match=r"Impossible to load Strategy '" + CURRENT_TEST_STRATEGY + "'. "
r"Order-types mapping is incomplete."):
StrategyResolver.load_strategy(default_conf)
@@ -252,38 +282,38 @@ def test_strategy_override_order_tif(caplog, default_conf):
caplog.set_level(logging.INFO)
order_time_in_force = {
'buy': 'fok',
'sell': 'gtc',
'entry': 'fok',
'exit': 'gtc',
}
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'order_time_in_force': order_time_in_force
})
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.order_time_in_force
for method in ['buy', 'sell']:
for method in ['entry', 'exit']:
assert strategy.order_time_in_force[method] == order_time_in_force[method]
assert log_has("Override strategy 'order_time_in_force' with value in config file:"
" {'buy': 'fok', 'sell': 'gtc'}.", caplog)
" {'entry': 'fok', 'exit': 'gtc'}.", caplog)
default_conf.update({
'strategy': 'StrategyTestV2',
'order_time_in_force': {'buy': 'fok'}
'strategy': CURRENT_TEST_STRATEGY,
'order_time_in_force': {'entry': 'fok'}
})
# Raise error for invalid configuration
with pytest.raises(ImportError,
match=r"Impossible to load Strategy 'StrategyTestV2'. "
r"Order-time-in-force mapping is incomplete."):
match=f"Impossible to load Strategy '{CURRENT_TEST_STRATEGY}'. "
"Order-time-in-force mapping is incomplete."):
StrategyResolver.load_strategy(default_conf)
def test_strategy_override_use_sell_signal(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
})
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.use_sell_signal
@@ -293,7 +323,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
assert default_conf['use_sell_signal']
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'use_sell_signal': False,
})
strategy = StrategyResolver.load_strategy(default_conf)
@@ -306,7 +336,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
def test_strategy_override_use_sell_profit_only(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
})
strategy = StrategyResolver.load_strategy(default_conf)
assert not strategy.sell_profit_only
@@ -316,7 +346,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
assert not default_conf['sell_profit_only']
default_conf.update({
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'sell_profit_only': True,
})
strategy = StrategyResolver.load_strategy(default_conf)
@@ -344,7 +374,7 @@ def test_deprecate_populate_indicators(result, default_conf):
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
strategy.advise_buy(indicators, {'pair': 'ETH/BTC'})
strategy.advise_entry(indicators, {'pair': 'ETH/BTC'})
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \
@@ -353,7 +383,7 @@ def test_deprecate_populate_indicators(result, default_conf):
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
strategy.advise_sell(indicators, {'pair': 'ETH_BTC'})
strategy.advise_exit(indicators, {'pair': 'ETH_BTC'})
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \
@@ -361,7 +391,50 @@ def test_deprecate_populate_indicators(result, default_conf):
@pytest.mark.filterwarnings("ignore:deprecated")
def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
def test_missing_implements(default_conf):
default_location = Path(__file__).parent / "strats/broken_strats"
default_conf.update({'strategy': 'TestStrategyNoImplements',
'strategy_path': default_location})
with pytest.raises(OperationalException,
match=r"`populate_entry_trend` or `populate_buy_trend`.*"):
StrategyResolver.load_strategy(default_conf)
default_conf['strategy'] = 'TestStrategyNoImplementSell'
with pytest.raises(OperationalException,
match=r"`populate_exit_trend` or `populate_sell_trend`.*"):
StrategyResolver.load_strategy(default_conf)
# Futures mode is more strict ...
default_conf['trading_mode'] = 'futures'
with pytest.raises(OperationalException,
match=r"`populate_exit_trend` must be implemented.*"):
StrategyResolver.load_strategy(default_conf)
default_conf['strategy'] = 'TestStrategyNoImplements'
with pytest.raises(OperationalException,
match=r"`populate_entry_trend` must be implemented.*"):
StrategyResolver.load_strategy(default_conf)
default_conf['strategy'] = 'TestStrategyImplementCustomSell'
with pytest.raises(OperationalException,
match=r"Please migrate your implementation of `custom_sell`.*"):
StrategyResolver.load_strategy(default_conf)
default_conf['strategy'] = 'TestStrategyImplementBuyTimeout'
with pytest.raises(OperationalException,
match=r"Please migrate your implementation of `check_buy_timeout`.*"):
StrategyResolver.load_strategy(default_conf)
default_conf['strategy'] = 'TestStrategyImplementSellTimeout'
with pytest.raises(OperationalException,
match=r"Please migrate your implementation of `check_sell_timeout`.*"):
StrategyResolver.load_strategy(default_conf)
@pytest.mark.filterwarnings("ignore:deprecated")
def test_call_deprecated_function(result, default_conf, caplog):
default_location = Path(__file__).parent / "strats"
del default_conf['timeframe']
default_conf.update({'strategy': 'TestStrategyLegacyV1',
@@ -380,16 +453,16 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
assert isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns
buydf = strategy.advise_buy(result, metadata=metadata)
assert isinstance(buydf, DataFrame)
assert 'buy' in buydf.columns
enterdf = strategy.advise_entry(result, metadata=metadata)
assert isinstance(enterdf, DataFrame)
assert 'enter_long' in enterdf.columns
selldf = strategy.advise_sell(result, metadata=metadata)
assert isinstance(selldf, DataFrame)
assert 'sell' in selldf
exitdf = strategy.advise_exit(result, metadata=metadata)
assert isinstance(exitdf, DataFrame)
assert 'exit_long' in exitdf
def test_strategy_interface_versioning(result, monkeypatch, default_conf):
def test_strategy_interface_versioning(result, default_conf):
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
metadata = {'pair': 'ETH/BTC'}
@@ -404,10 +477,13 @@ def test_strategy_interface_versioning(result, monkeypatch, default_conf):
assert isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns
buydf = strategy.advise_buy(result, metadata=metadata)
assert isinstance(buydf, DataFrame)
assert 'buy' in buydf.columns
enterdf = strategy.advise_entry(result, metadata=metadata)
assert isinstance(enterdf, DataFrame)
selldf = strategy.advise_sell(result, metadata=metadata)
assert isinstance(selldf, DataFrame)
assert 'sell' in selldf
assert 'buy' not in enterdf.columns
assert 'enter_long' in enterdf.columns
exitdf = strategy.advise_exit(result, metadata=metadata)
assert isinstance(exitdf, DataFrame)
assert 'sell' not in exitdf
assert 'exit_long' in exitdf

View File

@@ -7,6 +7,7 @@ import pytest
from freqtrade.commands import Arguments
from freqtrade.commands.cli_options import check_int_nonzero, check_int_positive
from tests.conftest import CURRENT_TEST_STRATEGY
# Parse common command-line-arguments. Used for all tools
@@ -123,7 +124,7 @@ def test_parse_args_backtesting_custom() -> None:
'-c', 'test_conf.json',
'--timeframe', '1m',
'--strategy-list',
'StrategyTestV2',
CURRENT_TEST_STRATEGY,
'SampleStrategy'
]
call_args = Arguments(args).get_parsed_arg()

View File

@@ -23,7 +23,8 @@ from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.loggers import FTBufferingHandler, _set_loggers, setup_logging, setup_logging_pre
from tests.conftest import log_has, log_has_re, patched_configuration_load_config_file
from tests.conftest import (CURRENT_TEST_STRATEGY, log_has, log_has_re,
patched_configuration_load_config_file)
@pytest.fixture(scope="function")
@@ -403,7 +404,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
arglist = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
args = Arguments(arglist).get_parsed_arg()
@@ -440,7 +441,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
arglist = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar',
'--userdir', "/tmp/freqtrade",
'--timeframe', '1m',
@@ -497,7 +498,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
'--timeframe', '1m',
'--export', 'trades',
'--strategy-list',
'StrategyTestV2',
CURRENT_TEST_STRATEGY,
'TestStrategy'
]
@@ -797,8 +798,8 @@ def test_validate_max_open_trades(default_conf):
def test_validate_price_side(default_conf):
default_conf['order_types'] = {
"buy": "limit",
"sell": "limit",
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": False,
}
@@ -806,23 +807,23 @@ def test_validate_price_side(default_conf):
validate_config_consistency(default_conf)
conf = deepcopy(default_conf)
conf['order_types']['buy'] = 'market'
conf['order_types']['entry'] = 'market'
with pytest.raises(OperationalException,
match='Market buy orders require bid_strategy.price_side = "ask".'):
match='Market entry orders require entry_pricing.price_side = "other".'):
validate_config_consistency(conf)
conf = deepcopy(default_conf)
conf['order_types']['sell'] = 'market'
conf['order_types']['exit'] = 'market'
with pytest.raises(OperationalException,
match='Market sell orders require ask_strategy.price_side = "bid".'):
match='Market exit orders require exit_pricing.price_side = "other".'):
validate_config_consistency(conf)
# Validate inversed case
conf = deepcopy(default_conf)
conf['order_types']['sell'] = 'market'
conf['order_types']['buy'] = 'market'
conf['ask_strategy']['price_side'] = 'bid'
conf['bid_strategy']['price_side'] = 'ask'
conf['order_types']['exit'] = 'market'
conf['order_types']['entry'] = 'market'
conf['exit_pricing']['price_side'] = 'bid'
conf['entry_pricing']['price_side'] = 'ask'
validate_config_consistency(conf)
@@ -925,18 +926,138 @@ def test_validate_protections(default_conf, protconf, expected):
def test_validate_ask_orderbook(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf['ask_strategy']['use_order_book'] = True
conf['ask_strategy']['order_book_min'] = 2
conf['ask_strategy']['order_book_max'] = 2
conf['exit_pricing']['use_order_book'] = True
conf['exit_pricing']['order_book_min'] = 2
conf['exit_pricing']['order_book_max'] = 2
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Please use `order_book_top` instead of.*", caplog)
assert conf['ask_strategy']['order_book_top'] == 2
assert conf['exit_pricing']['order_book_top'] == 2
conf['ask_strategy']['order_book_max'] = 5
conf['exit_pricing']['order_book_max'] = 5
with pytest.raises(OperationalException,
match=r"Using order_book_max != order_book_min in ask_strategy.*"):
match=r"Using order_book_max != order_book_min in exit_pricing.*"):
validate_config_consistency(conf)
def test_validate_time_in_force(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf['order_time_in_force'] = {
'buy': 'gtc',
'sell': 'gtc',
}
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for time_in_force is.*", caplog)
assert conf['order_time_in_force']['entry'] == 'gtc'
assert conf['order_time_in_force']['exit'] == 'gtc'
conf = deepcopy(default_conf)
conf['order_time_in_force'] = {
'buy': 'gtc',
'sell': 'gtc',
}
conf['trading_mode'] = 'futures'
with pytest.raises(OperationalException,
match=r"Please migrate your time_in_force settings .* 'entry' and 'exit'\."):
validate_config_consistency(conf)
def test__validate_order_types(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf['order_types'] = {
'buy': 'limit',
'sell': 'market',
'forcesell': 'market',
'forcebuy': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False,
}
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for order_types is.*", caplog)
assert conf['order_types']['entry'] == 'limit'
assert conf['order_types']['exit'] == 'market'
assert conf['order_types']['forceentry'] == 'limit'
assert 'buy' not in conf['order_types']
assert 'sell' not in conf['order_types']
assert 'forcebuy' not in conf['order_types']
assert 'forcesell' not in conf['order_types']
conf = deepcopy(default_conf)
conf['order_types'] = {
'buy': 'limit',
'sell': 'market',
'forcesell': 'market',
'forcebuy': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False,
}
conf['trading_mode'] = 'futures'
with pytest.raises(OperationalException,
match=r"Please migrate your order_types settings to use the new wording\."):
validate_config_consistency(conf)
def test__validate_unfilledtimeout(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf['unfilledtimeout'] = {
'buy': 30,
'sell': 35,
}
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for unfilledtimeout is.*", caplog)
assert conf['unfilledtimeout']['entry'] == 30
assert conf['unfilledtimeout']['exit'] == 35
assert 'buy' not in conf['unfilledtimeout']
assert 'sell' not in conf['unfilledtimeout']
conf = deepcopy(default_conf)
conf['unfilledtimeout'] = {
'buy': 30,
'sell': 35,
}
conf['trading_mode'] = 'futures'
with pytest.raises(
OperationalException,
match=r"Please migrate your unfilledtimeout settings to use the new wording\."):
validate_config_consistency(conf)
def test__validate_pricing_rules(default_conf, caplog) -> None:
def_conf = deepcopy(default_conf)
del def_conf['entry_pricing']
del def_conf['exit_pricing']
def_conf['ask_strategy'] = {
'price_side': 'ask',
'use_order_book': True,
'bid_last_balance': 0.5
}
def_conf['bid_strategy'] = {
'price_side': 'bid',
'use_order_book': False,
'ask_last_balance': 0.7
}
conf = deepcopy(def_conf)
validate_config_consistency(conf)
assert log_has_re(
r"DEPRECATED: Using 'ask_strategy' and 'bid_strategy' is.*", caplog)
assert conf['exit_pricing']['price_side'] == 'ask'
assert conf['exit_pricing']['use_order_book'] is True
assert conf['exit_pricing']['price_last_balance'] == 0.5
assert conf['entry_pricing']['price_side'] == 'bid'
assert conf['entry_pricing']['use_order_book'] is False
assert conf['entry_pricing']['price_last_balance'] == 0.7
assert 'ask_strategy' not in conf
assert 'bid_strategy' not in conf
conf = deepcopy(def_conf)
conf['trading_mode'] = 'futures'
with pytest.raises(
OperationalException,
match=r"Please migrate your pricing settings to use the new wording\."):
validate_config_consistency(conf)
@@ -1257,11 +1378,14 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
# The value of the new setting shall have been set to the
# value of the deprecated one
assert default_conf['sectionA']['new_setting'] == 'valB'
# Old setting is removed
assert 'deprecated_setting' not in default_conf['sectionB']
caplog.clear()
# Delete new setting (deprecated exists)
del default_conf['sectionA']['new_setting']
default_conf['sectionB']['deprecated_setting'] = 'valB'
process_deprecated_setting(default_conf,
'sectionB', 'deprecated_setting',
'sectionA', 'new_setting')
@@ -1275,7 +1399,7 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
# Assign new setting
default_conf['sectionA']['new_setting'] = 'valA'
# Delete deprecated setting
del default_conf['sectionB']['deprecated_setting']
default_conf['sectionB'].pop('deprecated_setting', None)
process_deprecated_setting(default_conf,
'sectionB', 'deprecated_setting',
'sectionA', 'new_setting')
@@ -1348,15 +1472,15 @@ def test_flat_vars_to_nested_dict(caplog):
'FREQTRADE__EXCHANGE__SOME_SETTING': 'true',
'FREQTRADE__EXCHANGE__SOME_FALSE_SETTING': 'false',
'FREQTRADE__EXCHANGE__CONFIG__whatever': 'sometime',
'FREQTRADE__ASK_STRATEGY__PRICE_SIDE': 'bid',
'FREQTRADE__ASK_STRATEGY__cccc': '500',
'FREQTRADE__EXIT_PRICING__PRICE_SIDE': 'bid',
'FREQTRADE__EXIT_PRICING__cccc': '500',
'FREQTRADE__STAKE_AMOUNT': '200.05',
'FREQTRADE__TELEGRAM__CHAT_ID': '2151',
'NOT_RELEVANT': '200.0', # Will be ignored
}
expected = {
'stake_amount': 200.05,
'ask_strategy': {
'exit_pricing': {
'price_side': 'bid',
'cccc': 500,
},

File diff suppressed because it is too large Load Diff

View File

@@ -2,11 +2,10 @@ from unittest.mock import MagicMock
import pytest
from freqtrade.enums import SellType
from freqtrade.enums import ExitCheckTuple, ExitType
from freqtrade.persistence import Trade
from freqtrade.persistence.models import Order
from freqtrade.rpc.rpc import RPC
from freqtrade.strategy.interface import SellCheckTuple
from tests.conftest import get_patched_freqtradebot, patch_get_signal
@@ -53,8 +52,8 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
# Sell 3rd trade (not called for the first trade)
should_sell_mock = MagicMock(side_effect=[
SellCheckTuple(sell_type=SellType.NONE),
SellCheckTuple(sell_type=SellType.SELL_SIGNAL)]
ExitCheckTuple(exit_type=ExitType.NONE),
ExitCheckTuple(exit_type=ExitType.SELL_SIGNAL)]
)
cancel_order_mock = MagicMock()
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
@@ -73,14 +72,14 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
create_stoploss_order=MagicMock(return_value=True),
_notify_exit=MagicMock(),
)
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=1000))
freqtrade = get_patched_freqtradebot(mocker, default_conf)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# Switch ordertype to market to close trade immediately
freqtrade.strategy.order_types['sell'] = 'market'
freqtrade.strategy.order_types['exit'] = 'market'
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
patch_get_signal(freqtrade)
@@ -116,7 +115,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
assert wallets_mock.call_count == 4
trade = trades[0]
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
assert trade.sell_reason == ExitType.STOPLOSS_ON_EXCHANGE.value
assert not trade.is_open
trade = trades[1]
@@ -124,7 +123,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
assert trade.is_open
trade = trades[2]
assert trade.sell_reason == SellType.SELL_SIGNAL.value
assert trade.sell_reason == ExitType.SELL_SIGNAL.value
assert not trade.is_open
@@ -161,19 +160,19 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
_notify_exit=MagicMock(),
)
should_sell_mock = MagicMock(side_effect=[
SellCheckTuple(sell_type=SellType.NONE),
SellCheckTuple(sell_type=SellType.SELL_SIGNAL),
SellCheckTuple(sell_type=SellType.NONE),
SellCheckTuple(sell_type=SellType.NONE),
SellCheckTuple(sell_type=SellType.NONE)]
ExitCheckTuple(exit_type=ExitType.NONE),
ExitCheckTuple(exit_type=ExitType.SELL_SIGNAL),
ExitCheckTuple(exit_type=ExitType.NONE),
ExitCheckTuple(exit_type=ExitType.NONE),
ExitCheckTuple(exit_type=ExitType.NONE)]
)
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtrade)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# Switch ordertype to market to close trade immediately
freqtrade.strategy.order_types['sell'] = 'market'
freqtrade.strategy.order_types['exit'] = 'market'
patch_get_signal(freqtrade)
# Create 4 trades
@@ -184,7 +183,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
assert len(trades) == 4
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1
rpc._rpc_forcebuy('TKN/BTC', None)
rpc._rpc_force_entry('TKN/BTC', None)
trades = Trade.query.all()
assert len(trades) == 5
@@ -231,13 +230,13 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
assert len(Trade.get_trades().all()) == 1
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert trade.stake_amount == 60
assert pytest.approx(trade.stake_amount) == 60
assert trade.open_rate == 2.0
# No adjustment
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert trade.stake_amount == 60
assert pytest.approx(trade.stake_amount) == 60
# Reduce bid amount
ticker_usdt_modif = ticker_usdt.return_value
@@ -266,9 +265,10 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
assert trade.nr_of_successful_buys == 2
assert trade.nr_of_successful_entries == 2
# Sell
patch_get_signal(freqtrade, value=(False, True, None, None))
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
freqtrade.process()
trade = Trade.get_trades().first()
assert trade.is_open is False
@@ -280,3 +280,74 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
assert trade.orders[2].amount == trade.amount
assert trade.nr_of_successful_buys == 2
assert trade.nr_of_successful_entries == 2
def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> 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,
)
patch_get_signal(freqtrade, enter_long=False, enter_short=True)
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 trade.open_rate == 2.02
# No adjustment
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert pytest.approx(trade.stake_amount) == 60
# Reduce bid amount
ticker_usdt_modif = ticker_usdt.return_value
ticker_usdt_modif['ask'] = ticker_usdt_modif['ask'] * 1.004
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif)
# additional buy order
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
for o in trade.orders:
assert o.status == "closed"
assert pytest.approx(trade.stake_amount) == 120
# Open-rate averaged between 2.0 and 2.0 * 1.015
assert trade.open_rate >= 2.02
assert trade.open_rate < 2.02 * 1.015
# No action - profit raised above 1% (the bar set in the strategy).
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert pytest.approx(trade.stake_amount) == 120
# assert trade.orders[0].amount == 30
assert trade.orders[1].amount == 60 / ticker_usdt_modif['ask']
assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
assert trade.nr_of_successful_entries == 2
# Buy
patch_get_signal(freqtrade, enter_long=False, exit_short=True)
freqtrade.process()
trade = Trade.get_trades().first()
assert trade.is_open is False
# assert trade.orders[0].amount == 30
assert trade.orders[0].side == 'sell'
assert trade.orders[1].amount == 60 / ticker_usdt_modif['ask']
# Sold everything
assert trade.orders[-1].side == 'buy'
assert trade.orders[2].amount == trade.amount
assert trade.nr_of_successful_entries == 2
assert trade.nr_of_successful_exits == 1

View File

@@ -1,15 +1,16 @@
# pragma pylint: disable=missing-docstring,C0103
import datetime
from copy import deepcopy
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from freqtrade.misc import (decimals_per_coin, file_dump_json, file_load_json, format_ms_time,
pair_to_filename, parse_db_uri_for_logging, plural, render_template,
render_template_with_fallback, round_coin_value, safe_value_fallback,
safe_value_fallback2, shorten_date)
from freqtrade.misc import (decimals_per_coin, deep_merge_dicts, file_dump_json, file_load_json,
format_ms_time, pair_to_filename, parse_db_uri_for_logging, plural,
render_template, render_template_with_fallback, round_coin_value,
safe_value_fallback, safe_value_fallback2, shorten_date)
def test_decimals_per_coin():
@@ -72,14 +73,17 @@ def test_file_load_json(mocker, testdatadir) -> None:
("ETH/BTC", 'ETH_BTC'),
("ETH/USDT", 'ETH_USDT'),
("ETH/USDT:USDT", 'ETH_USDT_USDT'), # swap with USDT as settlement currency
("ETH/USDT:USDT-210625", 'ETH_USDT_USDT_210625'), # expiring futures
("ETH/USD:USD", 'ETH_USD_USD'), # swap with USD as settlement currency
("AAVE/USD:USD", 'AAVE_USD_USD'), # swap with USDT as settlement currency
("ETH/USDT:USDT-210625", 'ETH_USDT_USDT-210625'), # expiring futures
("Fabric Token/ETH", 'Fabric_Token_ETH'),
("ETHH20", 'ETHH20'),
(".XBTBON2H", '_XBTBON2H'),
("ETHUSD.d", 'ETHUSD_d'),
("ADA-0327", 'ADA_0327'),
("BTC-USD-200110", 'BTC_USD_200110'),
("F-AKRO/USDT", 'F_AKRO_USDT'),
("ADA-0327", 'ADA-0327'),
("BTC-USD-200110", 'BTC-USD-200110'),
("BTC-PERP:USDT", 'BTC-PERP_USDT'),
("F-AKRO/USDT", 'F-AKRO_USDT'),
("LC+/ETH", 'LC__ETH'),
("CMT@18/ETH", 'CMT_18_ETH'),
("LBTC:1022/SAI", 'LBTC_1022_SAI'),
@@ -202,3 +206,16 @@ def test_render_template_fallback(mocker):
def test_parse_db_uri_for_logging(conn_url, expected) -> None:
assert parse_db_uri_for_logging(conn_url) == expected
def test_deep_merge_dicts():
a = {'first': {'rows': {'pass': 'dog', 'number': '1', 'test': None}}}
b = {'first': {'rows': {'fail': 'cat', 'number': '5', 'test': 'asdf'}}}
res = {'first': {'rows': {'pass': 'dog', 'fail': 'cat', 'number': '5', 'test': 'asdf'}}}
res2 = {'first': {'rows': {'pass': 'dog', 'fail': 'cat', 'number': '1', 'test': None}}}
assert deep_merge_dicts(b, deepcopy(a)) == res
assert deep_merge_dicts(a, deepcopy(b)) == res2
res2['first']['rows']['test'] = 'asdf'
assert deep_merge_dicts(a, deepcopy(b), allow_null_overrides=False) == res2

File diff suppressed because it is too large Load Diff

View File

@@ -200,8 +200,10 @@ def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, t
timerange = TimeRange(None, 'line', 0, -1000)
data = history.load_pair_history(pair=pair, timeframe='1m',
datadir=testdatadir, timerange=timerange)
data['buy'] = 0
data['sell'] = 0
data['enter_long'] = 0
data['exit_long'] = 0
data['enter_short'] = 0
data['exit_short'] = 0
indicators1 = []
indicators2 = []
@@ -222,8 +224,10 @@ def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, t
assert row_mock.call_count == 2
assert trades_mock.call_count == 1
assert log_has("No buy-signals found.", caplog)
assert log_has("No sell-signals found.", caplog)
assert log_has("No enter_long-signals found.", caplog)
assert log_has("No exit_long-signals found.", caplog)
assert log_has("No enter_short-signals found.", caplog)
assert log_has("No exit_short-signals found.", caplog)
def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir):
@@ -249,7 +253,7 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
assert fig.layout.title.text == pair
figure = fig.layout.figure
assert len(figure.data) == 6
assert len(figure.data) == 8
# Candlesticks are plotted first
candles = find_trace_in_fig_data(figure.data, "Price")
assert isinstance(candles, go.Candlestick)
@@ -257,15 +261,15 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
volume = find_trace_in_fig_data(figure.data, "Volume")
assert isinstance(volume, go.Bar)
buy = find_trace_in_fig_data(figure.data, "buy")
assert isinstance(buy, go.Scatter)
enter_long = find_trace_in_fig_data(figure.data, "enter_long")
assert isinstance(enter_long, go.Scatter)
# All buy-signals should be plotted
assert int(data.buy.sum()) == len(buy.x)
assert int(data['enter_long'].sum()) == len(enter_long.x)
sell = find_trace_in_fig_data(figure.data, "sell")
assert isinstance(sell, go.Scatter)
exit_long = find_trace_in_fig_data(figure.data, "exit_long")
assert isinstance(exit_long, go.Scatter)
# All buy-signals should be plotted
assert int(data.sell.sum()) == len(sell.x)
assert int(data['exit_long'].sum()) == len(exit_long.x)
assert find_trace_in_fig_data(figure.data, "Bollinger Band")

View File

@@ -6,7 +6,7 @@ import pytest
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exceptions import DependencyException
from tests.conftest import get_patched_freqtradebot, patch_wallet
from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_wallet
def test_sync_wallet_at_boot(mocker, default_conf):
@@ -180,24 +180,32 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
assert result == 0
@pytest.mark.parametrize('stake_amount,min_stake_amount,max_stake_amount,expected', [
(22, 11, 50, 22),
(100, 11, 500, 100),
(1000, 11, 500, 500), # Above max-stake
(20, 15, 10, 0), # Minimum stake > max-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
(1000, None, 1000, 1000), # No min-stake-amount could be determined
@pytest.mark.parametrize('stake_amount,min_stake,stake_available,max_stake,expected', [
(22, 11, 50, 10000, 22),
(100, 11, 500, 10000, 100),
(1000, 11, 500, 10000, 500), # Above stake_available
(700, 11, 1000, 400, 400), # Above max_stake, below stake available
(20, 15, 10, 10000, 0), # Minimum stake > stake_available
(9, 11, 100, 10000, 11), # Below min stake
(1, 15, 10, 10000, 0), # Below min stake and min_stake > stake_available
(20, 50, 100, 10000, 0), # Below min stake and stake * 1.3 > min_stake
(1000, None, 1000, 10000, 1000), # No min-stake-amount could be determined
])
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,
stake_available,
max_stake,
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)
return_value=stake_available)
res = freqtrade.wallets.validate_stake_amount('XRP/USDT', stake_amount, min_stake, max_stake)
assert res == expected
@@ -226,3 +234,131 @@ def test_get_starting_balance(mocker, default_conf, available_capital, closed_pr
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.wallets.get_starting_balance() == expected
def test_sync_wallet_futures_live(mocker, default_conf):
default_conf['dry_run'] = False
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
mock_result = [
{
"symbol": "ETH/USDT:USDT",
"timestamp": None,
"datetime": None,
"initialMargin": 0.0,
"initialMarginPercentage": None,
"maintenanceMargin": 0.0,
"maintenanceMarginPercentage": 0.005,
"entryPrice": 0.0,
"notional": 100.0,
"leverage": 5.0,
"unrealizedPnl": 0.0,
"contracts": 100.0,
"contractSize": 1,
"marginRatio": None,
"liquidationPrice": 0.0,
"markPrice": 2896.41,
"collateral": 20,
"marginType": "isolated",
"side": 'short',
"percentage": None
},
{
"symbol": "ADA/USDT:USDT",
"timestamp": None,
"datetime": None,
"initialMargin": 0.0,
"initialMarginPercentage": None,
"maintenanceMargin": 0.0,
"maintenanceMarginPercentage": 0.005,
"entryPrice": 0.0,
"notional": 100.0,
"leverage": 5.0,
"unrealizedPnl": 0.0,
"contracts": 100.0,
"contractSize": 1,
"marginRatio": None,
"liquidationPrice": 0.0,
"markPrice": 0.91,
"collateral": 20,
"marginType": "isolated",
"side": 'short',
"percentage": None
},
{
# Closed position
"symbol": "SOL/BUSD:BUSD",
"timestamp": None,
"datetime": None,
"initialMargin": 0.0,
"initialMarginPercentage": None,
"maintenanceMargin": 0.0,
"maintenanceMarginPercentage": 0.005,
"entryPrice": 0.0,
"notional": 0.0,
"leverage": 5.0,
"unrealizedPnl": 0.0,
"contracts": 0.0,
"contractSize": 1,
"marginRatio": None,
"liquidationPrice": 0.0,
"markPrice": 15.41,
"collateral": 0.0,
"marginType": "isolated",
"side": 'short',
"percentage": None
}
]
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value={
"USDT": {
"free": 900,
"used": 100,
"total": 1000
},
}),
fetch_positions=MagicMock(return_value=mock_result)
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 2
assert 'USDT' in freqtrade.wallets._wallets
assert 'ETH/USDT:USDT' in freqtrade.wallets._positions
assert freqtrade.wallets._last_wallet_refresh > 0
# Remove ETH/USDT:USDT position
del mock_result[0]
freqtrade.wallets.update()
assert len(freqtrade.wallets._positions) == 1
assert 'ETH/USDT:USDT' not in freqtrade.wallets._positions
def test_sync_wallet_futures_dry(mocker, default_conf, fee):
default_conf['dry_run'] = True
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 0
create_mock_trades(fee, is_short=None)
freqtrade.wallets.update()
assert len(freqtrade.wallets._wallets) == 1
assert len(freqtrade.wallets._positions) == 4
positions = freqtrade.wallets.get_all_positions()
positions['ETH/BTC'].side == 'short'
positions['ETC/BTC'].side == 'long'
positions['XRP/BTC'].side == 'long'
positions['LTC/BTC'].side == 'short'
assert freqtrade.wallets.get_starting_balance() == default_conf['dry_run_wallet']
total = freqtrade.wallets.get_total('BTC')
free = freqtrade.wallets.get_free('BTC')
used = freqtrade.wallets.get_used('BTC')
assert free + used == total

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,102 @@
[
[1636959600000, 1.21431, 1.2198, 1.20895, 1.20895, null],
[1636963200000, 1.20902, 1.21106, 1.19972, 1.20968, null],
[1636966800000, 1.20968, 1.21876, 1.20791, 1.20998, null],
[1636970400000, 1.20999, 1.21043, 1.20442, 1.20859, null],
[1636974000000, 1.20858, 1.20933, 1.20154, 1.20581, null],
[1636977600000, 1.20584, 1.20775, 1.20065, 1.20337, null],
[1636981200000, 1.20342, 1.2097, 1.19327, 1.19792, null],
[1636984800000, 1.19796, 1.1982, 1.18611, 1.19024, null],
[1636988400000, 1.19025, 1.19177, 1.18373, 1.18771, null],
[1636992000000, 1.18768, 1.19109, 1.18095, 1.1887, null],
[1636995600000, 1.18869, 1.18968, 1.18355, 1.18387, null],
[1636999200000, 1.18388, 1.18729, 1.17753, 1.18138, null],
[1637002800000, 1.18145, 1.18684, 1.17799, 1.18463, null],
[1637006400000, 1.18464, 1.18474, 1.17368, 1.17652, null],
[1637010000000, 1.17653, 1.18185, 1.16557, 1.17979, null],
[1637013600000, 1.17979, 1.18113, 1.16934, 1.18014, null],
[1637017200000, 1.18014, 1.18015, 1.16999, 1.17214, null],
[1637020800000, 1.17214, 1.17217, 1.12958, 1.14209, null],
[1637024400000, 1.14255, 1.14666, 1.10933, 1.14198, null],
[1637028000000, 1.14197, 1.14419, 1.12766, 1.12999, null],
[1637031600000, 1.12999, 1.13522, 1.11142, 1.12177, null],
[1637035200000, 1.12176, 1.13211, 1.10579, 1.1288, null],
[1637038800000, 1.12871, 1.13243, 1.12142, 1.12316, null],
[1637042400000, 1.12323, 1.1262, 1.11489, 1.12429, null],
[1637046000000, 1.12406, 1.12727, 1.11835, 1.1249, null],
[1637049600000, 1.12485, 1.13047, 1.1211, 1.12931, null],
[1637053200000, 1.12931, 1.13346, 1.10256, 1.10267, null],
[1637056800000, 1.10266, 1.10412, 1.04149, 1.0928, null],
[1637060400000, 1.09277, 1.09856, 1.08371, 1.09093, null],
[1637064000000, 1.09094, 1.09512, 1.079, 1.08003, null],
[1637067600000, 1.0802, 1.09914, 1.08016, 1.09515, null],
[1637071200000, 1.09518, 1.11627, 1.0937, 1.10985, null],
[1637074800000, 1.10985, 1.11353, 1.09618, 1.10071, null],
[1637078400000, 1.09989, 1.10852, 1.09763, 1.10461, null],
[1637082000000, 1.10459, 1.10837, 1.09662, 1.09847, null],
[1637085600000, 1.09858, 1.10506, 1.08687, 1.08716, null],
[1637089200000, 1.08677, 1.10096, 1.08151, 1.09271, null],
[1637092800000, 1.09245, 1.09269, 1.06592, 1.08025, null],
[1637096400000, 1.08026, 1.09732, 1.07953, 1.09527, null],
[1637100000000, 1.09527, 1.10506, 1.09524, 1.09933, null],
[1637103600000, 1.09933, 1.10205, 1.08761, 1.08785, null],
[1637107200000, 1.08763, 1.09518, 1.07646, 1.07999, null],
[1637110800000, 1.07997, 1.0978, 1.07651, 1.07936, null],
[1637114400000, 1.07932, 1.08758, 1.07352, 1.07603, null],
[1637118000000, 1.07604, 1.08542, 1.05931, 1.06764, null],
[1637121600000, 1.06788, 1.07848, 1.06045, 1.07608, null],
[1637125200000, 1.07613, 1.08797, 1.07293, 1.08377, null],
[1637128800000, 1.08379, 1.08567, 1.07428, 1.07942, null],
[1637132400000, 1.07958, 1.09472, 1.07356, 1.08713, null],
[1637136000000, 1.08714, 1.09149, 1.08018, 1.08021, null],
[1637139600000, 1.08021, 1.08021, 1.0668, 1.07032, null],
[1637143200000, 1.07042, 1.10563, 1.07034, 1.10255, null],
[1637146800000, 1.10284, 1.10954, 1.09767, 1.10685, null],
[1637150400000, 1.10669, 1.10848, 1.10157, 1.10537, null],
[1637154000000, 1.10537, 1.11263, 1.09554, 1.09585, null],
[1637157600000, 1.09569, 1.10051, 1.08402, 1.08431, null],
[1637161200000, 1.08444, 1.08942, 1.07569, 1.08489, null],
[1637164800000, 1.08498, 1.09581, 1.07939, 1.09485, null],
[1637168400000, 1.09443, 1.09793, 1.08778, 1.0944, null],
[1637172000000, 1.09445, 1.10227, 1.09376, 1.0992, null],
[1637175600000, 1.0992, 1.10189, 1.09216, 1.09474, null],
[1637179200000, 1.09476, 1.10198, 1.09045, 1.0993, null],
[1637182800000, 1.09934, 1.09959, 1.08755, 1.0948, null],
[1637186400000, 1.09483, 1.09519, 1.08532, 1.0923, null],
[1637190000000, 1.0923, 1.09876, 1.0874, 1.095, null],
[1637193600000, 1.09503, 1.10673, 1.09047, 1.10441, null],
[1637197200000, 1.10437, 1.16166, 1.09815, 1.12902, null],
[1637200800000, 1.12875, 1.15094, 1.1242, 1.13764, null],
[1637204400000, 1.13795, 1.14262, 1.12341, 1.12423, null],
[1637208000000, 1.12424, 1.14806, 1.11333, 1.1142, null],
[1637211600000, 1.11435, 1.12608, 1.11085, 1.11436, null],
[1637215200000, 1.11398, 1.11718, 1.10538, 1.11388, null],
[1637218800000, 1.1139, 1.11452, 1.09674, 1.1072, null],
[1637222400000, 1.10725, 1.10999, 1.10209, 1.10706, null],
[1637226000000, 1.10712, 1.10712, 1.07747, 1.08658, null],
[1637229600000, 1.08692, 1.09865, 1.0807, 1.09767, null],
[1637233200000, 1.09768, 1.10211, 1.08348, 1.08409, null],
[1637236800000, 1.08423, 1.09498, 1.08002, 1.08259, null],
[1637240400000, 1.0827, 1.08773, 1.06597, 1.07719, null],
[1637244000000, 1.07718, 1.08075, 1.06678, 1.07077, null],
[1637247600000, 1.07029, 1.07824, 1.04568, 1.05497, null],
[1637251200000, 1.05591, 1.06325, 1.03957, 1.04032, null],
[1637254800000, 1.04051, 1.05342, 1.01557, 1.04158, null],
[1637258400000, 1.04153, 1.05436, 1.04122, 1.05208, null],
[1637262000000, 1.05207, 1.05948, 1.04961, 1.05515, null],
[1637265600000, 1.05516, 1.05927, 1.04767, 1.04808, null],
[1637269200000, 1.04789, 1.05622, 1.04191, 1.04587, null],
[1637272800000, 1.04575, 1.05336, 1.03405, 1.03941, null],
[1637276400000, 1.03931, 1.04614, 1.02868, 1.0411, null],
[1637280000000, 1.04093, 1.05672, 1.0295, 1.05495, null],
[1637283600000, 1.05495, 1.0553, 1.03548, 1.03595, null],
[1637287200000, 1.0359, 1.04585, 1.02026, 1.02312, null],
[1637290800000, 1.0242, 1.02908, 1.01788, 1.02871, null],
[1637294400000, 1.02871, 1.04474, 1.02584, 1.04247, null],
[1637298000000, 1.04251, 1.04654, 1.03685, 1.0449, null],
[1637301600000, 1.0449, 1.04971, 1.04109, 1.04452, null],
[1637305200000, 1.04456, 1.04875, 1.03802, 1.04268, null],
[1637308800000, 1.04239, 1.06573, 1.04164, 1.05717, null],
[1637312400000, 1.05721, 1.06464, 1.05619, 1.05896, null],
[1637316000000, 1.05893, 1.05918, 1.04976, 1.05188, null]
]

Binary file not shown.

View File

@@ -0,0 +1,102 @@
[
[ 1637110800000, 1.0801, 1.09758, 1.07654, 1.07925, 3153694.607359 ],
[ 1637114400000, 1.07896, 1.0875, 1.07351, 1.07616, 2697616.070908 ],
[ 1637118000000, 1.07607, 1.08521, 1.05896, 1.06804, 4014666.826073 ],
[ 1637121600000, 1.06848, 1.07846, 1.06067, 1.07629, 3764015.567745 ],
[ 1637125200000, 1.07647, 1.08791, 1.07309, 1.0839, 1669038.113726 ],
[ 1637128800000, 1.08414, 1.0856, 1.07431, 1.0794, 1921068.874499 ],
[ 1637132400000, 1.0798, 1.09499, 1.07363, 1.08721, 2491096.863582 ],
[ 1637136000000, 1.08688, 1.09133, 1.08004, 1.08011, 1983486.794272 ],
[ 1637139600000, 1.08017, 1.08027, 1.06667, 1.07039, 3429247.985309 ],
[ 1637143200000, 1.07054, 1.10699, 1.07038, 1.10284, 4554151.954177 ],
[ 1637146800000, 1.10315, 1.10989, 1.09781, 1.1071, 2012983.10465 ],
[ 1637150400000, 1.10627, 1.10849, 1.10155, 1.10539, 1117804.08918 ],
[ 1637154000000, 1.10545, 1.11299, 1.09574, 1.09604, 2252781.33926 ],
[ 1637157600000, 1.09583, 1.10037, 1.08402, 1.08404, 1882359.279342 ],
[ 1637161200000, 1.08433, 1.08924, 1.07583, 1.08543, 1826745.82579 ],
[ 1637164800000, 1.08571, 1.09622, 1.07946, 1.09496, 1651730.678891 ],
[ 1637168400000, 1.09509, 1.0979, 1.0878, 1.0945, 1081210.614598 ],
[ 1637172000000, 1.09483, 1.10223, 1.09362, 1.09922, 1065998.492028 ],
[ 1637175600000, 1.09916, 1.10201, 1.09226, 1.09459, 924935.492048 ],
[ 1637179200000, 1.09458, 1.10196, 1.09051, 1.09916, 1253539.625345 ],
[ 1637182800000, 1.09939, 1.09948, 1.08751, 1.09485, 1066269.190094 ],
[ 1637186400000, 1.0949, 1.095, 1.08537, 1.09229, 924726.680514 ],
[ 1637190000000, 1.0923, 1.09877, 1.08753, 1.09522, 1150213.905599 ],
[ 1637193600000, 1.09538, 1.10675, 1.09058, 1.10453, 1489867.578178 ],
[ 1637197200000, 1.10446, 1.16313, 1.0978, 1.12907, 10016166.026355 ],
[ 1637200800000, 1.1287, 1.15367, 1.12403, 1.1381, 7167920.053752 ],
[ 1637204400000, 1.13818, 1.14242, 1.12358, 1.1244, 2665326.190545 ],
[ 1637208000000, 1.12432, 1.14864, 1.11061, 1.11447, 9340547.947608 ],
[ 1637211600000, 1.114, 1.12618, 1.10911, 1.11412, 11759138.472952 ],
[ 1637215200000, 1.11381, 1.11701, 1.10507, 1.1136, 3104670.727264 ],
[ 1637218800000, 1.11433, 1.1145, 1.09682, 1.10715, 2522287.830673 ],
[ 1637222400000, 1.1073, 1.11, 1.10224, 1.10697, 2021691.204473 ],
[ 1637226000000, 1.10622, 1.10707, 1.07727, 1.08674, 3679010.223352 ],
[ 1637229600000, 1.08651, 1.09861, 1.08065, 1.09771, 2041421.476307 ],
[ 1637233200000, 1.09784, 1.102, 1.08339, 1.08399, 1920597.122813 ],
[ 1637236800000, 1.08458, 1.09523, 1.07961, 1.08263, 2403158.337373 ],
[ 1637240400000, 1.08309, 1.08959, 1.06094, 1.07703, 4425686.808376 ],
[ 1637244000000, 1.07702, 1.08064, 1.063, 1.07049, 3361334.048801 ],
[ 1637247600000, 1.07126, 1.07851, 1.04538, 1.0562, 5865602.611111 ],
[ 1637251200000, 1.05616, 1.06326, 1.0395, 1.04074, 4206860.947352 ],
[ 1637254800000, 1.04023, 1.0533, 1.01478, 1.0417, 5641193.647291 ],
[ 1637258400000, 1.04177, 1.05444, 1.04132, 1.05204, 1819341.083656 ],
[ 1637262000000, 1.05201, 1.05962, 1.04964, 1.05518, 1567923.362515 ],
[ 1637265600000, 1.05579, 1.05924, 1.04772, 1.04773, 1794108.065606 ],
[ 1637269200000, 1.0484, 1.05622, 1.04183, 1.04544, 1936537.403899 ],
[ 1637272800000, 1.04543, 1.05331, 1.03396, 1.03892, 2839486.418143 ],
[ 1637276400000, 1.03969, 1.04592, 1.02886, 1.04086, 3116275.899177 ],
[ 1637280000000, 1.0409, 1.05681, 1.02922, 1.05481, 4671209.916896 ],
[ 1637283600000, 1.05489, 1.05538, 1.03539, 1.03599, 2566357.247547 ],
[ 1637287200000, 1.03596, 1.04606, 1.02038, 1.02428, 3441834.238546 ],
[ 1637290800000, 1.02483, 1.0291, 1.01785, 1.0285, 2678602.729339 ],
[ 1637294400000, 1.0287, 1.0446, 1.0259, 1.04264, 2303621.340808 ],
[ 1637298000000, 1.04313, 1.04676, 1.03662, 1.04499, 2426475.439485 ],
[ 1637301600000, 1.0451, 1.04971, 1.041, 1.04448, 2088365.810515 ],
[ 1637305200000, 1.04473, 1.04845, 1.03801, 1.04227, 2222396.213472 ],
[ 1637308800000, 1.04211, 1.06965, 1.04168, 1.05711, 3267643.936025 ],
[ 1637312400000, 1.0569, 1.06578, 1.05626, 1.05844, 1512848.016057 ],
[ 1637316000000, 1.05814, 1.05916, 1.04923, 1.05464, 1710694.805693 ],
[ 1637319600000, 1.05484, 1.05731, 1.0458, 1.05359, 1587100.45253 ],
[ 1637323200000, 1.05382, 1.06063, 1.05156, 1.05227, 1409095.236152 ],
[ 1637326800000, 1.05256, 1.06489, 1.04996, 1.06471, 1879315.174541 ],
[ 1637330400000, 1.06491, 1.1036, 1.06489, 1.09439, 6212842.71216 ],
[ 1637334000000, 1.09441, 1.10252, 1.082, 1.08879, 4833417.181969 ],
[ 1637337600000, 1.08866, 1.09485, 1.07538, 1.09045, 2554438.746366 ],
[ 1637341200000, 1.09058, 1.09906, 1.08881, 1.09039, 1961024.28963 ],
[ 1637344800000, 1.09063, 1.09447, 1.08555, 1.09041, 1427538.639232 ],
[ 1637348400000, 1.09066, 1.09521, 1.088, 1.09332, 847724.821691 ],
[ 1637352000000, 1.09335, 1.09489, 1.08402, 1.08501, 1035043.133874 ],
[ 1637355600000, 1.08474, 1.08694, 1.08, 1.08606, 969952.892274 ],
[ 1637359200000, 1.08601, 1.09, 1.08201, 1.08476, 1105782.581808 ],
[ 1637362800000, 1.08463, 1.09245, 1.08201, 1.08971, 1334467.438673 ],
[ 1637366400000, 1.0897, 1.09925, 1.08634, 1.09049, 2460070.020396 ],
[ 1637370000000, 1.0908, 1.10002, 1.09002, 1.09845, 1210028.489394 ],
[ 1637373600000, 1.09785, 1.09791, 1.08944, 1.08962, 1261987.295847 ],
[ 1637377200000, 1.08951, 1.0919, 1.08429, 1.08548, 1124938.783404 ],
[ 1637380800000, 1.08536, 1.09, 1.08424, 1.08783, 1330935.680168 ],
[ 1637384400000, 1.0877, 1.08969, 1.08266, 1.08617, 874900.746037 ],
[ 1637388000000, 1.08622, 1.09224, 1.0843, 1.0889, 1240184.759178 ],
[ 1637391600000, 1.08917, 1.0909, 1.08408, 1.08535, 706148.380072 ],
[ 1637395200000, 1.08521, 1.08857, 1.07829, 1.08349, 1713832.050838 ],
[ 1637398800000, 1.08343, 1.08841, 1.08272, 1.0855, 696597.06327 ],
[ 1637402400000, 1.08553, 1.0898, 1.08353, 1.08695, 1104159.802108 ],
[ 1637406000000, 1.08703, 1.09838, 1.08635, 1.09695, 1404001.384389 ],
[ 1637409600000, 1.09695, 1.10175, 1.09024, 1.09278, 1219090.620484 ],
[ 1637413200000, 1.093, 1.09577, 1.08615, 1.08792, 994797.546591 ],
[ 1637416800000, 1.08793, 1.09239, 1.08572, 1.08725, 1251685.429497 ],
[ 1637420400000, 1.08721, 1.08767, 1.06029, 1.06556, 3955719.53631 ],
[ 1637424000000, 1.06553, 1.07385, 1.06169, 1.07257, 1868359.179534 ],
[ 1637427600000, 1.07266, 1.0745, 1.06759, 1.07261, 1015134.469304 ],
[ 1637431200000, 1.07255, 1.0974, 1.06819, 1.09369, 4377675.964829 ],
[ 1637434800000, 1.09368, 1.09562, 1.08899, 1.09036, 914791.699929 ],
[ 1637438400000, 1.09085, 1.09262, 1.08855, 1.09214, 661436.936672 ],
[ 1637442000000, 1.0924, 1.09475, 1.08874, 1.09282, 593143.283519 ],
[ 1637445600000, 1.09301, 1.09638, 1.09154, 1.09611, 603952.916221 ],
[ 1637449200000, 1.09569, 1.09828, 1.09301, 1.09747, 676053.591571 ],
[ 1637452800000, 1.09742, 1.09822, 1.09011, 1.0902, 1375704.506469 ],
[ 1637456400000, 1.0901, 1.09311, 1.08619, 1.08856, 928706.03929 ],
[ 1637460000000, 1.08855, 1.08941, 1.07401, 1.08035, 2669150.388642 ],
[ 1637463600000, 1.08016, 1.08341, 1.07448, 1.07672, 1604049.131307 ],
[ 1637467200000, 1.07685, 1.08229, 1.07552, 1.0765, 1153357.274076 ]
]

Binary file not shown.

View File

@@ -0,0 +1,102 @@
[
[1636956000000, 1.20932, 1.21787, 1.20763, 1.21431, null],
[1636959600000, 1.21431, 1.2198, 1.20895, 1.20895, null],
[1636963200000, 1.20902, 1.21106, 1.19972, 1.20968, null],
[1636966800000, 1.20968, 1.21876, 1.20791, 1.20998, null],
[1636970400000, 1.20999, 1.21043, 1.20442, 1.20859, null],
[1636974000000, 1.20858, 1.20933, 1.20154, 1.20581, null],
[1636977600000, 1.20584, 1.20775, 1.20065, 1.20337, null],
[1636981200000, 1.20342, 1.2097, 1.19327, 1.19792, null],
[1636984800000, 1.19796, 1.1982, 1.18611, 1.19024, null],
[1636988400000, 1.19025, 1.19177, 1.18373, 1.18771, null],
[1636992000000, 1.18768, 1.19109, 1.18095, 1.1887, null],
[1636995600000, 1.18869, 1.18968, 1.18355, 1.18387, null],
[1636999200000, 1.18388, 1.18729, 1.17753, 1.18138, null],
[1637002800000, 1.18145, 1.18684, 1.17799, 1.18463, null],
[1637006400000, 1.18464, 1.18474, 1.17368, 1.17652, null],
[1637010000000, 1.17653, 1.18185, 1.16557, 1.17979, null],
[1637013600000, 1.17979, 1.18113, 1.16934, 1.18014, null],
[1637017200000, 1.18014, 1.18015, 1.16999, 1.17214, null],
[1637020800000, 1.17214, 1.17217, 1.12958, 1.14209, null],
[1637024400000, 1.14255, 1.14666, 1.10933, 1.14198, null],
[1637028000000, 1.14197, 1.14419, 1.12766, 1.12999, null],
[1637031600000, 1.12999, 1.13522, 1.11142, 1.12177, null],
[1637035200000, 1.12176, 1.13211, 1.10579, 1.1288, null],
[1637038800000, 1.12871, 1.13243, 1.12142, 1.12316, null],
[1637042400000, 1.12323, 1.1262, 1.11489, 1.12429, null],
[1637046000000, 1.12406, 1.12727, 1.11835, 1.1249, null],
[1637049600000, 1.12485, 1.13047, 1.1211, 1.12931, null],
[1637053200000, 1.12931, 1.13346, 1.10256, 1.10267, null],
[1637056800000, 1.10266, 1.10412, 1.04149, 1.0928, null],
[1637060400000, 1.09277, 1.09856, 1.08371, 1.09093, null],
[1637064000000, 1.09094, 1.09512, 1.079, 1.08003, null],
[1637067600000, 1.0802, 1.09914, 1.08016, 1.09515, null],
[1637071200000, 1.09518, 1.11627, 1.0937, 1.10985, null],
[1637074800000, 1.10985, 1.11353, 1.09618, 1.10071, null],
[1637078400000, 1.09989, 1.10852, 1.09763, 1.10461, null],
[1637082000000, 1.10459, 1.10837, 1.09662, 1.09847, null],
[1637085600000, 1.09858, 1.10506, 1.08687, 1.08716, null],
[1637089200000, 1.08677, 1.10096, 1.08151, 1.09271, null],
[1637092800000, 1.09245, 1.09269, 1.06592, 1.08025, null],
[1637096400000, 1.08026, 1.09732, 1.07953, 1.09527, null],
[1637100000000, 1.09527, 1.10506, 1.09524, 1.09933, null],
[1637103600000, 1.09933, 1.10205, 1.08761, 1.08785, null],
[1637107200000, 1.08763, 1.09518, 1.07646, 1.07999, null],
[1637110800000, 1.07997, 1.0978, 1.07651, 1.07936, null],
[1637114400000, 1.07932, 1.08758, 1.07352, 1.07603, null],
[1637118000000, 1.07604, 1.08542, 1.05931, 1.06764, null],
[1637121600000, 1.06788, 1.07848, 1.06045, 1.07608, null],
[1637125200000, 1.07613, 1.08797, 1.07293, 1.08377, null],
[1637128800000, 1.08379, 1.08567, 1.07428, 1.07942, null],
[1637132400000, 1.07958, 1.09472, 1.07356, 1.08713, null],
[1637136000000, 1.08714, 1.09149, 1.08018, 1.08021, null],
[1637139600000, 1.08021, 1.08021, 1.0668, 1.07032, null],
[1637143200000, 1.07042, 1.10563, 1.07034, 1.10255, null],
[1637146800000, 1.10284, 1.10954, 1.09767, 1.10685, null],
[1637150400000, 1.10669, 1.10848, 1.10157, 1.10537, null],
[1637154000000, 1.10537, 1.11263, 1.09554, 1.09585, null],
[1637157600000, 1.09569, 1.10051, 1.08402, 1.08431, null],
[1637161200000, 1.08444, 1.08942, 1.07569, 1.08489, null],
[1637164800000, 1.08498, 1.09581, 1.07939, 1.09485, null],
[1637168400000, 1.09443, 1.09793, 1.08778, 1.0944, null],
[1637172000000, 1.09445, 1.10227, 1.09376, 1.0992, null],
[1637175600000, 1.0992, 1.10189, 1.09216, 1.09474, null],
[1637179200000, 1.09476, 1.10198, 1.09045, 1.0993, null],
[1637182800000, 1.09934, 1.09959, 1.08755, 1.0948, null],
[1637186400000, 1.09483, 1.09519, 1.08532, 1.0923, null],
[1637190000000, 1.0923, 1.09876, 1.0874, 1.095, null],
[1637193600000, 1.09503, 1.10673, 1.09047, 1.10441, null],
[1637197200000, 1.10437, 1.16166, 1.09815, 1.12902, null],
[1637200800000, 1.12875, 1.15094, 1.1242, 1.13764, null],
[1637204400000, 1.13795, 1.14262, 1.12341, 1.12423, null],
[1637208000000, 1.12424, 1.14806, 1.11333, 1.1142, null],
[1637211600000, 1.11435, 1.12608, 1.11085, 1.11436, null],
[1637215200000, 1.11398, 1.11718, 1.10538, 1.11388, null],
[1637218800000, 1.1139, 1.11452, 1.09674, 1.1072, null],
[1637222400000, 1.10725, 1.10999, 1.10209, 1.10706, null],
[1637226000000, 1.10712, 1.10712, 1.07747, 1.08658, null],
[1637229600000, 1.08692, 1.09865, 1.0807, 1.09767, null],
[1637233200000, 1.09768, 1.10211, 1.08348, 1.08409, null],
[1637236800000, 1.08423, 1.09498, 1.08002, 1.08259, null],
[1637240400000, 1.0827, 1.08773, 1.06597, 1.07719, null],
[1637244000000, 1.07718, 1.08075, 1.06678, 1.07077, null],
[1637247600000, 1.07029, 1.07824, 1.04568, 1.05497, null],
[1637251200000, 1.05591, 1.06325, 1.03957, 1.04032, null],
[1637254800000, 1.04051, 1.05342, 1.01557, 1.04158, null],
[1637258400000, 1.04153, 1.05436, 1.04122, 1.05208, null],
[1637262000000, 1.05207, 1.05948, 1.04961, 1.05515, null],
[1637265600000, 1.05516, 1.05927, 1.04767, 1.04808, null],
[1637269200000, 1.04789, 1.05622, 1.04191, 1.04587, null],
[1637272800000, 1.04575, 1.05336, 1.03405, 1.03941, null],
[1637276400000, 1.03931, 1.04614, 1.02868, 1.0411, null],
[1637280000000, 1.04093, 1.05672, 1.0295, 1.05495, null],
[1637283600000, 1.05495, 1.0553, 1.03548, 1.03595, null],
[1637287200000, 1.0359, 1.04585, 1.02026, 1.02312, null],
[1637290800000, 1.0242, 1.02908, 1.01788, 1.02871, null],
[1637294400000, 1.02871, 1.04474, 1.02584, 1.04247, null],
[1637298000000, 1.04251, 1.04654, 1.03685, 1.0449, null],
[1637301600000, 1.0449, 1.04971, 1.04109, 1.04452, null],
[1637305200000, 1.04456, 1.04875, 1.03802, 1.04268, null],
[1637308800000, 1.04239, 1.06573, 1.04164, 1.05717, null],
[1637312400000, 1.05721, 1.06464, 1.05619, 1.06051, null]
]

View File

@@ -0,0 +1 @@
[[1637193600017,0.0001,0.0,0.0,0.0,0.0],[1637222400007,0.0001,0.0,0.0,0.0,0.0],[1637251200011,0.0001,0.0,0.0,0.0,0.0],[1637280000000,0.0001,0.0,0.0,0.0,0.0],[1637308800000,0.0001,0.0,0.0,0.0,0.0],[1637337600005,0.0001,0.0,0.0,0.0,0.0],[1637366400012,0.00013046,0.0,0.0,0.0,0.0],[1637395200000,0.0001,0.0,0.0,0.0,0.0],[1637424000007,0.0001,0.0,0.0,0.0,0.0],[1637452800000,0.00013862,0.0,0.0,0.0,0.0],[1637481600006,0.0001,0.0,0.0,0.0,0.0],[1637510400000,0.00019881,0.0,0.0,0.0,0.0],[1637539200004,0.00013991,0.0,0.0,0.0,0.0],[1637568000000,0.0001,0.0,0.0,0.0,0.0],[1637596800000,0.0001,0.0,0.0,0.0,0.0],[1637625600004,0.0001,0.0,0.0,0.0,0.0],[1637654400010,0.0001,0.0,0.0,0.0,0.0],[1637683200005,0.00017402,0.0,0.0,0.0,0.0],[1637712000001,0.00016775,0.0,0.0,0.0,0.0],[1637740800003,0.00033523,0.0,0.0,0.0,0.0],[1637769600010,0.0001,0.0,0.0,0.0,0.0],[1637798400000,0.00020066,0.0,0.0,0.0,0.0],[1637827200010,0.00034381,0.0,0.0,0.0,0.0],[1637856000000,0.00032096,0.0,0.0,0.0,0.0],[1637884800000,0.00058316,0.0,0.0,0.0,0.0],[1637913600000,0.0001646,0.0,0.0,0.0,0.0],[1637942400016,0.0001,0.0,0.0,0.0,0.0],[1637971200005,0.0001,0.0,0.0,0.0,0.0],[1638000000008,0.0001,0.0,0.0,0.0,0.0],[1638028800007,0.0001,0.0,0.0,0.0,0.0],[1638057600018,0.0001,0.0,0.0,0.0,0.0],[1638086400000,0.0001,0.0,0.0,0.0,0.0],[1638115200004,0.0001,0.0,0.0,0.0,0.0],[1638144000002,0.0001,0.0,0.0,0.0,0.0],[1638172800004,0.0001,0.0,0.0,0.0,0.0],[1638201600000,0.0001,0.0,0.0,0.0,0.0],[1638230400000,0.0001,0.0,0.0,0.0,0.0],[1638259200006,0.0001,0.0,0.0,0.0,0.0],[1638288000000,0.0001,0.0,0.0,0.0,0.0],[1638316800000,0.0001,0.0,0.0,0.0,0.0],[1638345600000,0.0001,0.0,0.0,0.0,0.0],[1638374400001,0.0001,0.0,0.0,0.0,0.0],[1638403200000,0.0001,0.0,0.0,0.0,0.0],[1638432000007,0.0001,0.0,0.0,0.0,0.0],[1638460800008,0.0001,0.0,0.0,0.0,0.0],[1638489600004,0.0001,0.0,0.0,0.0,0.0],[1638518400002,0.0001,0.0,0.0,0.0,0.0],[1638547200006,0.0001,0.0,0.0,0.0,0.0],[1638576000006,0.0001,0.0,0.0,0.0,0.0],[1638604800004,-0.00219334,0.0,0.0,0.0,0.0],[1638633600000,0.0001,0.0,0.0,0.0,0.0],[1638662400003,0.00006147,0.0,0.0,0.0,0.0],[1638691200008,0.0001,0.0,0.0,0.0,0.0],[1638720000007,0.0001,0.0,0.0,0.0,0.0],[1638748800009,0.0001,0.0,0.0,0.0,0.0],[1638777600001,0.0001,0.0,0.0,0.0,0.0],[1638806400000,0.0001,0.0,0.0,0.0,0.0],[1638835200018,0.0001,0.0,0.0,0.0,0.0],[1638864000000,0.0001,0.0,0.0,0.0,0.0],[1638892800000,0.0001,0.0,0.0,0.0,0.0],[1638921600000,0.0001,0.0,0.0,0.0,0.0],[1638950400018,0.0001,0.0,0.0,0.0,0.0],[1638979200010,0.0001,0.0,0.0,0.0,0.0],[1639008000010,0.0001,0.0,0.0,0.0,0.0],[1639036800000,0.0001,0.0,0.0,0.0,0.0],[1639065600000,0.0001,0.0,0.0,0.0,0.0],[1639094400000,0.0001,0.0,0.0,0.0,0.0],[1639123200008,0.0001,0.0,0.0,0.0,0.0],[1639152000012,0.00008995,0.0,0.0,0.0,0.0],[1639180800009,0.0001,0.0,0.0,0.0,0.0],[1639209600008,-0.00002574,0.0,0.0,0.0,0.0],[1639238400000,-0.00002024,0.0,0.0,0.0,0.0],[1639267200001,-0.00008282,0.0,0.0,0.0,0.0],[1639296000015,0.0001,0.0,0.0,0.0,0.0],[1639324800011,0.00008752,0.0,0.0,0.0,0.0],[1639353600006,0.0001,0.0,0.0,0.0,0.0],[1639382400019,0.0001,0.0,0.0,0.0,0.0],[1639411200000,0.0001,0.0,0.0,0.0,0.0],[1639440000004,0.00007825,0.0,0.0,0.0,0.0],[1639468800000,0.00007108,0.0,0.0,0.0,0.0],[1639497600015,0.0001,0.0,0.0,0.0,0.0],[1639526400000,0.0001,0.0,0.0,0.0,0.0],[1639555200008,0.0001,0.0,0.0,0.0,0.0],[1639584000005,0.0001,0.0,0.0,0.0,0.0],[1639612800006,0.0001,0.0,0.0,0.0,0.0],[1639641600009,0.0001,0.0,0.0,0.0,0.0],[1639670400000,0.0001,0.0,0.0,0.0,0.0],[1639699200000,0.0001,0.0,0.0,0.0,0.0],[1639728000005,0.0001,0.0,0.0,0.0,0.0],[1639756800006,0.0001,0.0,0.0,0.0,0.0],[1639785600014,0.0001,0.0,0.0,0.0,0.0]]

File diff suppressed because one or more lines are too long