Merge branch 'develop' into feat_readjust_entry

This commit is contained in:
eSeR1805
2022-05-01 21:42:15 +03:00
53 changed files with 689 additions and 388 deletions

View File

@@ -8,14 +8,14 @@ from pandas import DataFrame, DateOffset, Timestamp, to_datetime
from freqtrade.configuration import TimeRange
from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism, calculate_cagr,
calculate_csum, calculate_market_change,
calculate_max_drawdown, calculate_underwater,
combine_dataframes_with_mean, create_cum_profit,
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism,
extract_trades_of_period, get_latest_backtest_filename,
get_latest_hyperopt_file, load_backtest_data,
load_backtest_metadata, load_trades, load_trades_from_db)
from freqtrade.data.history import load_data, load_pair_history
from freqtrade.data.metrics import (calculate_cagr, calculate_csum, calculate_market_change,
calculate_max_drawdown, calculate_underwater,
combine_dataframes_with_mean, create_cum_profit)
from freqtrade.exceptions import OperationalException
from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades
from tests.conftest_trades import MOCK_TRADE_COUNT

View File

@@ -149,8 +149,8 @@ def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog,
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'candle type: spot and store in .*', caplog
r'\(0/1\) - Download history data for "MEME/BTC", 1m, '
r'spot and store in .*', caplog
)
@@ -223,42 +223,65 @@ 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(
data, start_ts, end_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
assert end_ts is None
# timeframe starts earlier than the cached data - prepending
timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0)
data, start_ts, end_ts = _load_cached_data_for_updating(
'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT, True)
assert_frame_equal(data, test_data_df.iloc[:-1])
assert start_ts == test_data[0][0] - 1000
assert end_ts == test_data[0][0]
# 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(
data, start_ts, end_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]
assert end_ts is 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(
data, start_ts, end_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]
assert end_ts is None
# no datafile exist
# should return timestamp start time
timerange = TimeRange('date', None, now_ts - 10000, 0)
data, start_ts = _load_cached_data_for_updating(
data, start_ts, end_ts = _load_cached_data_for_updating(
'NONEXIST/BTC', '1m', timerange, data_handler, CandleType.SPOT)
assert data.empty
assert start_ts == (now_ts - 10000) * 1000
assert end_ts is None
# no datafile exist
# should return timestamp start and end time time
timerange = TimeRange('date', 'date', now_ts - 1000000, now_ts - 100000)
data, start_ts, end_ts = _load_cached_data_for_updating(
'NONEXIST/BTC', '1m', timerange, data_handler, CandleType.SPOT)
assert data.empty
assert start_ts == (now_ts - 1000000) * 1000
assert end_ts == (now_ts - 100000) * 1000
# no datafile exist, no timeframe is set
# should return an empty array and None
data, start_ts = _load_cached_data_for_updating(
data, start_ts, end_ts = _load_cached_data_for_updating(
'NONEXIST/BTC', '1m', None, data_handler, CandleType.SPOT)
assert data.empty
assert start_ts is None
assert end_ts is None
@pytest.mark.parametrize('candle_type,subdir,file_tail', [

View File

@@ -1983,6 +1983,20 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_
assert exchange._api_async.fetch_ohlcv.call_count > 200
assert res[0] == ohlcv[0]
exchange._api_async.fetch_ohlcv.reset_mock()
end_ts = 1_500_500_000_000
start_ts = 1_500_000_000_000
respair, restf, _, res = await exchange._async_get_historic_ohlcv(
pair, "5m", since_ms=start_ts, candle_type=candle_type, is_new_pair=False,
until_ms=end_ts
)
# Required candles
candles = (end_ts - start_ts) / 300_000
exp = candles // exchange.ohlcv_candle_limit('5m') + 1
# Depending on the exchange, this should be called between 1 and 6 times.
assert exchange._api_async.fetch_ohlcv.call_count == exp
@pytest.mark.parametrize('candle_type', [CandleType.FUTURES, CandleType.MARK, CandleType.SPOT])
def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None:

View File

@@ -4,7 +4,7 @@ from unittest.mock import MagicMock
import pytest
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt_loss_short_trade_dur import ShortTradeDurHyperOptLoss
from freqtrade.optimize.hyperopt_loss.hyperopt_loss_short_trade_dur import ShortTradeDurHyperOptLoss
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver

View File

@@ -21,8 +21,22 @@ def test_PairLocks(use_db):
pair = 'ETH/BTC'
assert not PairLocks.is_pair_locked(pair)
PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime)
# ETH/BTC locked for 4 minutes
# ETH/BTC locked for 4 minutes (on both sides)
assert PairLocks.is_pair_locked(pair)
assert PairLocks.is_pair_locked(pair, side='long')
assert PairLocks.is_pair_locked(pair, side='short')
pair = 'BNB/BTC'
PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime, side='long')
assert not PairLocks.is_pair_locked(pair)
assert PairLocks.is_pair_locked(pair, side='long')
assert not PairLocks.is_pair_locked(pair, side='short')
pair = 'BNB/USDT'
PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime, side='short')
assert not PairLocks.is_pair_locked(pair)
assert not PairLocks.is_pair_locked(pair, side='long')
assert PairLocks.is_pair_locked(pair, side='short')
# XRP/BTC should not be locked now
pair = 'XRP/BTC'

View File

@@ -11,9 +11,10 @@ from tests.conftest import get_patched_freqtradebot, log_has_re
def generate_mock_trade(pair: str, fee: float, is_open: bool,
sell_reason: str = ExitType.EXIT_SIGNAL,
exit_reason: str = ExitType.EXIT_SIGNAL,
min_ago_open: int = None, min_ago_close: int = None,
profit_rate: float = 0.9
profit_rate: float = 0.9,
is_short: bool = False,
):
open_rate = random.random()
@@ -28,11 +29,12 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
is_open=is_open,
amount=0.01 / open_rate,
exchange='binance',
is_short=is_short,
)
trade.recalc_open_trade_value()
if not is_open:
trade.close(open_rate * profit_rate)
trade.exit_reason = sell_reason
trade.close(open_rate * (2 - profit_rate if is_short else profit_rate))
trade.exit_reason = exit_reason
return trade
@@ -45,9 +47,9 @@ def test_protectionmanager(mocker, default_conf):
for handler in freqtrade.protections._protection_handlers:
assert handler.name in constants.AVAILABLE_PROTECTIONS
if not handler.has_global_stop:
assert handler.global_stop(datetime.utcnow()) == (False, None, None)
assert handler.global_stop(datetime.utcnow(), '*') is None
if not handler.has_local_stop:
assert handler.stop_per_pair('XRP/BTC', datetime.utcnow()) == (False, None, None)
assert handler.stop_per_pair('XRP/BTC', datetime.utcnow(), '*') is None
@pytest.mark.parametrize('timeframe,expected,protconf', [
@@ -68,7 +70,7 @@ def test_protectionmanager(mocker, default_conf):
('1h', [60, 540],
[{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration_candles": 9}]),
])
def test_protections_init(mocker, default_conf, timeframe, expected, protconf):
def test_protections_init(default_conf, timeframe, expected, protconf):
default_conf['timeframe'] = timeframe
man = ProtectionManager(default_conf, protconf)
assert len(man._protection_handlers) == len(protconf)
@@ -76,8 +78,10 @@ def test_protections_init(mocker, default_conf, timeframe, expected, protconf):
assert man._protection_handlers[0]._stop_duration == expected[1]
@pytest.mark.parametrize('is_short', [False, True])
@pytest.mark.usefixtures("init_persistence")
def test_stoploss_guard(mocker, default_conf, fee, caplog):
def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short):
# Active for both sides (long and short)
default_conf['protections'] = [{
"method": "StoplossGuard",
"lookback_period": 60,
@@ -91,8 +95,8 @@ 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=ExitType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30,
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, is_short=is_short,
))
assert not freqtrade.protections.global_stop()
@@ -100,13 +104,13 @@ 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=ExitType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100,
'BCH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100, is_short=is_short,
))
Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30,
'ETH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30, is_short=is_short,
))
# 3 Trades closed - but the 2nd has been closed too long ago.
assert not freqtrade.protections.global_stop()
@@ -114,8 +118,8 @@ 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=ExitType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30,
'LTC/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30, is_short=is_short,
))
assert freqtrade.protections.global_stop()
@@ -130,15 +134,19 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
@pytest.mark.parametrize('only_per_pair', [False, True])
@pytest.mark.parametrize('only_per_side', [False, True])
@pytest.mark.usefixtures("init_persistence")
def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair):
def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair, only_per_side):
default_conf['protections'] = [{
"method": "StoplossGuard",
"lookback_period": 60,
"trade_limit": 2,
"stop_duration": 60,
"only_per_pair": only_per_pair
"only_per_pair": only_per_pair,
"only_per_side": only_per_side,
}]
check_side = 'long' if only_per_side else '*'
is_short = False
freqtrade = get_patched_freqtradebot(mocker, default_conf)
message = r"Trading stopped due to .*"
pair = 'XRP/BTC'
@@ -148,8 +156,8 @@ 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=ExitType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, profit_rate=0.9,
pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, profit_rate=0.9, is_short=is_short
))
assert not freqtrade.protections.stop_per_pair(pair)
@@ -158,13 +166,13 @@ 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=ExitType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100, profit_rate=0.9,
pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100, profit_rate=0.9, is_short=is_short
))
# 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=ExitType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30, profit_rate=0.9,
'ETH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30, profit_rate=0.9, is_short=is_short
))
# 3 Trades closed - but the 2nd has been closed too long ago.
assert not freqtrade.protections.stop_per_pair(pair)
@@ -176,16 +184,34 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
caplog.clear()
# Trade does not count potentially, as it's in the wrong direction
Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=150, min_ago_close=25, profit_rate=0.9, is_short=not is_short
))
freqtrade.protections.stop_per_pair(pair)
assert freqtrade.protections.global_stop() != only_per_pair
assert PairLocks.is_pair_locked(pair, side=check_side) != (only_per_side and only_per_pair)
assert PairLocks.is_global_lock(side=check_side) != only_per_pair
if only_per_side:
assert not PairLocks.is_pair_locked(pair, side='*')
assert not PairLocks.is_global_lock(side='*')
caplog.clear()
# 2nd Trade that counts with correct pair
Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30, profit_rate=0.9,
pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30, profit_rate=0.9, is_short=is_short
))
freqtrade.protections.stop_per_pair(pair)
assert freqtrade.protections.global_stop() != only_per_pair
assert PairLocks.is_pair_locked(pair)
assert PairLocks.is_global_lock() != only_per_pair
assert PairLocks.is_pair_locked(pair, side=check_side)
assert PairLocks.is_global_lock(side=check_side) != only_per_pair
if only_per_side:
assert not PairLocks.is_pair_locked(pair, side='*')
assert not PairLocks.is_global_lock(side='*')
@pytest.mark.usefixtures("init_persistence")
@@ -203,7 +229,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=ExitType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30,
))
@@ -213,7 +239,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=ExitType.ROI.value,
'ETH/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value,
min_ago_open=205, min_ago_close=35,
))
@@ -242,7 +268,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=ExitType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=800, min_ago_close=450, profit_rate=0.9,
))
@@ -253,7 +279,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=ExitType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=120, profit_rate=0.9,
))
@@ -265,14 +291,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=ExitType.ROI.value,
'XRP/BTC', fee.return_value, False, exit_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=ExitType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=110, min_ago_close=20, profit_rate=0.8,
))
@@ -300,15 +326,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=ExitType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_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=ExitType.STOP_LOSS.value,
'ETH/BTC', fee.return_value, False, exit_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=ExitType.STOP_LOSS.value,
'NEO/BTC', fee.return_value, False, exit_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 +342,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=ExitType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=500, min_ago_close=400, profit_rate=0.9,
))
# Not locked with one trade
@@ -326,7 +352,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=ExitType.STOP_LOSS.value,
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
min_ago_open=1200, min_ago_close=1100, profit_rate=0.5,
))
@@ -339,7 +365,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=ExitType.ROI.value,
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value,
min_ago_open=320, min_ago_close=410, profit_rate=1.5,
))
assert not freqtrade.protections.global_stop()
@@ -349,7 +375,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=ExitType.ROI.value,
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value,
min_ago_open=20, min_ago_close=10, profit_rate=0.8,
))
assert not freqtrade.protections.stop_per_pair('XRP/BTC')

