Merge branch 'develop' into cyber-forcesell-tg
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
1315
tests/conftest.py
1315
tests/conftest.py
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
360
tests/exchange/test_okx.py
Normal 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
|
||||
},
|
||||
],
|
||||
}
|
||||
27
tests/leverage/test_candletype.py
Normal file
27
tests/leverage/test_candletype.py
Normal 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
|
||||
38
tests/leverage/test_interest.py
Normal file
38
tests/leverage/test_interest.py
Normal 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)
|
||||
@@ -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'])
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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[
|
||||
(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
|
||||
# --------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
193
tests/strategy/strats/strategy_test_v3.py
Normal file
193
tests/strategy/strats/strategy_test_v3.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
|
||||
@@ -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
2
tests/testdata/backtest-result_new.json
vendored
2
tests/testdata/backtest-result_new.json
vendored
File diff suppressed because one or more lines are too long
102
tests/testdata/futures/UNITTEST_USDT-1h-mark.json
vendored
Normal file
102
tests/testdata/futures/UNITTEST_USDT-1h-mark.json
vendored
Normal 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]
|
||||
]
|
||||
BIN
tests/testdata/futures/UNITTEST_USDT_USDT-1h-mark.h5
vendored
Normal file
BIN
tests/testdata/futures/UNITTEST_USDT_USDT-1h-mark.h5
vendored
Normal file
Binary file not shown.
102
tests/testdata/futures/XRP_USDT-1h-futures.json
vendored
Normal file
102
tests/testdata/futures/XRP_USDT-1h-futures.json
vendored
Normal 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 ]
|
||||
]
|
||||
BIN
tests/testdata/futures/XRP_USDT-1h-futures.json.gz
vendored
Normal file
BIN
tests/testdata/futures/XRP_USDT-1h-futures.json.gz
vendored
Normal file
Binary file not shown.
102
tests/testdata/futures/XRP_USDT-1h-mark.json
vendored
Normal file
102
tests/testdata/futures/XRP_USDT-1h-mark.json
vendored
Normal 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]
|
||||
]
|
||||
1
tests/testdata/futures/XRP_USDT-8h-funding_rate.json
vendored
Normal file
1
tests/testdata/futures/XRP_USDT-8h-funding_rate.json
vendored
Normal 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]]
|
||||
1
tests/testdata/futures/XRP_USDT-8h-mark.json
vendored
Normal file
1
tests/testdata/futures/XRP_USDT-8h-mark.json
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user