Added short and exit_short to strategy

This commit is contained in:
Sam Germain
2021-08-08 03:38:34 -06:00
parent 98fe3e73de
commit d4a7d2d444
24 changed files with 862 additions and 152 deletions

View File

@@ -154,3 +154,48 @@ class DefaultStrategy(IStrategy):
),
'sell'] = 1
return dataframe
def populate_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the short signal for the given dataframe
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with short column
"""
dataframe.loc[
(
(dataframe['rsi'] > 65) &
(dataframe['fastd'] > 65) &
(dataframe['adx'] < 70) &
(dataframe['plus_di'] < 0.5) # TODO-lev: What to do here
) |
(
(dataframe['adx'] < 35) &
(dataframe['plus_di'] < 0.5) # TODO-lev: What to do here
),
'short'] = 1
return dataframe
def populate_exit_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the exit_short signal for the given dataframe
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with exit_short column
"""
dataframe.loc[
(
(
(qtpylib.crossed_below(dataframe['rsi'], 30)) |
(qtpylib.crossed_below(dataframe['fastd'], 30))
) &
(dataframe['adx'] < 90) &
(dataframe['minus_di'] < 0) # TODO-lev: what to do here
) |
(
(dataframe['adx'] > 30) &
(dataframe['minus_di'] < 0.5) # TODO-lev: what to do here
),
'exit_short'] = 1
return dataframe

View File

@@ -60,6 +60,15 @@ class HyperoptableStrategy(IStrategy):
'sell_minusdi': 0.4
}
short_params = {
'short_rsi': 65,
}
exit_short_params = {
'exit_short_rsi': 26,
'exit_short_minusdi': 0.6
}
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')
@@ -78,6 +87,12 @@ class HyperoptableStrategy(IStrategy):
})
return prot
short_rsi = IntParameter([50, 100], default=70, space='sell')
short_plusdi = RealParameter(low=0, high=1, default=0.5, space='sell')
exit_short_rsi = IntParameter(low=0, high=50, default=30, space='buy')
exit_short_minusdi = DecimalParameter(low=0, high=1, default=0.4999, decimals=3, space='buy',
load=False)
def informative_pairs(self):
"""
Define additional, informative pair/interval combinations to be cached from the exchange.
@@ -167,7 +182,7 @@ class HyperoptableStrategy(IStrategy):
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
:return: DataFrame with sell column
"""
dataframe.loc[
(
@@ -184,3 +199,48 @@ class HyperoptableStrategy(IStrategy):
),
'sell'] = 1
return dataframe
def populate_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the short signal for the given dataframe
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with short column
"""
dataframe.loc[
(
(dataframe['rsi'] > self.short_rsi.value) &
(dataframe['fastd'] > 65) &
(dataframe['adx'] < 70) &
(dataframe['plus_di'] < self.short_plusdi.value)
) |
(
(dataframe['adx'] < 35) &
(dataframe['plus_di'] < self.short_plusdi.value)
),
'short'] = 1
return dataframe
def populate_exit_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the exit_short signal for the given dataframe
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with exit_short column
"""
dataframe.loc[
(
(
(qtpylib.crossed_below(dataframe['rsi'], self.exit_short_rsi.value)) |
(qtpylib.crossed_below(dataframe['fastd'], 30))
) &
(dataframe['adx'] < 90) &
(dataframe['minus_di'] < 0) # TODO-lev: What should this be
) |
(
(dataframe['adx'] < 30) &
(dataframe['minus_di'] < self.exit_short_minusdi.value)
),
'exit_short'] = 1
return dataframe

View File

@@ -85,3 +85,34 @@ class TestStrategyLegacy(IStrategy):
),
'sell'] = 1
return dataframe
def populate_short_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
(dataframe['adx'] > 30) &
(dataframe['tema'] > dataframe['tema'].shift(1)) &
(dataframe['volume'] > 0)
),
'buy'] = 1
return dataframe
def populate_exit_short_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
(dataframe['adx'] > 70) &
(dataframe['tema'] < dataframe['tema'].shift(1)) &
(dataframe['volume'] > 0)
),
'sell'] = 1
return dataframe

View File