View File

@@ -1483,7 +1483,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir):
assert not result['running']
assert result['status_msg'] == 'Backtest reset'
ftbot.config['export'] = 'trades'
ftbot.config['backtest_cache'] = 'none'
ftbot.config['backtest_cache'] = 'day'
ftbot.config['user_data_dir'] = Path(tmpdir)
ftbot.config['exportfilename'] = Path(tmpdir) / "backtest_results"
ftbot.config['exportfilename'].mkdir()
@@ -1556,19 +1556,19 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir):
ApiServer._bgtask_running = False
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy',
side_effect=DependencyException())
rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data))
assert log_has("Backtesting caused an error: ", caplog)
ftbot.config['backtest_cache'] = 'day'
# Rerun backtest (should get previous result)
rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data))
assert_response(rc)
result = rc.json()
assert log_has_re('Reusing result of previous backtest.*', caplog)
data['stake_amount'] = 101
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy',
side_effect=DependencyException())
rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data))
assert log_has("Backtesting caused an error: ", caplog)
# Delete backtesting to avoid leakage since the backtest-object may stick around.
rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc)

View File

@@ -666,23 +666,23 @@ def test_is_pair_locked(default_conf):
assert not strategy.is_pair_locked(pair)
# latest candle is from 14:20, lock goes to 14:30
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-10))
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-50))
assert strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-10))
assert strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-50))
# latest candle is from 14:25 (lock should be lifted)
# Since this is the "new candle" available at 14:30
assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-4))
assert not strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-4))
# Should not be locked after time expired
assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=10))
assert not strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=10))
# Change timeframe to 15m
strategy.timeframe = '15m'
# Candle from 14:14 - lock goes until 14:30
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-16))
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-15, seconds=-2))
assert strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-16))
assert strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-15, seconds=-2))
# Candle from 14:15 - lock goes until 14:30
assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-15))
assert not strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-15))
def test_is_informative_pairs_callback(default_conf):

