Merge pull request #6523 from freqtrade/short_exit_trend
Update methods wording
This commit is contained in:
commit
b8cbc4dcb7
@ -220,6 +220,31 @@ class StrategyResolver(IResolver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if strategy:
|
if strategy:
|
||||||
|
if strategy.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
|
# Require new method
|
||||||
|
if not check_override(strategy, IStrategy, 'populate_entry_trend'):
|
||||||
|
raise OperationalException("`populate_entry_trend` must be implemented.")
|
||||||
|
if not check_override(strategy, IStrategy, 'populate_exit_trend'):
|
||||||
|
raise OperationalException("`populate_exit_trend` must be implemented.")
|
||||||
|
if check_override(strategy, IStrategy, 'custom_sell'):
|
||||||
|
raise OperationalException(
|
||||||
|
"Please migrate your implementation of `custom_sell` to `custom_exit`.")
|
||||||
|
else:
|
||||||
|
# TODO: Implementing one of the following methods should show a deprecation warning
|
||||||
|
# buy_trend and sell_trend, custom_sell
|
||||||
|
if (
|
||||||
|
not check_override(strategy, IStrategy, 'populate_buy_trend')
|
||||||
|
and not check_override(strategy, IStrategy, 'populate_entry_trend')
|
||||||
|
):
|
||||||
|
raise OperationalException(
|
||||||
|
"`populate_entry_trend` or `populate_buy_trend` must be implemented.")
|
||||||
|
if (
|
||||||
|
not check_override(strategy, IStrategy, 'populate_sell_trend')
|
||||||
|
and not check_override(strategy, IStrategy, 'populate_exit_trend')
|
||||||
|
):
|
||||||
|
raise OperationalException(
|
||||||
|
"`populate_exit_trend` or `populate_sell_trend` must be implemented.")
|
||||||
|
|
||||||
strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args)
|
strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args)
|
||||||
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
|
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
|
||||||
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
|
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
|
||||||
@ -236,3 +261,11 @@ class StrategyResolver(IResolver):
|
|||||||
f"Impossible to load Strategy '{strategy_name}'. This class does not exist "
|
f"Impossible to load Strategy '{strategy_name}'. This class does not exist "
|
||||||
"or contains Python code errors."
|
"or contains Python code errors."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_override(object, parentclass, attribute):
|
||||||
|
"""
|
||||||
|
Checks if a object overrides the parent class attribute.
|
||||||
|
:returns: True if the object is overridden.
|
||||||
|
"""
|
||||||
|
return getattr(type(object), attribute) != getattr(parentclass, attribute)
|
||||||
|
@ -29,7 +29,7 @@ from freqtrade.wallets import Wallets
|
|||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
CUSTOM_SELL_MAX_LENGTH = 64
|
CUSTOM_EXIT_MAX_LENGTH = 64
|
||||||
|
|
||||||
|
|
||||||
class SellCheckTuple:
|
class SellCheckTuple:
|
||||||
@ -177,19 +177,27 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
"""
|
"""
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
DEPRECATED - please migrate to populate_entry_trend
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:param metadata: Additional information, like the currently traded pair
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
@abstractmethod
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the entry signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
|
:return: DataFrame with entry columns populated
|
||||||
|
"""
|
||||||
|
return self.populate_buy_trend(dataframe, metadata)
|
||||||
|
|
||||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
|
DEPRECATED - please migrate to populate_exit_trend
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:param metadata: Additional information, like the currently traded pair
|
:param metadata: Additional information, like the currently traded pair
|
||||||
@ -197,6 +205,15 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
"""
|
"""
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the exit signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
|
:return: DataFrame with exit columns populated
|
||||||
|
"""
|
||||||
|
return self.populate_sell_trend(dataframe, metadata)
|
||||||
|
|
||||||
def bot_loop_start(self, **kwargs) -> None:
|
def bot_loop_start(self, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Called at the start of the bot iteration (one loop).
|
Called at the start of the bot iteration (one loop).
|
||||||
@ -363,6 +380,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
|
def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
|
||||||
current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
|
current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
|
||||||
"""
|
"""
|
||||||
|
DEPRECATED - please use custom_exit instead.
|
||||||
Custom exit signal logic indicating that specified position should be sold. Returning a
|
Custom exit signal logic indicating that specified position should be sold. Returning a
|
||||||
string or True from this method is equal to setting exit signal on a candle at specified
|
string or True from this method is equal to setting exit signal on a candle at specified
|
||||||
time. This method is not called when exit signal is set.
|
time. This method is not called when exit signal is set.
|
||||||
@ -384,6 +402,30 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
|
||||||
|
current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
|
||||||
|
"""
|
||||||
|
Custom exit signal logic indicating that specified position should be sold. Returning a
|
||||||
|
string or True from this method is equal to setting exit signal on a candle at specified
|
||||||
|
time. This method is not called when exit signal is set.
|
||||||
|
|
||||||
|
This method should be overridden to create exit signals that depend on trade parameters. For
|
||||||
|
example you could implement an exit relative to the candle when the trade was opened,
|
||||||
|
or a custom 1:2 risk-reward ROI.
|
||||||
|
|
||||||
|
Custom exit reason max length is 64. Exceeding characters will be removed.
|
||||||
|
|
||||||
|
:param pair: Pair that's currently analyzed
|
||||||
|
:param trade: trade object.
|
||||||
|
:param current_time: datetime object, containing the current datetime
|
||||||
|
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||||
|
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||||
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
|
:return: To execute exit, return a string with custom sell reason or True. Otherwise return
|
||||||
|
None or False.
|
||||||
|
"""
|
||||||
|
return self.custom_sell(pair, trade, current_time, current_rate, current_profit, **kwargs)
|
||||||
|
|
||||||
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
||||||
proposed_stake: float, min_stake: float, max_stake: float,
|
proposed_stake: float, min_stake: float, max_stake: float,
|
||||||
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
||||||
@ -849,17 +891,17 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
sell_signal = SellType.SELL_SIGNAL
|
sell_signal = SellType.SELL_SIGNAL
|
||||||
else:
|
else:
|
||||||
trade_type = "exit_short" if trade.is_short else "sell"
|
trade_type = "exit_short" if trade.is_short else "sell"
|
||||||
custom_reason = strategy_safe_wrapper(self.custom_sell, default_retval=False)(
|
custom_reason = strategy_safe_wrapper(self.custom_exit, default_retval=False)(
|
||||||
pair=trade.pair, trade=trade, current_time=current_time,
|
pair=trade.pair, trade=trade, current_time=current_time,
|
||||||
current_rate=current_rate, current_profit=current_profit)
|
current_rate=current_rate, current_profit=current_profit)
|
||||||
if custom_reason:
|
if custom_reason:
|
||||||
sell_signal = SellType.CUSTOM_SELL
|
sell_signal = SellType.CUSTOM_SELL
|
||||||
if isinstance(custom_reason, str):
|
if isinstance(custom_reason, str):
|
||||||
if len(custom_reason) > CUSTOM_SELL_MAX_LENGTH:
|
if len(custom_reason) > CUSTOM_EXIT_MAX_LENGTH:
|
||||||
logger.warning(f'Custom {trade_type} reason returned from '
|
logger.warning(f'Custom {trade_type} reason returned from '
|
||||||
f'custom_{trade_type} is too long and was trimmed'
|
f'custom_exit is too long and was trimmed'
|
||||||
f'to {CUSTOM_SELL_MAX_LENGTH} characters.')
|
f'to {CUSTOM_EXIT_MAX_LENGTH} characters.')
|
||||||
custom_reason = custom_reason[:CUSTOM_SELL_MAX_LENGTH]
|
custom_reason = custom_reason[:CUSTOM_EXIT_MAX_LENGTH]
|
||||||
else:
|
else:
|
||||||
custom_reason = None
|
custom_reason = None
|
||||||
if sell_signal in (SellType.CUSTOM_SELL, SellType.SELL_SIGNAL):
|
if sell_signal in (SellType.CUSTOM_SELL, SellType.SELL_SIGNAL):
|
||||||
@ -1072,7 +1114,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
"the current function headers!", DeprecationWarning)
|
"the current function headers!", DeprecationWarning)
|
||||||
df = self.populate_buy_trend(dataframe) # type: ignore
|
df = self.populate_buy_trend(dataframe) # type: ignore
|
||||||
else:
|
else:
|
||||||
df = self.populate_buy_trend(dataframe, metadata)
|
df = self.populate_entry_trend(dataframe, metadata)
|
||||||
if 'enter_long' not in df.columns:
|
if 'enter_long' not in df.columns:
|
||||||
df = df.rename({'buy': 'enter_long', 'buy_tag': 'enter_tag'}, axis='columns')
|
df = df.rename({'buy': 'enter_long', 'buy_tag': 'enter_tag'}, axis='columns')
|
||||||
|
|
||||||
@ -1094,7 +1136,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
"the current function headers!", DeprecationWarning)
|
"the current function headers!", DeprecationWarning)
|
||||||
df = self.populate_sell_trend(dataframe) # type: ignore
|
df = self.populate_sell_trend(dataframe) # type: ignore
|
||||||
else:
|
else:
|
||||||
df = self.populate_sell_trend(dataframe, metadata)
|
df = self.populate_exit_trend(dataframe, metadata)
|
||||||
if 'exit_long' not in df.columns:
|
if 'exit_long' not in df.columns:
|
||||||
df = df.rename({'sell': 'exit_long'}, axis='columns')
|
df = df.rename({'sell': 'exit_long'}, axis='columns')
|
||||||
return df
|
return df
|
||||||
|
@ -29,7 +29,7 @@ class {{ strategy }}(IStrategy):
|
|||||||
|
|
||||||
You must keep:
|
You must keep:
|
||||||
- the lib in the section "Do not remove these libs"
|
- the lib in the section "Do not remove these libs"
|
||||||
- the methods: populate_indicators, populate_buy_trend, populate_sell_trend
|
- the methods: populate_indicators, populate_entry_trend, populate_exit_trend
|
||||||
You should keep:
|
You should keep:
|
||||||
- timeframe, minimal_roi, stoploss, trailing_*
|
- timeframe, minimal_roi, stoploss, trailing_*
|
||||||
"""
|
"""
|
||||||
@ -119,12 +119,12 @@ class {{ strategy }}(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
Based on TA indicators, populates the entry signal for the given dataframe
|
||||||
:param dataframe: DataFrame populated with indicators
|
:param dataframe: DataFrame
|
||||||
:param metadata: Additional information, like the currently traded pair
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with entry columns populated
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
@ -144,12 +144,12 @@ class {{ strategy }}(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the exit signal for the given dataframe
|
||||||
:param dataframe: DataFrame populated with indicators
|
:param dataframe: DataFrame
|
||||||
:param metadata: Additional information, like the currently traded pair
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with exit columns populated
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
|
@ -30,7 +30,7 @@ class SampleShortStrategy(IStrategy):
|
|||||||
|
|
||||||
You must keep:
|
You must keep:
|
||||||
- the lib in the section "Do not remove these libs"
|
- the lib in the section "Do not remove these libs"
|
||||||
- the methods: populate_indicators, populate_buy_trend, populate_sell_trend
|
- the methods: populate_indicators, populate_entry_trend, populate_exit_trend
|
||||||
You should keep:
|
You should keep:
|
||||||
- timeframe, minimal_roi, stoploss, trailing_*
|
- timeframe, minimal_roi, stoploss, trailing_*
|
||||||
"""
|
"""
|
||||||
@ -341,7 +341,7 @@ class SampleShortStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
:param dataframe: DataFrame populated with indicators
|
:param dataframe: DataFrame populated with indicators
|
||||||
@ -361,7 +361,7 @@ class SampleShortStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
:param dataframe: DataFrame populated with indicators
|
:param dataframe: DataFrame populated with indicators
|
||||||
|
@ -29,7 +29,7 @@ class SampleStrategy(IStrategy):
|
|||||||
|
|
||||||
You must keep:
|
You must keep:
|
||||||
- the lib in the section "Do not remove these libs"
|
- the lib in the section "Do not remove these libs"
|
||||||
- the methods: populate_indicators, populate_buy_trend, populate_sell_trend
|
- the methods: populate_indicators, populate_entry_trend, populate_exit_trend
|
||||||
You should keep:
|
You should keep:
|
||||||
- timeframe, minimal_roi, stoploss, trailing_*
|
- timeframe, minimal_roi, stoploss, trailing_*
|
||||||
"""
|
"""
|
||||||
@ -342,12 +342,12 @@ class SampleStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
Based on TA indicators, populates the entry signal for the given dataframe
|
||||||
:param dataframe: DataFrame populated with indicators
|
:param dataframe: DataFrame
|
||||||
:param metadata: Additional information, like the currently traded pair
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with entry columns populated
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
@ -371,12 +371,12 @@ class SampleStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the exit signal for the given dataframe
|
||||||
:param dataframe: DataFrame populated with indicators
|
:param dataframe: DataFrame
|
||||||
:param metadata: Additional information, like the currently traded pair
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with sell column
|
:return: DataFrame with exit columns populated
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
|
@ -92,7 +92,7 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime',
|
|||||||
"""
|
"""
|
||||||
return self.stoploss
|
return self.stoploss
|
||||||
|
|
||||||
def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
|
def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
|
||||||
current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]':
|
current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]':
|
||||||
"""
|
"""
|
||||||
Custom sell signal logic indicating that specified position should be sold. Returning a
|
Custom sell signal logic indicating that specified position should be sold. Returning a
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
"""
|
||||||
|
The strategies here are minimal strategies designed to fail loading in certain conditions.
|
||||||
|
They are not operational, and don't aim to be.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
|
||||||
|
|
||||||
|
class TestStrategyNoImplements(IStrategy):
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
return super().populate_indicators(dataframe, metadata)
|
||||||
|
|
||||||
|
|
||||||
|
class TestStrategyNoImplementSell(TestStrategyNoImplements):
|
||||||
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
return super().populate_entry_trend(dataframe, metadata)
|
||||||
|
|
||||||
|
|
||||||
|
class TestStrategyImplementCustomSell(TestStrategyNoImplementSell):
|
||||||
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
return super().populate_exit_trend(dataframe, metadata)
|
||||||
|
|
||||||
|
def custom_sell(self, pair: str, trade, current_time: datetime,
|
||||||
|
current_rate: float, current_profit: float,
|
||||||
|
**kwargs):
|
||||||
|
return False
|
@ -125,7 +125,7 @@ class StrategyTestV3(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
@ -147,7 +147,7 @@ class StrategyTestV3(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
|
@ -13,8 +13,8 @@ def test_strategy_test_v3_structure():
|
|||||||
assert hasattr(StrategyTestV3, 'stoploss')
|
assert hasattr(StrategyTestV3, 'stoploss')
|
||||||
assert hasattr(StrategyTestV3, 'timeframe')
|
assert hasattr(StrategyTestV3, 'timeframe')
|
||||||
assert hasattr(StrategyTestV3, 'populate_indicators')
|
assert hasattr(StrategyTestV3, 'populate_indicators')
|
||||||
assert hasattr(StrategyTestV3, 'populate_buy_trend')
|
assert hasattr(StrategyTestV3, 'populate_entry_trend')
|
||||||
assert hasattr(StrategyTestV3, 'populate_sell_trend')
|
assert hasattr(StrategyTestV3, 'populate_exit_trend')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('is_short,side', [
|
@pytest.mark.parametrize('is_short,side', [
|
||||||
|
@ -477,7 +477,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
|
|||||||
strategy.custom_stoploss = original_stopvalue
|
strategy.custom_stoploss = original_stopvalue
|
||||||
|
|
||||||
|
|
||||||
def test_custom_sell(default_conf, fee, caplog) -> None:
|
def test_custom_exit(default_conf, fee, caplog) -> None:
|
||||||
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -499,7 +499,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
|
|||||||
assert res.sell_flag is False
|
assert res.sell_flag is False
|
||||||
assert res.sell_type == SellType.NONE
|
assert res.sell_type == SellType.NONE
|
||||||
|
|
||||||
strategy.custom_sell = MagicMock(return_value=True)
|
strategy.custom_exit = MagicMock(return_value=True)
|
||||||
res = strategy.should_exit(trade, 1, now,
|
res = strategy.should_exit(trade, 1, now,
|
||||||
enter=False, exit_=False,
|
enter=False, exit_=False,
|
||||||
low=None, high=None)
|
low=None, high=None)
|
||||||
@ -507,7 +507,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
|
|||||||
assert res.sell_type == SellType.CUSTOM_SELL
|
assert res.sell_type == SellType.CUSTOM_SELL
|
||||||
assert res.sell_reason == 'custom_sell'
|
assert res.sell_reason == 'custom_sell'
|
||||||
|
|
||||||
strategy.custom_sell = MagicMock(return_value='hello world')
|
strategy.custom_exit = MagicMock(return_value='hello world')
|
||||||
|
|
||||||
res = strategy.should_exit(trade, 1, now,
|
res = strategy.should_exit(trade, 1, now,
|
||||||
enter=False, exit_=False,
|
enter=False, exit_=False,
|
||||||
@ -517,14 +517,14 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
|
|||||||
assert res.sell_reason == 'hello world'
|
assert res.sell_reason == 'hello world'
|
||||||
|
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
strategy.custom_sell = MagicMock(return_value='h' * 100)
|
strategy.custom_exit = MagicMock(return_value='h' * 100)
|
||||||
res = strategy.should_exit(trade, 1, now,
|
res = strategy.should_exit(trade, 1, now,
|
||||||
enter=False, exit_=False,
|
enter=False, exit_=False,
|
||||||
low=None, high=None)
|
low=None, high=None)
|
||||||
assert res.sell_type == SellType.CUSTOM_SELL
|
assert res.sell_type == SellType.CUSTOM_SELL
|
||||||
assert res.sell_flag is True
|
assert res.sell_flag is True
|
||||||
assert res.sell_reason == 'h' * 64
|
assert res.sell_reason == 'h' * 64
|
||||||
assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog)
|
assert log_has_re('Custom sell reason returned from custom_exit is too long.*', caplog)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('side', TRADE_SIDES)
|
@pytest.mark.parametrize('side', TRADE_SIDES)
|
||||||
|
@ -144,6 +144,16 @@ def test_strategy_can_short(caplog, default_conf):
|
|||||||
assert isinstance(strat, IStrategy)
|
assert isinstance(strat, IStrategy)
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_implements_populate_entry(caplog, default_conf):
|
||||||
|
caplog.set_level(logging.INFO)
|
||||||
|
default_conf.update({
|
||||||
|
'strategy': "StrategyTestV2",
|
||||||
|
})
|
||||||
|
default_conf['trading_mode'] = 'futures'
|
||||||
|
with pytest.raises(OperationalException, match="`populate_entry_trend` must be implemented."):
|
||||||
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_strategy_override_minimal_roi(caplog, default_conf):
|
def test_strategy_override_minimal_roi(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
@ -381,6 +391,40 @@ def test_deprecate_populate_indicators(result, default_conf):
|
|||||||
in str(w[-1].message)
|
in str(w[-1].message)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||||
|
def test_missing_implements(default_conf):
|
||||||
|
default_location = Path(__file__).parent / "strats/broken_strats"
|
||||||
|
default_conf.update({'strategy': 'TestStrategyNoImplements',
|
||||||
|
'strategy_path': default_location})
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r"`populate_entry_trend` or `populate_buy_trend`.*"):
|
||||||
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
|
default_conf['strategy'] = 'TestStrategyNoImplementSell'
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r"`populate_exit_trend` or `populate_sell_trend`.*"):
|
||||||
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
|
# Futures mode is more strict ...
|
||||||
|
default_conf['trading_mode'] = 'futures'
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r"`populate_exit_trend` must be implemented.*"):
|
||||||
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
|
default_conf['strategy'] = 'TestStrategyNoImplements'
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r"`populate_entry_trend` must be implemented.*"):
|
||||||
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
|
default_conf['strategy'] = 'TestStrategyImplementCustomSell'
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r"Please migrate your implementation of `custom_sell`.*"):
|
||||||
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||||
def test_call_deprecated_function(result, default_conf, caplog):
|
def test_call_deprecated_function(result, default_conf, caplog):
|
||||||
default_location = Path(__file__).parent / "strats"
|
default_location = Path(__file__).parent / "strats"
|
||||||
|
Loading…
Reference in New Issue
Block a user