@@ -14,6 +14,8 @@ def test_default_strategy_structure():
assert hasattr(DefaultStrategy, 'populate_indicators')
assert hasattr(DefaultStrategy, 'populate_buy_trend')
assert hasattr(DefaultStrategy, 'populate_sell_trend')
assert hasattr(DefaultStrategy, 'populate_short_trend')
assert hasattr(DefaultStrategy, 'populate_exit_short_trend')
def test_default_strategy(result, fee):
@@ -27,6 +29,10 @@ def test_default_strategy(result, fee):
assert type(indicators) is DataFrame
assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame
assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame
# TODO-lev: I think these two should be commented out in the strategy by default
# TODO-lev: so they can be tested, but the tests can't really remain
assert type(strategy.populate_short_trend(indicators, metadata)) is DataFrame
assert type(strategy.populate_exit_short_trend(indicators, metadata)) is DataFrame
trade = Trade(
open_rate=19_000,
@@ -37,10 +43,28 @@ def test_default_strategy(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
is_short=False, current_time=datetime.utcnow()) 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
is_short=False, current_time=datetime.utcnow()) is True
# TODO-lev: Test for shorts?
assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(),
current_rate=20_000, current_profit=0.05) == strategy.stoploss
short_trade = Trade(
open_rate=21_000,
amount=0.1,
pair='ETH/BTC',
fee_open=fee.return_value
)
assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc',
is_short=True, current_time=datetime.utcnow()) is True
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=short_trade, order_type='limit',
amount=0.1, rate=20000, time_in_force='gtc',
sell_reason='roi', is_short=True,
current_time=datetime.utcnow()) is True

View File

@@ -156,17 +156,21 @@ def test_ignore_expired_candle(default_conf):
# Add 1 candle length as the "latest date" defines candle open.
current_time = latest_date + timedelta(seconds=80 + 300)
assert strategy.ignore_expired_candle(latest_date=latest_date,
current_time=current_time,
timeframe_seconds=300,
buy=True) is True
assert strategy.ignore_expired_candle(
latest_date=latest_date,
current_time=current_time,
timeframe_seconds=300,
enter=True
) is True
current_time = latest_date + timedelta(seconds=30 + 300)
assert not strategy.ignore_expired_candle(latest_date=latest_date,
current_time=current_time,
timeframe_seconds=300,
buy=True) is True
assert not strategy.ignore_expired_candle(
latest_date=latest_date,
current_time=current_time,
timeframe_seconds=300,
enter=True
) is True
def test_assert_df_raise(mocker, caplog, ohlcv_history):
@@ -478,20 +482,20 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
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)
enter_mock = MagicMock(side_effect=lambda x, meta, is_short: x)
exit_mock = MagicMock(side_effect=lambda x, meta, is_short: x)
mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy',
advise_indicators=ind_mock,
advise_buy=buy_mock,
advise_sell=sell_mock,
advise_enter=enter_mock,
advise_exit=exit_mock,
)
strategy = DefaultStrategy({})
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 enter_mock.call_count == 2
assert enter_mock.call_count == 2
assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
@@ -500,8 +504,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 enter_mock.call_count == 4
assert enter_mock.call_count == 4
assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
@@ -509,13 +513,13 @@ 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)
enter_mock = MagicMock(side_effect=lambda x, meta, is_short: x)
exit_mock = MagicMock(side_effect=lambda x, meta, is_short: x)
mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy',
advise_indicators=ind_mock,
advise_buy=buy_mock,
advise_sell=sell_mock,
advise_enter=enter_mock,
advise_exit=exit_mock,
)
strategy = DefaultStrategy({})
@@ -528,8 +532,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 enter_mock.call_count == 2 # Once for buy, once for short
assert enter_mock.call_count == 2
assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
caplog.clear()
@@ -537,8 +541,8 @@ 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 enter_mock.call_count == 2
assert enter_mock.call_count == 2
# only skipped analyze adds buy and sell columns, otherwise it's all mocked
assert 'buy' in ret.columns
assert 'sell' in ret.columns
@@ -743,10 +747,10 @@ def test_auto_hyperopt_interface(default_conf):
assert strategy.sell_minusdi.value == 0.5
all_params = strategy.detect_all_parameters()
assert isinstance(all_params, dict)
assert len(all_params['buy']) == 2
assert len(all_params['sell']) == 2
# Number of Hyperoptable parameters
assert all_params['count'] == 6
# TODO-lev: Should these be 4,4 and 10?
assert len(all_params['buy']) == 4
assert len(all_params['sell']) == 4
assert all_params['count'] == 10
strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy')