View File

@@ -21,6 +21,7 @@ from freqtrade.exceptions import (DependencyException, ExchangeError, Insufficie
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Order, PairLocks, Trade
from freqtrade.persistence.models import PairLock
from freqtrade.plugins.protections.iprotection import ProtectionReturn
from freqtrade.worker import Worker
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, get_patched_worker,
log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal,
@@ -420,7 +421,7 @@ def test_enter_positions_global_pairlock(default_conf_usdt, ticker_usdt, limit_b
assert not log_has_re(message, caplog)
caplog.clear()
PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because')
PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because', side='*')
n = freqtrade.enter_positions()
assert n == 0
assert log_has_re(message, caplog)
@@ -441,9 +442,9 @@ def test_handle_protections(mocker, default_conf_usdt, fee, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.protections._protection_handlers[1].global_stop = MagicMock(
return_value=(True, arrow.utcnow().shift(hours=1).datetime, "asdf"))
return_value=ProtectionReturn(True, arrow.utcnow().shift(hours=1).datetime, "asdf"))
create_mock_trades(fee, is_short)
freqtrade.handle_protections('ETC/BTC')
freqtrade.handle_protections('ETC/BTC', '*')
send_msg_mock = freqtrade.rpc.send_msg
assert send_msg_mock.call_count == 2
assert send_msg_mock.call_args_list[0][0][0]['type'] == RPCMessageType.PROTECTION_TRIGGER
@@ -3793,13 +3794,16 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)
)
trade.close(ticker_usdt_sell_down()['bid'])
assert freqtrade.strategy.is_pair_locked(trade.pair)
assert freqtrade.strategy.is_pair_locked(trade.pair, side='*')
# Boths sides are locked
assert freqtrade.strategy.is_pair_locked(trade.pair, side='long')
assert freqtrade.strategy.is_pair_locked(trade.pair, side='short')
# reinit - should buy other pair.
caplog.clear()
freqtrade.enter_positions()
assert log_has_re(f"Pair {trade.pair} is still locked.*", caplog)
assert log_has_re(fr"Pair {trade.pair} \* is locked.*", caplog)
@pytest.mark.parametrize("is_short", [False, True])

