merged with feat/short
This commit is contained in:
@@ -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):
|
||||
|
@@ -4,7 +4,7 @@
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
|
||||
# --------------------------------
|
||||
|
@@ -4,7 +4,7 @@ import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
|
||||
class StrategyTestV2(IStrategy):
|
||||
|
181
tests/strategy/strats/strategy_test_v3.py
Normal file
181
tests/strategy/strats/strategy_test_v3.py
Normal file
@@ -0,0 +1,181 @@
|
||||
# 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.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 = {
|
||||
'buy': 'limit',
|
||||
'sell': '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 = {
|
||||
'buy': 'gtc',
|
||||
'sell': '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-lev: Can we make this work with protection tests?
|
||||
# TODO-lev: (Would 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_buy_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_sell_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
|
||||
|
||||
# TODO-lev: Add short logic
|
||||
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
|
@@ -4,20 +4,20 @@ 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')
|
||||
assert hasattr(StrategyTestV3, 'minimal_roi')
|
||||
assert hasattr(StrategyTestV3, 'stoploss')
|
||||
assert hasattr(StrategyTestV3, 'timeframe')
|
||||
assert hasattr(StrategyTestV3, 'populate_indicators')
|
||||
assert hasattr(StrategyTestV3, 'populate_buy_trend')
|
||||
assert hasattr(StrategyTestV3, 'populate_sell_trend')
|
||||
|
||||
|
||||
def test_strategy_test_v2(result, fee):
|
||||
strategy = StrategyTestV2({})
|
||||
strategy = StrategyTestV3({})
|
||||
|
||||
metadata = {'pair': 'ETH/BTC'}
|
||||
assert type(strategy.minimal_roi) is dict
|
||||
@@ -37,7 +37,7 @@ def test_strategy_test_v2(result, fee):
|
||||
|
||||
assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
|
||||
rate=20000, time_in_force='gtc',
|
||||
current_time=datetime.utcnow()) is True
|
||||
current_time=datetime.utcnow(), side='long') 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
|
||||
|
@@ -21,13 +21,13 @@ from freqtrade.strategy.hyper import (BaseParameter, BooleanParameter, Categoric
|
||||
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)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ def test_returns_latest_signal(ohlcv_history):
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False)
|
||||
mocked_history.loc[1, 'exit_long'] = 0
|
||||
mocked_history.loc[1, 'enter_long'] = 1
|
||||
mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01'
|
||||
mocked_history.loc[1, 'enter_tag'] = 'buy_signal_01'
|
||||
|
||||
assert _STRATEGY.get_entry_signal(
|
||||
'ETH/BTC', '5m', mocked_history) == (SignalDirection.LONG, 'buy_signal_01')
|
||||
@@ -70,8 +70,10 @@ def test_returns_latest_signal(ohlcv_history):
|
||||
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'
|
||||
|
||||
assert _STRATEGY.get_entry_signal(
|
||||
'ETH/BTC', '5m', mocked_history) == (SignalDirection.SHORT, None)
|
||||
'ETH/BTC', '5m', mocked_history) == (SignalDirection.SHORT, 'sell_signal_01')
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (True, False)
|
||||
|
||||
@@ -177,7 +179,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
|
||||
|
||||
@@ -224,8 +225,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'])
|
||||
@@ -248,8 +249,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
|
||||
@@ -262,7 +263,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')
|
||||
@@ -273,7 +273,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')
|
||||
@@ -291,7 +290,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(
|
||||
@@ -330,7 +328,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(
|
||||
@@ -365,7 +362,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(
|
||||
@@ -418,8 +414,6 @@ def test_min_roi_reached3(default_conf, fee) -> 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',
|
||||
@@ -466,8 +460,6 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
|
||||
|
||||
def test_custom_sell(default_conf, fee, caplog) -> None:
|
||||
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@@ -516,23 +508,49 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
|
||||
assert log_has_re('Custom sell reason returned from custom_sell 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)
|
||||
@@ -541,8 +559,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)
|
||||
|
||||
@@ -550,16 +568,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
|
||||
|
||||
@@ -569,8 +587,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()
|
||||
@@ -578,20 +596,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)
|
||||
|
@@ -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) == 5
|
||||
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) == 6
|
||||
# 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]) == 5
|
||||
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'}
|
||||
@@ -117,11 +119,11 @@ 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)
|
||||
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)
|
||||
dataframe = strategy.advise_exit(df_indicators, metadata=metadata)
|
||||
assert 'sell' not in dataframe.columns
|
||||
assert 'exit_long' in dataframe.columns
|
||||
|
||||
@@ -129,7 +131,7 @@ def test_strategy(result, 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
|
||||
@@ -146,7 +148,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)
|
||||
@@ -158,7 +160,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)
|
||||
@@ -171,7 +173,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
|
||||
|
||||
@@ -191,7 +193,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'
|
||||
})
|
||||
@@ -207,7 +209,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)
|
||||
@@ -227,7 +229,7 @@ def test_strategy_override_order_types(caplog, default_conf):
|
||||
'stoploss_on_exchange': True,
|
||||
}
|
||||
default_conf.update({
|
||||
'strategy': 'StrategyTestV2',
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
'order_types': order_types
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@@ -241,12 +243,12 @@ def test_strategy_override_order_types(caplog, default_conf):
|
||||
" 'stoploss_on_exchange': True}.", caplog)
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'StrategyTestV2',
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
'order_types': {'buy': '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)
|
||||
|
||||
@@ -260,7 +262,7 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
||||
}
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'StrategyTestV2',
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
'order_time_in_force': order_time_in_force
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@@ -273,20 +275,20 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
||||
" {'buy': 'fok', 'sell': 'gtc'}.", caplog)
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'StrategyTestV2',
|
||||
'strategy': CURRENT_TEST_STRATEGY,
|
||||
'order_time_in_force': {'buy': '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
|
||||
@@ -296,7 +298,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)
|
||||
@@ -309,7 +311,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
|
||||
@@ -319,7 +321,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)
|
||||
@@ -347,7 +349,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!" \
|
||||
@@ -356,7 +358,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!" \
|
||||
@@ -364,7 +366,7 @@ 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_call_deprecated_function(result, default_conf, caplog):
|
||||
default_location = Path(__file__).parent / "strats"
|
||||
del default_conf['timeframe']
|
||||
default_conf.update({'strategy': 'TestStrategyLegacyV1',
|
||||
@@ -384,13 +386,13 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
|
||||
assert isinstance(indicator_df, DataFrame)
|
||||
assert 'adx' in indicator_df.columns
|
||||
|
||||
enterdf = strategy.advise_buy(result, metadata=metadata)
|
||||
enterdf = strategy.advise_entry(result, metadata=metadata)
|
||||
assert isinstance(enterdf, DataFrame)
|
||||
assert 'buy' in enterdf.columns
|
||||
assert 'enter_long' in enterdf.columns
|
||||
|
||||
exitdf = strategy.advise_sell(result, metadata=metadata)
|
||||
exitdf = strategy.advise_exit(result, metadata=metadata)
|
||||
assert isinstance(exitdf, DataFrame)
|
||||
assert 'sell' in exitdf
|
||||
assert 'exit_long' in exitdf
|
||||
|
||||
assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.",
|
||||
caplog)
|
||||
@@ -411,13 +413,13 @@ def test_strategy_interface_versioning(result, default_conf):
|
||||
assert isinstance(indicator_df, DataFrame)
|
||||
assert 'adx' in indicator_df.columns
|
||||
|
||||
enterdf = strategy.advise_buy(result, metadata=metadata)
|
||||
enterdf = strategy.advise_entry(result, metadata=metadata)
|
||||
assert isinstance(enterdf, DataFrame)
|
||||
|
||||
assert 'buy' not in enterdf.columns
|
||||
assert 'enter_long' in enterdf.columns
|
||||
|
||||
exitdf = strategy.advise_sell(result, metadata=metadata)
|
||||
exitdf = strategy.advise_exit(result, metadata=metadata)
|
||||
assert isinstance(exitdf, DataFrame)
|
||||
assert 'sell' not in exitdf
|
||||
assert 'exit_long' in exitdf
|
||||
|
Reference in New Issue
Block a user