Merge branch 'develop' into feat_readjust_entry
This commit is contained in:
@@ -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
|
||||
|
@@ -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', [
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
||||
|
||||
|
@@ -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'
|
||||
|
@@ -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')
|
||||
|
@@ -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)
|
||||
|
@@ -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):
|
||||
|
@@ -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])
|
||||
|
@@ -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',
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user