View File

@@ -15,6 +15,7 @@ from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids
from freqtrade.persistence.models import PairLock
from tests.conftest import create_mock_trades, create_mock_trades_with_leverage, log_has, log_has_re
@@ -1427,6 +1428,55 @@ def test_migrate_set_sequence_ids():
assert engine.begin.call_count == 0
def test_migrate_pairlocks(mocker, default_conf, fee, caplog):
"""
Test Database migration (starting with new pairformat)
"""
caplog.set_level(logging.DEBUG)
# Always create all columns apart from the last!
create_table_old = """CREATE TABLE pairlocks (
id INTEGER NOT NULL,
pair VARCHAR(25) NOT NULL,
reason VARCHAR(255),
lock_time DATETIME NOT NULL,
lock_end_time DATETIME NOT NULL,
active BOOLEAN NOT NULL,
PRIMARY KEY (id)
)
"""
create_index1 = "CREATE INDEX ix_pairlocks_pair ON pairlocks (pair)"
create_index2 = "CREATE INDEX ix_pairlocks_lock_end_time ON pairlocks (lock_end_time)"
create_index3 = "CREATE INDEX ix_pairlocks_active ON pairlocks (active)"
insert_table_old = """INSERT INTO pairlocks (
id, pair, reason, lock_time, lock_end_time, active)
VALUES (1, 'ETH/BTC', 'Auto lock', '2021-07-12 18:41:03', '2021-07-11 18:45:00', 1)
"""
insert_table_old2 = """INSERT INTO pairlocks (
id, pair, reason, lock_time, lock_end_time, active)
VALUES (2, '*', 'Lock all', '2021-07-12 18:41:03', '2021-07-12 19:00:00', 1)
"""
engine = create_engine('sqlite://')
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
# Create table using the old format
with engine.begin() as connection:
connection.execute(text(create_table_old))
connection.execute(text(insert_table_old))
connection.execute(text(insert_table_old2))
connection.execute(text(create_index1))
connection.execute(text(create_index2))
connection.execute(text(create_index3))
init_db(default_conf['db_url'], default_conf['dry_run'])
assert len(PairLock.query.all()) == 2
assert len(PairLock.query.filter(PairLock.pair == '*').all()) == 1
pairlocks = PairLock.query.filter(PairLock.pair == 'ETH/BTC').all()
assert len(pairlocks) == 1
pairlocks[0].pair == 'ETH/BTC'
pairlocks[0].side == '*'
def test_adjust_stop_loss(fee):
trade = Trade(
pair='ADA/USDT',

View File

@@ -10,7 +10,8 @@ from plotly.subplots import make_subplots
from freqtrade.commands import start_plot_dataframe, start_plot_profit
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data
from freqtrade.data.btanalysis import load_backtest_data
from freqtrade.data.metrics import create_cum_profit
from freqtrade.exceptions import OperationalException
from freqtrade.plot.plotting import (add_areas, add_indicators, add_profit, create_plotconfig,
generate_candlestick_graph, generate_plot_filename,