View File

@@ -117,12 +117,18 @@ 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_enter(df_indicators, metadata=metadata, is_short=False)
assert 'buy' in dataframe.columns
dataframe = strategy.advise_sell(df_indicators, metadata=metadata)
dataframe = strategy.advise_exit(df_indicators, metadata=metadata, is_short=False)
assert 'sell' in dataframe.columns
dataframe = strategy.advise_enter(df_indicators, metadata=metadata, is_short=True)
assert 'short' in dataframe.columns
dataframe = strategy.advise_exit(df_indicators, metadata=metadata, is_short=True)
assert 'exit_short' in dataframe.columns
def test_strategy_override_minimal_roi(caplog, default_conf):
caplog.set_level(logging.INFO)
@@ -218,6 +224,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf):
def test_strategy_override_order_types(caplog, default_conf):
caplog.set_level(logging.INFO)
# TODO-lev: Maybe change
order_types = {
'buy': 'market',
'sell': 'limit',
@@ -345,7 +352,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_enter(indicators, {'pair': 'ETH/BTC'}, is_short=False) # TODO-lev
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \
@@ -354,7 +361,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'}, is_short=False) # TODO-lev
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \
@@ -374,6 +381,8 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
assert strategy._populate_fun_len == 2
assert strategy._buy_fun_len == 2
assert strategy._sell_fun_len == 2
# assert strategy._short_fun_len == 2
# assert strategy._exit_short_fun_len == 2
assert strategy.INTERFACE_VERSION == 1
assert strategy.timeframe == '5m'
assert strategy.ticker_interval == '5m'
@@ -382,14 +391,22 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
assert isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns
buydf = strategy.advise_buy(result, metadata=metadata)
buydf = strategy.advise_enter(result, metadata=metadata, is_short=False)
assert isinstance(buydf, DataFrame)
assert 'buy' in buydf.columns
selldf = strategy.advise_sell(result, metadata=metadata)
selldf = strategy.advise_exit(result, metadata=metadata, is_short=False)
assert isinstance(selldf, DataFrame)
assert 'sell' in selldf
# shortdf = strategy.advise_enter(result, metadata=metadata, is_short=True)
# assert isinstance(shortdf, DataFrame)
# assert 'short' in shortdf.columns
# exit_shortdf = strategy.advise_exit(result, metadata=metadata, is_short=True)
# assert isinstance(exit_shortdf, DataFrame)
# assert 'exit_short' in exit_shortdf
assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.",
caplog)
@@ -403,16 +420,26 @@ def test_strategy_interface_versioning(result, monkeypatch, default_conf):
assert strategy._populate_fun_len == 3
assert strategy._buy_fun_len == 3
assert strategy._sell_fun_len == 3
assert strategy._short_fun_len == 3
assert strategy._exit_short_fun_len == 3
assert strategy.INTERFACE_VERSION == 2
indicator_df = strategy.advise_indicators(result, metadata=metadata)
assert isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns
buydf = strategy.advise_buy(result, metadata=metadata)
buydf = strategy.advise_enter(result, metadata=metadata, is_short=False)
assert isinstance(buydf, DataFrame)
assert 'buy' in buydf.columns
selldf = strategy.advise_sell(result, metadata=metadata)
selldf = strategy.advise_exit(result, metadata=metadata, is_short=False)
assert isinstance(selldf, DataFrame)
assert 'sell' in selldf
shortdf = strategy.advise_enter(result, metadata=metadata, is_short=True)
assert isinstance(shortdf, DataFrame)
assert 'short' in shortdf.columns
exit_shortdf = strategy.advise_exit(result, metadata=metadata, is_short=True)
assert isinstance(exit_shortdf, DataFrame)
assert 'exit_short' in exit_shortdf