Merge branch 'develop' into feat/short

This commit is contained in:
Matthias
2022-02-11 17:02:04 +01:00
63 changed files with 1158 additions and 349 deletions

View File

@@ -36,6 +36,8 @@ class BTContainer(NamedTuple):
trailing_stop_positive_offset: float = 0.0
use_sell_signal: bool = False
use_custom_stoploss: bool = False
custom_entry_price: Optional[float] = None
custom_exit_price: Optional[float] = None
leverage: float = 1.0

View File

@@ -1,5 +1,6 @@
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, C0330, unused-argument
import logging
from unittest.mock import MagicMock
import pytest
@@ -534,11 +535,84 @@ tc33 = BTContainer(data=[
)]
)
# Test 34: (copy of test25 with leverage)
# 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
[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],
[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.0,
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
[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],
[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,
custom_entry_price=7200, trades=[
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)
]
)
# Test 36: Custom-entry-price around candle low
# Causes immediate ROI exit. This is currently expected behavior (#6261)
# https://github.com/freqtrade/freqtrade/issues/6261
# But may change at a later point.
tc36 = BTContainer(data=[
# D O H L C V B S BT
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5500, 4951, 5000, 6172, 0, 0], # Enter and immediate ROI
[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.1,
custom_entry_price=4952,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)]
)
# Test 37: Custom exit price below all candles
# Price adjusted to candle Low.
tc37 = BTContainer(data=[
# D O H L C V B S 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
[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.01,
use_sell_signal=True,
custom_exit_price=4552,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=3)]
)
# Test 38: Custom exit price above all candles
# causes sell signal timeout
tc38 = BTContainer(data=[
# D O H L C V B S 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
[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)]
)
# Test 39: (copy of test25 with leverage)
# Sell with signal sell in candle 3 (stoploss also triggers on this candle)
# Stoploss at 1%.
# Sell-signal wins over stoploss
tc34 = BTContainer(data=[
tc39 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
@@ -551,6 +625,7 @@ tc34 = BTContainer(data=[
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
)
TESTS = [
tc0,
tc1,
@@ -587,6 +662,11 @@ TESTS = [
tc32,
tc33,
tc34,
tc35,
tc36,
tc37,
tc38,
tc39,
# TODO-lev: Add tests for short here
]
@@ -621,6 +701,10 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
backtesting._can_short = True
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)

View File

@@ -21,6 +21,7 @@ from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import get_timerange
from freqtrade.enums import RunMode, SellType
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
@@ -524,6 +525,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
# Fake 2 trades, so there's not enough amount for the next trade left.
LocalTrade.trades_open.append(trade)
LocalTrade.trades_open.append(trade)
backtesting.wallets.update()
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade is None
LocalTrade.trades_open.pop()
@@ -531,6 +533,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
assert trade is not None
backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5
backtesting.wallets.update()
trade = backtesting._enter_trade(pair, row=row, direction='long')
assert trade
assert trade.stake_amount == 123.5
@@ -659,7 +662,8 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
assert res.sell_reason == SellType.ROI.value
# Sell at minute 3 (not available above!)
assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc)
assert round(res.close_rate, 3) == round(209.0225, 3)
sell_order = res.select_order('sell', True)
assert sell_order is not None
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
@@ -676,6 +680,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
timerange=timerange)
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
result = backtesting.backtest(
processed=deepcopy(processed),
start_date=min_date,
@@ -769,6 +774,47 @@ def test_processed(default_conf, mocker, testdatadir) -> None:
assert col in cols
def test_backtest_dataprovider_analyzed_df(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=100000)
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
timerange = TimeRange('date', None, 1517227800, 0)
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
global count
count = 0
def tmp_confirm_entry(pair, current_time, **kwargs):
dp = backtesting.strategy.dp
df, _ = dp.get_analyzed_dataframe(pair, backtesting.strategy.timeframe)
current_candle = df.iloc[-1].squeeze()
assert current_candle['enter_long'] == 1
candle_date = timeframe_to_next_date(backtesting.strategy.timeframe, current_candle['date'])
assert candle_date == current_time
# These asserts don't properly raise as they are nested,
# therefore we increment count and assert for that.
global count
count = count + 1
backtesting.strategy.confirm_trade_entry = tmp_confirm_entry
backtesting.backtest(
processed=deepcopy(processed),
start_date=min_date,
end_date=max_date,
max_open_trades=10,
position_stacking=False,
)
assert count == 5
def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatadir) -> None:
# While this test IS a copy of test_backtest_pricecontours, it's needed to ensure
# results do not carry-over to the next run, which is not given by using parametrize.
@@ -1013,6 +1059,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'config': default_conf,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
})
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
@@ -1124,6 +1172,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'config': default_conf,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
},
{
@@ -1131,6 +1181,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'config': default_conf,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
}
])
@@ -1238,6 +1290,8 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
'config': default_conf_usdt,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
},
{
@@ -1245,6 +1299,8 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
'config': default_conf_usdt,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
}
])
@@ -1337,6 +1393,8 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'config': default_conf,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
},
{
@@ -1344,6 +1402,8 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'config': default_conf,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
}
])
@@ -1405,6 +1465,8 @@ def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testda
'config': default_conf,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
})
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',

View File

@@ -365,6 +365,8 @@ def test_hyperopt_format_results(hyperopt):
'locks': [],
'final_balance': 0.02,
'rejected_signals': 2,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'backtest_start_time': 1619718665,
'backtest_end_time': 1619718665,
}
@@ -433,6 +435,8 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'config': hyperopt_conf,
'locks': [],
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'final_balance': 1000,
}

View File

@@ -86,6 +86,7 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) ->
"SharpeHyperOptLossDaily",
"MaxDrawDownHyperOptLoss",
"CalmarHyperOptLoss",
"ProfitDrawDownHyperOptLoss",
])
def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None:
@@ -106,7 +107,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct
config=default_conf,
processed=None,
backtest_stats={'profit_total': hyperopt_results['profit_abs'].sum()}
)
)
over = hl.hyperopt_loss_function(
results_over,
trade_count=len(results_over),

View File

@@ -84,6 +84,8 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
'locks': [],
'final_balance': 1000.02,
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp,
'run_id': '123',
@@ -134,6 +136,8 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
'locks': [],
'final_balance': 1000.02,
'rejected_signals': 20,
'timedout_entry_orders': 0,
'timedout_exit_orders': 0,
'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp,
'run_id': '124',