Added short and exit_short to strategy
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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')
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user