merged with feat/short

This commit is contained in:
Sam Germain 2021-09-27 23:26:20 -06:00
commit 1a132758d0
37 changed files with 528 additions and 300 deletions

View File

@ -42,7 +42,7 @@ docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_I
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
# Run backtest # Run backtest
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2 docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV3
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "failed running backtest" echo "failed running backtest"

View File

@ -53,7 +53,7 @@ docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE
docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT
# Run backtest # Run backtest
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2 docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV3
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "failed running backtest" echo "failed running backtest"

View File

@ -539,9 +539,10 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods # ... populate_* methods
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, **kwargs) -> bool: time_in_force: str, current_time: datetime,
side: str, **kwargs) -> bool:
""" """
Called right before placing a buy order. Called right before placing a entry order.
Timing for this function is critical, so avoid doing heavy computations or Timing for this function is critical, so avoid doing heavy computations or
network requests in this method. network requests in this method.
@ -549,12 +550,13 @@ class AwesomeStrategy(IStrategy):
When not implemented by a strategy, returns True (always confirming). When not implemented by a strategy, returns True (always confirming).
:param pair: Pair that's about to be bought. :param pair: Pair that's about to be bought/shorted.
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in target (quote) currency that's going to be traded. :param amount: Amount in target (quote) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the buy-order is placed on the exchange. :return bool: When True is returned, then the buy-order is placed on the exchange.
False aborts the process False aborts the process
@ -617,7 +619,7 @@ It is possible to manage your risk by reducing or increasing stake amount when p
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
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,
**kwargs) -> float: side: str, **kwargs) -> float:
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze() current_candle = dataframe.iloc[-1].squeeze()
@ -642,6 +644,34 @@ Freqtrade will fall back to the `proposed_stake` value should your code raise an
!!! Tip !!! Tip
Returning `0` or `None` will prevent trades from being placed. Returning `0` or `None` will prevent trades from being placed.
## Leverage Callback
When trading in markets that allow leverage, this method must return the desired Leverage (Defaults to 1 -> No leverage).
Assuming a capital of 500USDT, a trade with leverage=3 would result in a position with 500 x 3 = 1500 USDT.
Values that are above `max_leverage` will be adjusted to `max_leverage`.
For markets / exchanges that don't support leverage, this method is ignored.
``` python
class AwesomeStrategy(IStrategy):
def leverage(self, pair: str, current_time: 'datetime', current_rate: float,
proposed_leverage: float, max_leverage: float, side: str,
**kwargs) -> float:
"""
Customize leverage for each new trade.
:param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
:param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:return: A leverage amount, which is between 1.0 and max_leverage.
"""
return 1.0
```
--- ---
## Derived strategies ## Derived strategies

View File

@ -31,6 +31,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'profit_ratio', 'profit_abs', 'sell_reason', 'profit_ratio', 'profit_abs', 'sell_reason',
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs', 'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag'] 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag']
# TODO-lev: usage of the above might need compatibility code (buy_tag, is_short?, ...?)
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:

View File

@ -168,8 +168,8 @@ class Edge:
pair_data = pair_data.sort_values(by=['date']) pair_data = pair_data.sort_values(by=['date'])
pair_data = pair_data.reset_index(drop=True) pair_data = pair_data.reset_index(drop=True)
df_analyzed = self.strategy.advise_sell( df_analyzed = self.strategy.advise_exit(
dataframe=self.strategy.advise_buy( dataframe=self.strategy.advise_entry(
dataframe=pair_data, dataframe=pair_data,
metadata={'pair': pair} metadata={'pair': pair}
), ),

View File

@ -15,8 +15,7 @@ class SignalTagType(Enum):
""" """
Enum for signal columns Enum for signal columns
""" """
BUY_TAG = "buy_tag" ENTER_TAG = "enter_tag"
SHORT_TAG = "short_tag"
class SignalDirection(Enum): class SignalDirection(Enum):

View File

@ -182,12 +182,6 @@ class Kraken(Exchange):
Kraken set's the leverage as an option in the order object, so we need to Kraken set's the leverage as an option in the order object, so we need to
add it to params add it to params
""" """
if leverage > 1.0:
self._params['leverage'] = leverage
else:
if 'leverage' in self._params:
del self._params['leverage']
return return
def _get_params(self, ordertype: str, leverage: float, time_in_force: str = 'gtc') -> Dict: def _get_params(self, ordertype: str, leverage: float, time_in_force: str = 'gtc') -> Dict:

View File

@ -441,11 +441,11 @@ class FreqtradeBot(LoggingMixin):
return False return False
# running get_signal on historical data fetched # running get_signal on historical data fetched
(side, enter_tag) = self.strategy.get_entry_signal( (signal, enter_tag) = self.strategy.get_entry_signal(
pair, self.strategy.timeframe, analyzed_df pair, self.strategy.timeframe, analyzed_df
) )
if side: if signal:
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge) stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {}) bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {})
@ -585,7 +585,9 @@ class FreqtradeBot(LoggingMixin):
default_retval=stake_amount)( default_retval=stake_amount)(
pair=pair, current_time=datetime.now(timezone.utc), pair=pair, current_time=datetime.now(timezone.utc),
current_rate=enter_limit_requested, proposed_stake=stake_amount, current_rate=enter_limit_requested, proposed_stake=stake_amount,
min_stake=min_stake_amount, max_stake=max_stake_amount) min_stake=min_stake_amount, max_stake=max_stake_amount, side='long')
# TODO-lev: Add non-hardcoded "side" parameter
stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount) stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount)
if not stake_amount: if not stake_amount:
@ -603,10 +605,13 @@ class FreqtradeBot(LoggingMixin):
order_type = self.strategy.order_types.get('forcebuy', order_type) order_type = self.strategy.order_types.get('forcebuy', order_type)
# TODO-lev: Will this work for shorting? # TODO-lev: Will this work for shorting?
# TODO-lev: Add non-hardcoded "side" parameter
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested, pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
time_in_force=time_in_force, current_time=datetime.now(timezone.utc)): time_in_force=time_in_force, current_time=datetime.now(timezone.utc),
logger.info(f"User requested abortion of {name.lower()}ing {pair}") side='short' if is_short else 'long'
):
logger.info(f"User requested abortion of buying {pair}")
return False return False
amount = self.exchange.amount_to_precision(pair, amount) amount = self.exchange.amount_to_precision(pair, amount)
order = self.exchange.create_order( order = self.exchange.create_order(

View File

@ -45,8 +45,7 @@ LONG_IDX = 5
ELONG_IDX = 6 # Exit long ELONG_IDX = 6 # Exit long
SHORT_IDX = 7 SHORT_IDX = 7
ESHORT_IDX = 8 # Exit short ESHORT_IDX = 8 # Exit short
BUY_TAG_IDX = 9 ENTER_TAG_IDX = 9
SHORT_TAG_IDX = 10
class Backtesting: class Backtesting:
@ -139,6 +138,10 @@ class Backtesting:
self.config['startup_candle_count'] = self.required_startup self.config['startup_candle_count'] = self.required_startup
self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe) self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe)
# TODO-lev: This should come from the configuration setting or better a
# TODO-lev: combination of config/strategy "use_shorts"(?) and "can_short" from the exchange
self._can_short = False
self.progress = BTProgress() self.progress = BTProgress()
self.abort = False self.abort = False
@ -249,7 +252,7 @@ class Backtesting:
# Every change to this headers list must evaluate further usages of the resulting tuple # Every change to this headers list must evaluate further usages of the resulting tuple
# and eventually change the constants for indexes at the top # and eventually change the constants for indexes at the top
headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
'enter_short', 'exit_short', 'long_tag', 'short_tag'] 'enter_short', 'exit_short', 'enter_tag']
data: Dict = {} data: Dict = {}
self.progress.init_step(BacktestState.CONVERT, len(processed)) self.progress.init_step(BacktestState.CONVERT, len(processed))
@ -260,18 +263,10 @@ class Backtesting:
if not pair_data.empty: if not pair_data.empty:
# Cleanup from prior runs # Cleanup from prior runs
# TODO-lev: The below is not 100% compatible with the interface compatibility layer pair_data.drop(headers[5:] + ['buy', 'sell'], axis=1, errors='ignore')
if 'enter_long' in pair_data.columns:
pair_data.loc[:, 'enter_long'] = 0
pair_data.loc[:, 'enter_short'] = 0
if 'exit_long' in pair_data.columns:
pair_data.loc[:, 'exit_long'] = 0
pair_data.loc[:, 'exit_short'] = 0
pair_data.loc[:, 'long_tag'] = None
pair_data.loc[:, 'short_tag'] = None
df_analyzed = self.strategy.advise_sell( df_analyzed = self.strategy.advise_exit(
self.strategy.advise_buy(pair_data, {'pair': pair}), self.strategy.advise_entry(pair_data, {'pair': pair}),
{'pair': pair} {'pair': pair}
).copy() ).copy()
# Trim startup period from analyzed dataframe # Trim startup period from analyzed dataframe
@ -279,11 +274,11 @@ class Backtesting:
startup_candles=self.required_startup) startup_candles=self.required_startup)
# To avoid using data from future, we use buy/sell signals shifted # To avoid using data from future, we use buy/sell signals shifted
# from the previous candle # from the previous candle
df_analyzed.loc[:, 'enter_long'] = df_analyzed.loc[:, 'enter_long'].shift(1) for col in headers[5:]:
df_analyzed.loc[:, 'enter_short'] = df_analyzed.loc[:, 'enter_short'].shift(1) if col in df_analyzed.columns:
df_analyzed.loc[:, 'exit_long'] = df_analyzed.loc[:, 'exit_long'].shift(1) df_analyzed.loc[:, col] = df_analyzed.loc[:, col].shift(1)
df_analyzed.loc[:, 'exit_short'] = df_analyzed.loc[:, 'exit_short'].shift(1) else:
df_analyzed.loc[:, 'long_tag'] = df_analyzed.loc[:, 'long_tag'].shift(1) df_analyzed.loc[:, col] = 0 if col != 'enter_tag' else None
# Update dataprovider cache # Update dataprovider cache
self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed) self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed)
@ -434,7 +429,8 @@ class Backtesting:
stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
default_retval=stake_amount)( default_retval=stake_amount)(
pair=pair, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX], pair=pair, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX],
proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount) proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount,
side=direction)
stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount) stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount)
if not stake_amount: if not stake_amount:
@ -445,12 +441,13 @@ class Backtesting:
# Confirm trade entry: # Confirm trade entry:
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=stake_amount, rate=row[OPEN_IDX], pair=pair, order_type=order_type, amount=stake_amount, rate=row[OPEN_IDX],
time_in_force=time_in_force, current_time=row[DATE_IDX].to_pydatetime()): time_in_force=time_in_force, current_time=row[DATE_IDX].to_pydatetime(),
side=direction):
return None return None
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
# Enter trade # Enter trade
has_buy_tag = len(row) >= BUY_TAG_IDX + 1 has_enter_tag = len(row) >= ENTER_TAG_IDX + 1
trade = LocalTrade( trade = LocalTrade(
pair=pair, pair=pair,
open_rate=row[OPEN_IDX], open_rate=row[OPEN_IDX],
@ -460,7 +457,7 @@ class Backtesting:
fee_open=self.fee, fee_open=self.fee,
fee_close=self.fee, fee_close=self.fee,
is_open=True, is_open=True,
buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None, buy_tag=row[ENTER_TAG_IDX] if has_enter_tag else None,
exchange=self._exchange_name, exchange=self._exchange_name,
is_short=(direction == 'short'), is_short=(direction == 'short'),
) )
@ -499,8 +496,8 @@ class Backtesting:
def check_for_trade_entry(self, row) -> Optional[str]: def check_for_trade_entry(self, row) -> Optional[str]:
enter_long = row[LONG_IDX] == 1 enter_long = row[LONG_IDX] == 1
exit_long = row[ELONG_IDX] == 1 exit_long = row[ELONG_IDX] == 1
enter_short = row[SHORT_IDX] == 1 enter_short = self._can_short and row[SHORT_IDX] == 1
exit_short = row[ESHORT_IDX] == 1 exit_short = self._can_short and row[ESHORT_IDX] == 1
if enter_long == 1 and not any([exit_long, enter_short]): if enter_long == 1 and not any([exit_long, enter_short]):
# Long # Long

View File

@ -230,9 +230,9 @@ class IStrategy(ABC, HyperStrategyMixin):
""" """
pass pass
# TODO-lev: add side
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, **kwargs) -> bool: time_in_force: str, current_time: datetime,
side: str, **kwargs) -> bool:
""" """
Called right before placing a entry order. Called right before placing a entry order.
Timing for this function is critical, so avoid doing heavy computations or Timing for this function is critical, so avoid doing heavy computations or
@ -248,6 +248,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the buy-order is placed on the exchange. :return bool: When True is returned, then the buy-order is placed on the exchange.
False aborts the process False aborts the process
@ -365,13 +366,11 @@ class IStrategy(ABC, HyperStrategyMixin):
""" """
return None return None
# TODO-lev: add side
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,
**kwargs) -> float: side: str, **kwargs) -> float:
""" """
Customize stake size for each new trade. This method is not called when edge module is Customize stake size for each new trade.
enabled.
:param pair: Pair that's currently analyzed :param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
@ -379,10 +378,28 @@ class IStrategy(ABC, HyperStrategyMixin):
:param proposed_stake: A stake amount proposed by the bot. :param proposed_stake: A stake amount proposed by the bot.
:param min_stake: Minimal stake size allowed by exchange. :param min_stake: Minimal stake size allowed by exchange.
:param max_stake: Balance available for trading. :param max_stake: Balance available for trading.
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:return: A stake size, which is between min_stake and max_stake. :return: A stake size, which is between min_stake and max_stake.
""" """
return proposed_stake return proposed_stake
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, side: str,
**kwargs) -> float:
"""
Customize leverage for each new trade. This method is not called when edge module is
enabled.
:param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
:param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:return: A leverage amount, which is between 1.0 and max_leverage.
"""
return 1.0
def informative_pairs(self) -> ListPairsWithTimeframes: def informative_pairs(self) -> ListPairsWithTimeframes:
""" """
Define additional, informative pair/interval combinations to be cached from the exchange. Define additional, informative pair/interval combinations to be cached from the exchange.
@ -473,8 +490,8 @@ class IStrategy(ABC, HyperStrategyMixin):
""" """
logger.debug("TA Analysis Launched") logger.debug("TA Analysis Launched")
dataframe = self.advise_indicators(dataframe, metadata) dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata) dataframe = self.advise_entry(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata) dataframe = self.advise_exit(dataframe, metadata)
return dataframe return dataframe
def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@ -503,8 +520,7 @@ class IStrategy(ABC, HyperStrategyMixin):
dataframe[SignalType.EXIT_LONG.value] = 0 dataframe[SignalType.EXIT_LONG.value] = 0
dataframe[SignalType.ENTER_SHORT.value] = 0 dataframe[SignalType.ENTER_SHORT.value] = 0
dataframe[SignalType.EXIT_SHORT.value] = 0 dataframe[SignalType.EXIT_SHORT.value] = 0
dataframe[SignalTagType.BUY_TAG.value] = None dataframe[SignalTagType.ENTER_TAG.value] = None
dataframe[SignalTagType.SHORT_TAG.value] = None
# Other Defs in strategy that want to be called every loop here # Other Defs in strategy that want to be called every loop here
# twitter_sell = self.watch_twitter_feed(dataframe, metadata) # twitter_sell = self.watch_twitter_feed(dataframe, metadata)
@ -563,9 +579,8 @@ class IStrategy(ABC, HyperStrategyMixin):
message = "" message = ""
if dataframe is None: if dataframe is None:
message = "No dataframe returned (return statement missing?)." message = "No dataframe returned (return statement missing?)."
elif 'buy' not in dataframe: elif 'enter_long' not in dataframe:
# TODO-lev: Something? message = "enter_long/buy column not set."
message = "Buy column not set."
elif df_len != len(dataframe): elif df_len != len(dataframe):
message = message_template.format("length") message = message_template.format("length")
elif df_close != dataframe["close"].iloc[-1]: elif df_close != dataframe["close"].iloc[-1]:
@ -675,10 +690,10 @@ class IStrategy(ABC, HyperStrategyMixin):
enter_tag_value: Optional[str] = None enter_tag_value: Optional[str] = None
if enter_long == 1 and not any([exit_long, enter_short]): if enter_long == 1 and not any([exit_long, enter_short]):
enter_signal = SignalDirection.LONG enter_signal = SignalDirection.LONG
enter_tag_value = latest.get(SignalTagType.BUY_TAG.value, None) enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None)
if enter_short == 1 and not any([exit_short, enter_long]): if enter_short == 1 and not any([exit_short, enter_long]):
enter_signal = SignalDirection.SHORT enter_signal = SignalDirection.SHORT
enter_tag_value = latest.get(SignalTagType.SHORT_TAG.value, None) enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None)
timeframe_seconds = timeframe_to_seconds(timeframe) timeframe_seconds = timeframe_to_seconds(timeframe)
@ -897,7 +912,7 @@ class IStrategy(ABC, HyperStrategyMixin):
def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
""" """
Populates indicators for given candle (OHLCV) data (for multiple pairs) Populates indicators for given candle (OHLCV) data (for multiple pairs)
Does not run advise_buy or advise_sell! Does not run advise_entry or advise_exit!
Used by optimize operations only, not during dry / live runs. Used by optimize operations only, not during dry / live runs.
Using .copy() to get a fresh copy of the dataframe for every strategy run. Using .copy() to get a fresh copy of the dataframe for every strategy run.
Also copy on output to avoid PerformanceWarnings pandas 1.3.0 started to show. Also copy on output to avoid PerformanceWarnings pandas 1.3.0 started to show.
@ -929,7 +944,7 @@ class IStrategy(ABC, HyperStrategyMixin):
else: else:
return self.populate_indicators(dataframe, metadata) return self.populate_indicators(dataframe, metadata)
def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def advise_entry(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the entry order signal for the given dataframe Based on TA indicators, populates the entry order signal for the given dataframe
This method should not be overridden. This method should not be overridden.
@ -944,15 +959,15 @@ class IStrategy(ABC, HyperStrategyMixin):
if self._buy_fun_len == 2: if self._buy_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see " warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning) "the current function headers!", DeprecationWarning)
return 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_buy_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': 'long_tag'}, axis='columns') df = df.rename({'buy': 'enter_long', 'buy_tag': 'enter_tag'}, axis='columns')
return df return df
def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def advise_exit(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the exit order signal for the given dataframe Based on TA indicators, populates the exit order signal for the given dataframe
This method should not be overridden. This method should not be overridden.
@ -966,26 +981,9 @@ class IStrategy(ABC, HyperStrategyMixin):
if self._sell_fun_len == 2: if self._sell_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see " warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning) "the current function headers!", DeprecationWarning)
return 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_sell_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
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, side: str,
**kwargs) -> float:
"""
Customize leverage for each new trade. This method is not called when edge module is
enabled.
:param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
:param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:return: A leverage amount, which is between 1.0 and max_leverage.
"""
return 1.0

View File

@ -15,8 +15,9 @@ import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib import freqtrade.vendor.qtpylib.indicators as qtpylib
# TODO-lev: Create a meaningfull short strategy (not just revresed signs).
# This class is a sample. Feel free to customize it. # This class is a sample. Feel free to customize it.
class SampleStrategy(IStrategy): class SampleShortStrategy(IStrategy):
""" """
This is a sample strategy to inspire you. This is a sample strategy to inspire you.
More information in https://www.freqtrade.io/en/latest/strategy-customization/ More information in https://www.freqtrade.io/en/latest/strategy-customization/

View File

@ -12,12 +12,11 @@ def bot_loop_start(self, **kwargs) -> None:
""" """
pass pass
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,
**kwargs) -> float: side: str, **kwargs) -> float:
""" """
Customize stake size for each new trade. This method is not called when edge module is Customize stake size for each new trade.
enabled.
:param pair: Pair that's currently analyzed :param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
@ -25,6 +24,7 @@ def custom_stake_amount(self, pair: str, current_time: 'datetime', current_rate:
:param proposed_stake: A stake amount proposed by the bot. :param proposed_stake: A stake amount proposed by the bot.
:param min_stake: Minimal stake size allowed by exchange. :param min_stake: Minimal stake size allowed by exchange.
:param max_stake: Balance available for trading. :param max_stake: Balance available for trading.
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:return: A stake size, which is between min_stake and max_stake. :return: A stake size, which is between min_stake and max_stake.
""" """
return proposed_stake return proposed_stake
@ -80,9 +80,10 @@ def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', curre
return None return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: 'datetime', **kwargs) -> bool: time_in_force: str, current_time: datetime,
side: str, **kwargs) -> bool:
""" """
Called right before placing a buy order. Called right before placing a entry order.
Timing for this function is critical, so avoid doing heavy computations or Timing for this function is critical, so avoid doing heavy computations or
network requests in this method. network requests in this method.
@ -90,12 +91,13 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
When not implemented by a strategy, returns True (always confirming). When not implemented by a strategy, returns True (always confirming).
:param pair: Pair that's about to be bought. :param pair: Pair that's about to be bought/shorted.
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in target (quote) currency that's going to be traded. :param amount: Amount in target (quote) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the buy-order is placed on the exchange. :return bool: When True is returned, then the buy-order is placed on the exchange.
False aborts the process False aborts the process

View File

@ -19,8 +19,8 @@ from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_in
from freqtrade.configuration import setup_utils_configuration from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from tests.conftest import (create_mock_trades, get_args, log_has, log_has_re, patch_exchange, from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_args, log_has,
patched_configuration_load_config_file) log_has_re, patch_exchange, patched_configuration_load_config_file)
from tests.conftest_trades import MOCK_TRADE_COUNT from tests.conftest_trades import MOCK_TRADE_COUNT
@ -774,7 +774,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
captured = capsys.readouterr() captured = capsys.readouterr()
assert "TestStrategyLegacyV1" in captured.out assert "TestStrategyLegacyV1" in captured.out
assert "legacy_strategy_v1.py" not in captured.out assert "legacy_strategy_v1.py" not in captured.out
assert "StrategyTestV2" in captured.out assert CURRENT_TEST_STRATEGY in captured.out
# Test regular output # Test regular output
args = [ args = [
@ -789,7 +789,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
captured = capsys.readouterr() captured = capsys.readouterr()
assert "TestStrategyLegacyV1" in captured.out assert "TestStrategyLegacyV1" in captured.out
assert "legacy_strategy_v1.py" in captured.out assert "legacy_strategy_v1.py" in captured.out
assert "StrategyTestV2" in captured.out assert CURRENT_TEST_STRATEGY in captured.out
# Test color output # Test color output
args = [ args = [
@ -803,7 +803,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
captured = capsys.readouterr() captured = capsys.readouterr()
assert "TestStrategyLegacyV1" in captured.out assert "TestStrategyLegacyV1" in captured.out
assert "legacy_strategy_v1.py" in captured.out assert "legacy_strategy_v1.py" in captured.out
assert "StrategyTestV2" in captured.out assert CURRENT_TEST_STRATEGY in captured.out
assert "LOAD FAILED" in captured.out assert "LOAD FAILED" in captured.out

View File

@ -43,6 +43,9 @@ logging.getLogger('').setLevel(logging.INFO)
# Do not mask numpy errors as warnings that no one read, raise the exсeption # Do not mask numpy errors as warnings that no one read, raise the exсeption
np.seterr(all='raise') np.seterr(all='raise')
CURRENT_TEST_STRATEGY = 'StrategyTestV3'
TRADE_SIDES = ('long', 'short')
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addoption('--longrun', action='store_true', dest="longrun", parser.addoption('--longrun', action='store_true', dest="longrun",
@ -414,7 +417,7 @@ def get_default_conf(testdatadir):
"user_data_dir": Path("user_data"), "user_data_dir": Path("user_data"),
"verbosity": 3, "verbosity": 3,
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"), "strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
"strategy": "StrategyTestV2", "strategy": CURRENT_TEST_STRATEGY,
"disableparamexport": True, "disableparamexport": True,
"internals": {}, "internals": {},
"export": "none", "export": "none",

View File

@ -45,7 +45,7 @@ def mock_trade_1(fee, is_short: bool):
open_rate=0.123, open_rate=0.123,
exchange='binance', exchange='binance',
open_order_id=f'dry_run_buy_{direc(is_short)}_12345', open_order_id=f'dry_run_buy_{direc(is_short)}_12345',
strategy='StrategyTestV2', strategy='StrategyTestV3',
timeframe=5, timeframe=5,
is_short=is_short is_short=is_short
) )
@ -100,7 +100,7 @@ def mock_trade_2(fee, is_short: bool):
exchange='binance', exchange='binance',
is_open=False, is_open=False,
open_order_id=f'dry_run_sell_{direc(is_short)}_12345', open_order_id=f'dry_run_sell_{direc(is_short)}_12345',
strategy='StrategyTestV2', strategy='StrategyTestV3',
timeframe=5, timeframe=5,
sell_reason='sell_signal', sell_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
@ -160,7 +160,7 @@ def mock_trade_3(fee, is_short: bool):
close_profit_abs=-0.001155 if is_short else 0.000155, close_profit_abs=-0.001155 if is_short else 0.000155,
exchange='binance', exchange='binance',
is_open=False, is_open=False,
strategy='StrategyTestV2', strategy='StrategyTestV3',
timeframe=5, timeframe=5,
sell_reason='roi', sell_reason='roi',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
@ -204,7 +204,7 @@ def mock_trade_4(fee, is_short: bool):
open_rate=0.123, open_rate=0.123,
exchange='binance', exchange='binance',
open_order_id=f'prod_buy_{direc(is_short)}_12345', open_order_id=f'prod_buy_{direc(is_short)}_12345',
strategy='StrategyTestV2', strategy='StrategyTestV3',
timeframe=5, timeframe=5,
is_short=is_short is_short=is_short
) )

View File

@ -16,7 +16,7 @@ from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_
get_latest_hyperopt_file, load_backtest_data, load_trades, get_latest_hyperopt_file, load_backtest_data, load_trades,
load_trades_from_db) load_trades_from_db)
from freqtrade.data.history import load_data, load_pair_history from freqtrade.data.history import load_data, load_pair_history
from tests.conftest import create_mock_trades from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades
from tests.conftest_trades import MOCK_TRADE_COUNT from tests.conftest_trades import MOCK_TRADE_COUNT
@ -129,7 +129,7 @@ def test_load_trades_from_db(default_conf, fee, is_short, mocker):
for col in BT_DATA_COLUMNS: for col in BT_DATA_COLUMNS:
if col not in ['index', 'open_at_end']: if col not in ['index', 'open_at_end']:
assert col in trades.columns assert col in trades.columns
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='StrategyTestV2') trades = load_trades_from_db(db_url=default_conf['db_url'], strategy=CURRENT_TEST_STRATEGY)
assert len(trades) == 4 assert len(trades) == 4
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy') trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy')
assert len(trades) == 0 assert len(trades) == 0
@ -187,7 +187,7 @@ def test_load_trades(default_conf, mocker):
db_url=default_conf.get('db_url'), db_url=default_conf.get('db_url'),
exportfilename=default_conf.get('exportfilename'), exportfilename=default_conf.get('exportfilename'),
no_trades=False, no_trades=False,
strategy="StrategyTestV2", strategy=CURRENT_TEST_STRATEGY,
) )
assert db_mock.call_count == 1 assert db_mock.call_count == 1

View File

@ -26,7 +26,8 @@ from freqtrade.data.history.jsondatahandler import JsonDataHandler, JsonGzDataHa
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import file_dump_json from freqtrade.misc import file_dump_json
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from tests.conftest import get_patched_exchange, log_has, log_has_re, patch_exchange from tests.conftest import (CURRENT_TEST_STRATEGY, get_patched_exchange, log_has, log_has_re,
patch_exchange)
# Change this if modifying UNITTEST/BTC testdatafile # Change this if modifying UNITTEST/BTC testdatafile
@ -380,7 +381,7 @@ def test_file_dump_json_tofile(testdatadir) -> None:
def test_get_timerange(default_conf, mocker, testdatadir) -> None: def test_get_timerange(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker) patch_exchange(mocker)
default_conf.update({'strategy': 'StrategyTestV2'}) default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
data = strategy.advise_all_indicators( data = strategy.advise_all_indicators(
@ -398,7 +399,7 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None:
def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None: def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None:
patch_exchange(mocker) patch_exchange(mocker)
default_conf.update({'strategy': 'StrategyTestV2'}) default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
data = strategy.advise_all_indicators( data = strategy.advise_all_indicators(
@ -422,7 +423,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir)
def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None: def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None:
patch_exchange(mocker) patch_exchange(mocker)
default_conf.update({'strategy': 'StrategyTestV2'}) default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
timerange = TimeRange('index', 'index', 200, 250) timerange = TimeRange('index', 'index', 200, 250)

View File

@ -135,8 +135,6 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog):
assert ex._ccxt_config == {} assert ex._ccxt_config == {}
Exchange._headers = {} Exchange._headers = {}
# TODO-lev: Test with options in ccxt_config
def test_destroy(default_conf, mocker, caplog): def test_destroy(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)

View File

@ -1,10 +1,9 @@
from random import randint from random import randint
from unittest.mock import MagicMock, PropertyMock from unittest.mock import MagicMock
import ccxt import ccxt
import pytest import pytest
from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException, InvalidOrderException from freqtrade.exceptions import DependencyException, InvalidOrderException
from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT
from tests.conftest import get_patched_exchange from tests.conftest import get_patched_exchange

View File

@ -18,7 +18,7 @@ class BTrade(NamedTuple):
sell_reason: SellType sell_reason: SellType
open_tick: int open_tick: int
close_tick: int close_tick: int
buy_tag: Optional[str] = None enter_tag: Optional[str] = None
class BTContainer(NamedTuple): class BTContainer(NamedTuple):
@ -49,15 +49,13 @@ def _build_backtest_dataframe(data):
if len(data[0]) == 8: if len(data[0]) == 8:
# No short columns # No short columns
data = [d + [0, 0] for d in data] data = [d + [0, 0] for d in data]
columns = columns + ['long_tag'] if len(data[0]) == 11 else columns columns = columns + ['enter_tag'] if len(data[0]) == 11 else columns
frame = DataFrame.from_records(data, columns=columns) frame = DataFrame.from_records(data, columns=columns)
frame['date'] = frame['date'].apply(_get_frame_time_from_offset) frame['date'] = frame['date'].apply(_get_frame_time_from_offset)
# Ensure floats are in place # Ensure floats are in place
for column in ['open', 'high', 'low', 'close', 'volume']: for column in ['open', 'high', 'low', 'close', 'volume']:
frame[column] = frame[column].astype('float64') frame[column] = frame[column].astype('float64')
if 'long_tag' not in columns: if 'enter_tag' not in columns:
frame['long_tag'] = None frame['enter_tag'] = None
if 'short_tag' not in columns:
frame['short_tag'] = None
return frame return frame

View File

@ -532,7 +532,7 @@ tc33 = BTContainer(data=[
sell_reason=SellType.TRAILING_STOP_LOSS, sell_reason=SellType.TRAILING_STOP_LOSS,
open_tick=1, open_tick=1,
close_tick=1, close_tick=1,
buy_tag='buy_signal_01' enter_tag='buy_signal_01'
)] )]
) )
@ -598,8 +598,8 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
backtesting.required_startup = 0 backtesting.required_startup = 0
backtesting.strategy.advise_buy = lambda a, m: frame backtesting.strategy.advise_entry = lambda a, m: frame
backtesting.strategy.advise_sell = lambda a, m: frame backtesting.strategy.advise_exit = lambda a, m: frame
backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
@ -621,6 +621,6 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
for c, trade in enumerate(data.trades): for c, trade in enumerate(data.trades):
res = results.iloc[c] res = results.iloc[c]
assert res.sell_reason == trade.sell_reason.value assert res.sell_reason == trade.sell_reason.value
assert res.buy_tag == trade.buy_tag assert res.buy_tag == trade.enter_tag
assert res.open_date == _get_frame_time_from_offset(trade.open_tick) assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick) assert res.close_date == _get_frame_time_from_offset(trade.close_tick)

View File

@ -22,7 +22,7 @@ from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.optimize.backtesting import Backtesting from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence import LocalTrade from freqtrade.persistence import LocalTrade
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file) patched_configuration_load_config_file)
@ -159,7 +159,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
args = [ args = [
'backtesting', 'backtesting',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
'--export', 'none' '--export', 'none'
] ]
@ -194,7 +194,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
args = [ args = [
'backtesting', 'backtesting',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar', '--datadir', '/foo/bar',
'--timeframe', '1m', '--timeframe', '1m',
'--enable-position-stacking', '--enable-position-stacking',
@ -244,7 +244,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
args = [ args = [
'backtesting', 'backtesting',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1', '--stake-amount', '1',
'--starting-balance', '2' '--starting-balance', '2'
] ]
@ -255,7 +255,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
args = [ args = [
'backtesting', 'backtesting',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1', '--stake-amount', '1',
'--starting-balance', '0.5' '--starting-balance', '0.5'
] ]
@ -273,7 +273,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
args = [ args = [
'backtesting', 'backtesting',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
] ]
pargs = get_args(args) pargs = get_args(args)
start_backtesting(pargs) start_backtesting(pargs)
@ -295,8 +295,8 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
assert backtesting.config == default_conf assert backtesting.config == default_conf
assert backtesting.timeframe == '5m' assert backtesting.timeframe == '5m'
assert callable(backtesting.strategy.advise_all_indicators) assert callable(backtesting.strategy.advise_all_indicators)
assert callable(backtesting.strategy.advise_buy) assert callable(backtesting.strategy.advise_entry)
assert callable(backtesting.strategy.advise_sell) assert callable(backtesting.strategy.advise_exit)
assert isinstance(backtesting.strategy.dp, DataProvider) assert isinstance(backtesting.strategy.dp, DataProvider)
get_fee.assert_called() get_fee.assert_called()
assert backtesting.fee == 0.5 assert backtesting.fee == 0.5
@ -306,7 +306,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
del default_conf['timeframe'] del default_conf['timeframe']
default_conf['strategy_list'] = ['StrategyTestV2', default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY,
'SampleStrategy'] 'SampleStrategy']
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
@ -344,7 +344,6 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
assert len(processed['UNITTEST/BTC']) == 102 assert len(processed['UNITTEST/BTC']) == 102
# Load strategy to compare the result between Backtesting function and strategy are the same # Load strategy to compare the result between Backtesting function and strategy are the same
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
processed2 = strategy.advise_all_indicators(data) processed2 = strategy.advise_all_indicators(data)
@ -486,7 +485,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
Backtesting(default_conf) Backtesting(default_conf)
# Multiple strategies # Multiple strategies
default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1'] default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'TestStrategyLegacyV1']
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match='PrecisionFilter not allowed for backtesting multiple strategies.'): match='PrecisionFilter not allowed for backtesting multiple strategies.'):
Backtesting(default_conf) Backtesting(default_conf)
@ -803,7 +802,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
# Override the default buy trend function in our StrategyTestV2 # Override the default buy trend function in our StrategyTest
def fun(dataframe=None, pair=None): def fun(dataframe=None, pair=None):
buy_value = 1 buy_value = 1
sell_value = 1 sell_value = 1
@ -812,14 +811,14 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override backtesting.strategy.advise_entry = fun # Override
backtesting.strategy.advise_sell = fun # Override backtesting.strategy.advise_exit = fun # Override
result = backtesting.backtest(**backtest_conf) result = backtesting.backtest(**backtest_conf)
assert result['results'].empty assert result['results'].empty
def test_backtest_only_sell(mocker, default_conf, testdatadir): def test_backtest_only_sell(mocker, default_conf, testdatadir):
# Override the default buy trend function in our StrategyTestV2 # Override the default buy trend function in our StrategyTest
def fun(dataframe=None, pair=None): def fun(dataframe=None, pair=None):
buy_value = 0 buy_value = 0
sell_value = 1 sell_value = 1
@ -828,8 +827,8 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir):
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override backtesting.strategy.advise_entry = fun # Override
backtesting.strategy.advise_sell = fun # Override backtesting.strategy.advise_exit = fun # Override
result = backtesting.backtest(**backtest_conf) result = backtesting.backtest(**backtest_conf)
assert result['results'].empty assert result['results'].empty
@ -843,8 +842,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.required_startup = 0 backtesting.required_startup = 0
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate # Override backtesting.strategy.advise_entry = _trend_alternate # Override
backtesting.strategy.advise_sell = _trend_alternate # Override backtesting.strategy.advise_exit = _trend_alternate # Override
result = backtesting.backtest(**backtest_conf) result = backtesting.backtest(**backtest_conf)
# 200 candles in backtest data # 200 candles in backtest data
# won't buy on first (shifted by 1) # won't buy on first (shifted by 1)
@ -897,8 +896,8 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0]) backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate_hold # Override backtesting.strategy.advise_entry = _trend_alternate_hold # Override
backtesting.strategy.advise_sell = _trend_alternate_hold # Override backtesting.strategy.advise_exit = _trend_alternate_hold # Override
processed = backtesting.strategy.advise_all_indicators(data) processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed) min_date, max_date = get_timerange(processed)
@ -948,7 +947,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
args = [ args = [
'backtesting', 'backtesting',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
'--datadir', str(testdatadir), '--datadir', str(testdatadir),
'--timeframe', '1m', '--timeframe', '1m',
'--timerange', '1510694220-1510700340', '--timerange', '1510694220-1510700340',
@ -1019,7 +1018,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'--enable-position-stacking', '--enable-position-stacking',
'--disable-max-market-positions', '--disable-max-market-positions',
'--strategy-list', '--strategy-list',
'StrategyTestV2', CURRENT_TEST_STRATEGY,
'TestStrategyLegacyV1', 'TestStrategyLegacyV1',
] ]
args = get_args(args) args = get_args(args)
@ -1042,7 +1041,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'Backtesting with data from 2017-11-14 21:17:00 ' 'Backtesting with data from 2017-11-14 21:17:00 '
'up to 2017-11-14 22:58:00 (0 days).', 'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...', 'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy StrategyTestV2', f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
'Running backtesting for Strategy TestStrategyLegacyV1', 'Running backtesting for Strategy TestStrategyLegacyV1',
] ]
@ -1123,7 +1122,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'--enable-position-stacking', '--enable-position-stacking',
'--disable-max-market-positions', '--disable-max-market-positions',
'--strategy-list', '--strategy-list',
'StrategyTestV2', CURRENT_TEST_STRATEGY,
'TestStrategyLegacyV1', 'TestStrategyLegacyV1',
] ]
args = get_args(args) args = get_args(args)
@ -1140,7 +1139,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'Backtesting with data from 2017-11-14 21:17:00 ' 'Backtesting with data from 2017-11-14 21:17:00 '
'up to 2017-11-14 22:58:00 (0 days).', 'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...', 'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy StrategyTestV2', f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
'Running backtesting for Strategy TestStrategyLegacyV1', 'Running backtesting for Strategy TestStrategyLegacyV1',
] ]
@ -1228,7 +1227,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'--timeframe', '5m', '--timeframe', '5m',
'--timeframe-detail', '1m', '--timeframe-detail', '1m',
'--strategy-list', '--strategy-list',
'StrategyTestV2' CURRENT_TEST_STRATEGY
] ]
args = get_args(args) args = get_args(args)
start_backtesting(args) start_backtesting(args)
@ -1242,7 +1241,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'up to 2019-10-13 11:10:00 (2 days).', 'up to 2019-10-13 11:10:00 (2 days).',
'Backtesting with data from 2019-10-11 01:40:00 ' 'Backtesting with data from 2019-10-11 01:40:00 '
'up to 2019-10-13 11:10:00 (2 days).', 'up to 2019-10-13 11:10:00 (2 days).',
'Running backtesting for Strategy StrategyTestV2', f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
] ]
for line in exists: for line in exists:

View File

@ -6,7 +6,7 @@ from unittest.mock import MagicMock
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.optimize.edge_cli import EdgeCli from freqtrade.optimize.edge_cli import EdgeCli
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file) patched_configuration_load_config_file)
@ -16,7 +16,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
args = [ args = [
'edge', 'edge',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
] ]
config = setup_optimize_configuration(get_args(args), RunMode.EDGE) config = setup_optimize_configuration(get_args(args), RunMode.EDGE)
@ -46,7 +46,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N
args = [ args = [
'edge', 'edge',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar', '--datadir', '/foo/bar',
'--timeframe', '1m', '--timeframe', '1m',
'--timerange', ':100', '--timerange', ':100',
@ -80,7 +80,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
args = [ args = [
'edge', 'edge',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
] ]
pargs = get_args(args) pargs = get_args(args)
start_edge(pargs) start_edge(pargs)

View File

@ -18,7 +18,7 @@ from freqtrade.optimize.hyperopt_tools import HyperoptTools
from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.optimize.optimize_reports import generate_strategy_stats
from freqtrade.optimize.space import SKDecimal from freqtrade.optimize.space import SKDecimal
from freqtrade.strategy.hyper import IntParameter from freqtrade.strategy.hyper import IntParameter
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file) patched_configuration_load_config_file)
@ -125,7 +125,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
args = [ args = [
'hyperopt', 'hyperopt',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1', '--stake-amount', '1',
'--starting-balance', '0.5' '--starting-balance', '0.5'
] ]
@ -318,8 +318,8 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
# Should be called for historical candle data # Should be called for historical candle data
assert dumper.call_count == 1 assert dumper.call_count == 1
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking") assert hasattr(hyperopt, "position_stacking")
@ -698,8 +698,8 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
assert dumper.call_count == 1 assert dumper.call_count == 1
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking") assert hasattr(hyperopt, "position_stacking")
@ -772,8 +772,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
assert dumper.called assert dumper.called
assert dumper.call_count == 1 assert dumper.call_count == 1
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking") assert hasattr(hyperopt, "position_stacking")
@ -821,8 +821,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
assert dumper.called assert dumper.called
assert dumper.call_count == 1 assert dumper.call_count == 1
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades") assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking") assert hasattr(hyperopt, "position_stacking")

View File

@ -10,7 +10,7 @@ import rapidjson
from freqtrade.constants import FTHYPT_FILEVERSION from freqtrade.constants import FTHYPT_FILEVERSION
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer
from tests.conftest import log_has from tests.conftest import CURRENT_TEST_STRATEGY, log_has
# Functions for recurrent object patching # Functions for recurrent object patching
@ -167,9 +167,9 @@ def test__pprint_dict():
def test_get_strategy_filename(default_conf): def test_get_strategy_filename(default_conf):
x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV2') x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV3')
assert isinstance(x, Path) assert isinstance(x, Path)
assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v2.py' assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v3.py'
x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy') x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy')
assert x is None assert x is None
@ -177,7 +177,7 @@ def test_get_strategy_filename(default_conf):
def test_export_params(tmpdir): def test_export_params(tmpdir):
filename = Path(tmpdir) / "StrategyTestV2.json" filename = Path(tmpdir) / f"{CURRENT_TEST_STRATEGY}.json"
assert not filename.is_file() assert not filename.is_file()
params = { params = {
"params_details": { "params_details": {
@ -205,12 +205,12 @@ def test_export_params(tmpdir):
} }
} }
HyperoptTools.export_params(params, "StrategyTestV2", filename) HyperoptTools.export_params(params, CURRENT_TEST_STRATEGY, filename)
assert filename.is_file() assert filename.is_file()
content = rapidjson.load(filename.open('r')) content = rapidjson.load(filename.open('r'))
assert content['strategy_name'] == 'StrategyTestV2' assert content['strategy_name'] == CURRENT_TEST_STRATEGY
assert 'params' in content assert 'params' in content
assert "buy" in content["params"] assert "buy" in content["params"]
assert "sell" in content["params"] assert "sell" in content["params"]
@ -223,7 +223,7 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
default_conf['disableparamexport'] = False default_conf['disableparamexport'] = False
export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params") export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params")
filename = Path(tmpdir) / "StrategyTestV2.json" filename = Path(tmpdir) / f"{CURRENT_TEST_STRATEGY}.json"
assert not filename.is_file() assert not filename.is_file()
params = { params = {
"params_details": { "params_details": {
@ -252,17 +252,17 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
FTHYPT_FILEVERSION: 2, FTHYPT_FILEVERSION: 2,
} }
HyperoptTools.try_export_params(default_conf, "StrategyTestV222", params) HyperoptTools.try_export_params(default_conf, "StrategyTestVXXX", params)
assert log_has("Strategy not found, not exporting parameter file.", caplog) assert log_has("Strategy not found, not exporting parameter file.", caplog)
assert export_mock.call_count == 0 assert export_mock.call_count == 0
caplog.clear() caplog.clear()
HyperoptTools.try_export_params(default_conf, "StrategyTestV2", params) HyperoptTools.try_export_params(default_conf, CURRENT_TEST_STRATEGY, params)
assert export_mock.call_count == 1 assert export_mock.call_count == 1
assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2' assert export_mock.call_args_list[0][0][1] == CURRENT_TEST_STRATEGY
assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json' assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v3.json'
def test_params_print(capsys): def test_params_print(capsys):

View File

@ -21,6 +21,7 @@ from freqtrade.optimize.optimize_reports import (generate_backtest_stats, genera
text_table_bt_results, text_table_sell_reason, text_table_bt_results, text_table_sell_reason,
text_table_strategy) text_table_strategy)
from freqtrade.resolvers.strategy_resolver import StrategyResolver from freqtrade.resolvers.strategy_resolver import StrategyResolver
from tests.conftest import CURRENT_TEST_STRATEGY
from tests.data.test_history import _backup_file, _clean_test_file from tests.data.test_history import _backup_file, _clean_test_file
@ -52,7 +53,7 @@ def test_text_table_bt_results():
def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
default_conf.update({'strategy': 'StrategyTestV2'}) default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
StrategyResolver.load_strategy(default_conf) StrategyResolver.load_strategy(default_conf)
results = {'DefStrat': { results = {'DefStrat': {

View File

@ -24,8 +24,8 @@ from freqtrade.rpc import RPC
from freqtrade.rpc.api_server import ApiServer from freqtrade.rpc.api_server import ApiServer
from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token
from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer
from tests.conftest import (create_mock_trades, get_mock_coro, get_patched_freqtradebot, log_has, from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_mock_coro,
log_has_re, patch_get_signal) get_patched_freqtradebot, log_has, log_has_re, patch_get_signal)
BASE_URI = "/api/v1" BASE_URI = "/api/v1"
@ -895,7 +895,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'open_trade_value': 15.1668225, 'open_trade_value': 15.1668225,
'sell_reason': None, 'sell_reason': None,
'sell_order_status': None, 'sell_order_status': None,
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None, 'buy_tag': None,
'timeframe': 5, 'timeframe': 5,
'exchange': 'binance', 'exchange': 'binance',
@ -1000,7 +1000,7 @@ def test_api_forcebuy(botclient, mocker, fee):
close_rate=0.265441, close_rate=0.265441,
id=22, id=22,
timeframe=5, timeframe=5,
strategy="StrategyTestV2" strategy=CURRENT_TEST_STRATEGY
)) ))
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock) mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
@ -1050,7 +1050,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'open_trade_value': 0.24605460, 'open_trade_value': 0.24605460,
'sell_reason': None, 'sell_reason': None,
'sell_order_status': None, 'sell_order_status': None,
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None, 'buy_tag': None,
'timeframe': 5, 'timeframe': 5,
'exchange': 'binance', 'exchange': 'binance',
@ -1117,7 +1117,7 @@ def test_api_pair_candles(botclient, ohlcv_history):
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc) assert_response(rc)
assert 'strategy' in rc.json() assert 'strategy' in rc.json()
assert rc.json()['strategy'] == 'StrategyTestV2' assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert 'columns' in rc.json() assert 'columns' in rc.json()
assert 'data_start_ts' in rc.json() assert 'data_start_ts' in rc.json()
assert 'data_start' in rc.json() assert 'data_start' in rc.json()
@ -1155,19 +1155,19 @@ def test_api_pair_history(botclient, ohlcv_history):
# No pair # No pair
rc = client_get(client, rc = client_get(client,
f"{BASE_URI}/pair_history?timeframe={timeframe}" f"{BASE_URI}/pair_history?timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=StrategyTestV2") f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422) assert_response(rc, 422)
# No Timeframe # No Timeframe
rc = client_get(client, rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC" f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
"&timerange=20180111-20180112&strategy=StrategyTestV2") f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422) assert_response(rc, 422)
# No timerange # No timerange
rc = client_get(client, rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&strategy=StrategyTestV2") f"&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422) assert_response(rc, 422)
# No strategy # No strategy
@ -1179,14 +1179,14 @@ def test_api_pair_history(botclient, ohlcv_history):
# Working # Working
rc = client_get(client, rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=StrategyTestV2") f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 200) assert_response(rc, 200)
assert rc.json()['length'] == 289 assert rc.json()['length'] == 289
assert len(rc.json()['data']) == rc.json()['length'] assert len(rc.json()['data']) == rc.json()['length']
assert 'columns' in rc.json() assert 'columns' in rc.json()
assert 'data' in rc.json() assert 'data' in rc.json()
assert rc.json()['pair'] == 'UNITTEST/BTC' assert rc.json()['pair'] == 'UNITTEST/BTC'
assert rc.json()['strategy'] == 'StrategyTestV2' assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00' assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
assert rc.json()['data_start_ts'] == 1515628800000 assert rc.json()['data_start_ts'] == 1515628800000
assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00' assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00'
@ -1195,7 +1195,7 @@ def test_api_pair_history(botclient, ohlcv_history):
# No data found # No data found
rc = client_get(client, rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20200111-20200112&strategy=StrategyTestV2") f"&timerange=20200111-20200112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 502) assert_response(rc, 502)
assert rc.json()['error'] == ("Error querying /api/v1/pair_history: " assert rc.json()['error'] == ("Error querying /api/v1/pair_history: "
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.") "No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
@ -1236,19 +1236,20 @@ def test_api_strategies(botclient):
'HyperoptableStrategy', 'HyperoptableStrategy',
'InformativeDecoratorTest', 'InformativeDecoratorTest',
'StrategyTestV2', 'StrategyTestV2',
'TestStrategyLegacyV1' 'StrategyTestV3',
'TestStrategyLegacyV1',
]} ]}
def test_api_strategy(botclient): def test_api_strategy(botclient):
ftbot, client = botclient ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/strategy/StrategyTestV2") rc = client_get(client, f"{BASE_URI}/strategy/{CURRENT_TEST_STRATEGY}")
assert_response(rc) assert_response(rc)
assert rc.json()['strategy'] == 'StrategyTestV2' assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v2.py").read_text() data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v3.py").read_text()
assert rc.json()['code'] == data assert rc.json()['code'] == data
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat") rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
@ -1305,7 +1306,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog):
# start backtesting # start backtesting
data = { data = {
"strategy": "StrategyTestV2", "strategy": CURRENT_TEST_STRATEGY,
"timeframe": "5m", "timeframe": "5m",
"timerange": "20180110-20180111", "timerange": "20180110-20180111",
"max_open_trades": 3, "max_open_trades": 3,

View File

@ -25,8 +25,8 @@ from freqtrade.loggers import setup_logging
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
from freqtrade.rpc import RPC from freqtrade.rpc import RPC
from freqtrade.rpc.telegram import Telegram, authorized_only from freqtrade.rpc.telegram import Telegram, authorized_only
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, log_has_re, from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_patched_freqtradebot,
patch_exchange, patch_get_signal, patch_whitelist) log_has, log_has_re, patch_exchange, patch_get_signal, patch_whitelist)
class DummyCls(Telegram): class DummyCls(Telegram):
@ -1242,7 +1242,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0] assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0] assert f'*Strategy:* `{CURRENT_TEST_STRATEGY}`' in msg_mock.call_args_list[0][0][0]
assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock() msg_mock.reset_mock()
@ -1251,7 +1251,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0] assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0] assert f'*Strategy:* `{CURRENT_TEST_STRATEGY}`' in msg_mock.call_args_list[0][0][0]
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]

View File

@ -2,8 +2,7 @@
from pandas import DataFrame from pandas import DataFrame
from freqtrade.strategy import informative, merge_informative_pair from freqtrade.strategy import IStrategy, informative, merge_informative_pair
from freqtrade.strategy.interface import IStrategy
class InformativeDecoratorTest(IStrategy): class InformativeDecoratorTest(IStrategy):

View File

@ -4,7 +4,7 @@
import talib.abstract as ta import talib.abstract as ta
from pandas import DataFrame from pandas import DataFrame
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy import IStrategy
# -------------------------------- # --------------------------------

View File

@ -4,7 +4,7 @@ import talib.abstract as ta
from pandas import DataFrame from pandas import DataFrame
import freqtrade.vendor.qtpylib.indicators as qtpylib import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy import IStrategy
class StrategyTestV2(IStrategy): class StrategyTestV2(IStrategy):

View 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

View File

@ -4,20 +4,20 @@ from pandas import DataFrame
from freqtrade.persistence.models import Trade 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(): def test_strategy_test_v2_structure():
assert hasattr(StrategyTestV2, 'minimal_roi') assert hasattr(StrategyTestV3, 'minimal_roi')
assert hasattr(StrategyTestV2, 'stoploss') assert hasattr(StrategyTestV3, 'stoploss')
assert hasattr(StrategyTestV2, 'timeframe') assert hasattr(StrategyTestV3, 'timeframe')
assert hasattr(StrategyTestV2, 'populate_indicators') assert hasattr(StrategyTestV3, 'populate_indicators')
assert hasattr(StrategyTestV2, 'populate_buy_trend') assert hasattr(StrategyTestV3, 'populate_buy_trend')
assert hasattr(StrategyTestV2, 'populate_sell_trend') assert hasattr(StrategyTestV3, 'populate_sell_trend')
def test_strategy_test_v2(result, fee): def test_strategy_test_v2(result, fee):
strategy = StrategyTestV2({}) strategy = StrategyTestV3({})
metadata = {'pair': 'ETH/BTC'} metadata = {'pair': 'ETH/BTC'}
assert type(strategy.minimal_roi) is dict 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, assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc', 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, 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', rate=20000, time_in_force='gtc', sell_reason='roi',
current_time=datetime.utcnow()) is True current_time=datetime.utcnow()) is True

View File

@ -21,13 +21,13 @@ from freqtrade.strategy.hyper import (BaseParameter, BooleanParameter, Categoric
DecimalParameter, IntParameter, RealParameter) DecimalParameter, IntParameter, RealParameter)
from freqtrade.strategy.interface import SellCheckTuple from freqtrade.strategy.interface import SellCheckTuple
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper 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 # Avoid to reinit the same object again and again
_STRATEGY = StrategyTestV2(config={}) _STRATEGY = StrategyTestV3(config={})
_STRATEGY.dp = DataProvider({}, None, None) _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) assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False)
mocked_history.loc[1, 'exit_long'] = 0 mocked_history.loc[1, 'exit_long'] = 0
mocked_history.loc[1, 'enter_long'] = 1 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( assert _STRATEGY.get_entry_signal(
'ETH/BTC', '5m', mocked_history) == (SignalDirection.LONG, 'buy_signal_01') '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_long'] = 0
mocked_history.loc[1, 'enter_short'] = 1 mocked_history.loc[1, 'enter_short'] = 1
mocked_history.loc[1, 'exit_short'] = 0 mocked_history.loc[1, 'exit_short'] = 0
mocked_history.loc[1, 'enter_tag'] = 'sell_signal_01'
assert _STRATEGY.get_entry_signal( 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) == (False, False)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (True, 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): def test_ignore_expired_candle(default_conf):
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
strategy.ignore_buying_expired_candle_after = 60 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): def test_assert_df(ohlcv_history, caplog):
df_len = len(ohlcv_history) - 1 df_len = len(ohlcv_history) - 1
ohlcv_history.loc[:, 'buy'] = 0 ohlcv_history.loc[:, 'enter_long'] = 0
ohlcv_history.loc[:, 'sell'] = 0 ohlcv_history.loc[:, 'exit_long'] = 0
# Ensure it's running when passed correctly # Ensure it's running when passed correctly
_STRATEGY.assert_df(ohlcv_history, len(ohlcv_history), _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history),
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[df_len, 'date']) 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), _STRATEGY.assert_df(None, len(ohlcv_history),
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date']) ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date'])
with pytest.raises(StrategyError, with pytest.raises(StrategyError,
match="Buy column not set"): match="enter_long/buy column not set."):
_STRATEGY.assert_df(ohlcv_history.drop('buy', axis=1), len(ohlcv_history), _STRATEGY.assert_df(ohlcv_history.drop('enter_long', axis=1), len(ohlcv_history),
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date']) ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date'])
_STRATEGY.disable_dataframe_checks = True _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: def test_advise_all_indicators(default_conf, testdatadir) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
timerange = TimeRange.parse_timerange('1510694220-1510700340') 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: def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators') aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators')
timerange = TimeRange.parse_timerange('1510694220-1510700340') 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}, min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
{0: 0.1, 20: 0.05, 55: 0.01}] {0: 0.1, 20: 0.05, 55: 0.01}]
for roi in min_roi_list: for roi in min_roi_list:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = roi strategy.minimal_roi = roi
trade = Trade( trade = Trade(
@ -330,7 +328,6 @@ def test_min_roi_reached2(default_conf, fee) -> None:
}, },
] ]
for roi in min_roi_list: for roi in min_roi_list:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = roi strategy.minimal_roi = roi
trade = Trade( trade = Trade(
@ -365,7 +362,6 @@ def test_min_roi_reached3(default_conf, fee) -> None:
30: 0.05, 30: 0.05,
55: 0.30, 55: 0.30,
} }
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = min_roi strategy.minimal_roi = min_roi
trade = Trade( 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, def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom,
profit2, adjusted2, expected2, custom_stop) -> None: profit2, adjusted2, expected2, custom_stop) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
trade = Trade( trade = Trade(
pair='ETH/BTC', 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: def test_custom_sell(default_conf, fee, caplog) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
trade = Trade( trade = Trade(
pair='ETH/BTC', 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) 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: def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x) ind_mock = MagicMock(side_effect=lambda x, meta: x)
buy_mock = MagicMock(side_effect=lambda x, meta: x) entry_mock = MagicMock(side_effect=lambda x, meta: x)
sell_mock = MagicMock(side_effect=lambda x, meta: x) exit_mock = MagicMock(side_effect=lambda x, meta: x)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy', 'freqtrade.strategy.interface.IStrategy',
advise_indicators=ind_mock, advise_indicators=ind_mock,
advise_buy=buy_mock, advise_entry=entry_mock,
advise_sell=sell_mock, advise_exit=exit_mock,
) )
strategy = StrategyTestV2({}) strategy = StrategyTestV3({})
strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
assert ind_mock.call_count == 1 assert ind_mock.call_count == 1
assert buy_mock.call_count == 1 assert entry_mock.call_count == 1
assert buy_mock.call_count == 1 assert entry_mock.call_count == 1
assert log_has('TA Analysis Launched', caplog) assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', 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'}) strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
# No analysis happens as process_only_new_candles is true # No analysis happens as process_only_new_candles is true
assert ind_mock.call_count == 2 assert ind_mock.call_count == 2
assert buy_mock.call_count == 2 assert entry_mock.call_count == 2
assert buy_mock.call_count == 2 assert entry_mock.call_count == 2
assert log_has('TA Analysis Launched', caplog) assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', 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: def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> None:
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x) ind_mock = MagicMock(side_effect=lambda x, meta: x)
buy_mock = MagicMock(side_effect=lambda x, meta: x) entry_mock = MagicMock(side_effect=lambda x, meta: x)
sell_mock = MagicMock(side_effect=lambda x, meta: x) exit_mock = MagicMock(side_effect=lambda x, meta: x)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy', 'freqtrade.strategy.interface.IStrategy',
advise_indicators=ind_mock, advise_indicators=ind_mock,
advise_buy=buy_mock, advise_entry=entry_mock,
advise_sell=sell_mock, advise_exit=exit_mock,
) )
strategy = StrategyTestV2({}) strategy = StrategyTestV3({})
strategy.dp = DataProvider({}, None, None) strategy.dp = DataProvider({}, None, None)
strategy.process_only_new_candles = True 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 'close' in ret.columns
assert isinstance(ret, DataFrame) assert isinstance(ret, DataFrame)
assert ind_mock.call_count == 1 assert ind_mock.call_count == 1
assert buy_mock.call_count == 1 assert entry_mock.call_count == 1
assert buy_mock.call_count == 1 assert entry_mock.call_count == 1
assert log_has('TA Analysis Launched', caplog) assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
caplog.clear() 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'}) ret = strategy._analyze_ticker_internal(ohlcv_history, {'pair': 'ETH/BTC'})
# No analysis happens as process_only_new_candles is true # No analysis happens as process_only_new_candles is true
assert ind_mock.call_count == 1 assert ind_mock.call_count == 1
assert buy_mock.call_count == 1 assert entry_mock.call_count == 1
assert buy_mock.call_count == 1 assert entry_mock.call_count == 1
# only skipped analyze adds buy and sell columns, otherwise it's all mocked # only skipped analyze adds buy and sell columns, otherwise it's all mocked
assert 'buy' in ret.columns assert 'enter_long' in ret.columns
assert 'sell' in ret.columns assert 'exit_long' in ret.columns
assert ret['buy'].sum() == 0 assert ret['enter_long'].sum() == 0
assert ret['sell'].sum() == 0 assert ret['exit_long'].sum() == 0
assert not log_has('TA Analysis Launched', caplog) assert not log_has('TA Analysis Launched', caplog)
assert log_has('Skipping TA Analysis for already analyzed candle', caplog) assert log_has('Skipping TA Analysis for already analyzed candle', caplog)
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_is_pair_locked(default_conf): def test_is_pair_locked(default_conf):
default_conf.update({'strategy': 'StrategyTestV2'})
PairLocks.timeframe = default_conf['timeframe'] PairLocks.timeframe = default_conf['timeframe']
PairLocks.use_db = True PairLocks.use_db = True
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)

View File

@ -10,7 +10,7 @@ from pandas import DataFrame
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.interface import IStrategy 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(): def test_search_strategy():
@ -18,7 +18,7 @@ def test_search_strategy():
s, _ = StrategyResolver._search_object( s, _ = StrategyResolver._search_object(
directory=default_location, directory=default_location,
object_name='StrategyTestV2', object_name=CURRENT_TEST_STRATEGY,
add_source=True, add_source=True,
) )
assert issubclass(s, IStrategy) assert issubclass(s, IStrategy)
@ -35,7 +35,7 @@ def test_search_all_strategies_no_failed():
directory = Path(__file__).parent / "strats" directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=False) strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
assert isinstance(strategies, list) assert isinstance(strategies, list)
assert len(strategies) == 4 assert len(strategies) == 5
assert isinstance(strategies[0], dict) assert isinstance(strategies[0], dict)
@ -43,10 +43,10 @@ def test_search_all_strategies_with_failed():
directory = Path(__file__).parent / "strats" directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True) strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
assert isinstance(strategies, list) assert isinstance(strategies, list)
assert len(strategies) == 5 assert len(strategies) == 6
# with enum_failed=True search_all_objects() shall find 2 good strategies # with enum_failed=True search_all_objects() shall find 2 good strategies
# and 1 which fails to load # 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 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): def test_load_strategy_invalid_directory(result, caplog, default_conf):
default_conf['strategy'] = 'StrategyTestV2' default_conf['strategy'] = 'StrategyTestV3'
extra_dir = Path.cwd() / 'some/path' extra_dir = Path.cwd() / 'some/path'
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
StrategyResolver._load_strategy('StrategyTestV2', config=default_conf, StrategyResolver._load_strategy(CURRENT_TEST_STRATEGY, config=default_conf,
extra_dir=extra_dir) extra_dir=extra_dir)
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog) 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) StrategyResolver.load_strategy(default_conf)
def test_strategy(result, default_conf): @pytest.mark.filterwarnings("ignore:deprecated")
default_conf.update({'strategy': 'StrategyTestV2'}) @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) strategy = StrategyResolver.load_strategy(default_conf)
metadata = {'pair': 'ETH/BTC'} metadata = {'pair': 'ETH/BTC'}
@ -117,11 +119,11 @@ def test_strategy(result, default_conf):
df_indicators = strategy.advise_indicators(result, metadata=metadata) df_indicators = strategy.advise_indicators(result, metadata=metadata)
assert 'adx' in df_indicators 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 'buy' not in dataframe.columns
assert 'enter_long' 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 'sell' not in dataframe.columns
assert 'exit_long' 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): 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({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'minimal_roi': { 'minimal_roi': {
"20": 0.1, "20": 0.1,
"0": 0.5 "0": 0.5
@ -146,7 +148,7 @@ def test_strategy_override_minimal_roi(caplog, default_conf):
def test_strategy_override_stoploss(caplog, default_conf): def test_strategy_override_stoploss(caplog, default_conf):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'stoploss': -0.5 'stoploss': -0.5
}) })
strategy = StrategyResolver.load_strategy(default_conf) 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): def test_strategy_override_trailing_stop(caplog, default_conf):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'trailing_stop': True 'trailing_stop': True
}) })
strategy = StrategyResolver.load_strategy(default_conf) 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): def test_strategy_override_trailing_stop_positive(caplog, default_conf):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'trailing_stop_positive': -0.1, 'trailing_stop_positive': -0.1,
'trailing_stop_positive_offset': -0.2 'trailing_stop_positive_offset': -0.2
@ -191,7 +193,7 @@ def test_strategy_override_timeframe(caplog, default_conf):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'timeframe': 60, 'timeframe': 60,
'stake_currency': 'ETH' 'stake_currency': 'ETH'
}) })
@ -207,7 +209,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'process_only_new_candles': True 'process_only_new_candles': True
}) })
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
@ -227,7 +229,7 @@ def test_strategy_override_order_types(caplog, default_conf):
'stoploss_on_exchange': True, 'stoploss_on_exchange': True,
} }
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'order_types': order_types 'order_types': order_types
}) })
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
@ -241,12 +243,12 @@ def test_strategy_override_order_types(caplog, default_conf):
" 'stoploss_on_exchange': True}.", caplog) " 'stoploss_on_exchange': True}.", caplog)
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'order_types': {'buy': 'market'} 'order_types': {'buy': 'market'}
}) })
# Raise error for invalid configuration # Raise error for invalid configuration
with pytest.raises(ImportError, 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."): r"Order-types mapping is incomplete."):
StrategyResolver.load_strategy(default_conf) StrategyResolver.load_strategy(default_conf)
@ -260,7 +262,7 @@ def test_strategy_override_order_tif(caplog, default_conf):
} }
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'order_time_in_force': order_time_in_force 'order_time_in_force': order_time_in_force
}) })
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
@ -273,20 +275,20 @@ def test_strategy_override_order_tif(caplog, default_conf):
" {'buy': 'fok', 'sell': 'gtc'}.", caplog) " {'buy': 'fok', 'sell': 'gtc'}.", caplog)
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'order_time_in_force': {'buy': 'fok'} 'order_time_in_force': {'buy': 'fok'}
}) })
# Raise error for invalid configuration # Raise error for invalid configuration
with pytest.raises(ImportError, with pytest.raises(ImportError,
match=r"Impossible to load Strategy 'StrategyTestV2'. " match=f"Impossible to load Strategy '{CURRENT_TEST_STRATEGY}'. "
r"Order-time-in-force mapping is incomplete."): "Order-time-in-force mapping is incomplete."):
StrategyResolver.load_strategy(default_conf) StrategyResolver.load_strategy(default_conf)
def test_strategy_override_use_sell_signal(caplog, default_conf): def test_strategy_override_use_sell_signal(caplog, default_conf):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
}) })
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.use_sell_signal 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'] assert default_conf['use_sell_signal']
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'use_sell_signal': False, 'use_sell_signal': False,
}) })
strategy = StrategyResolver.load_strategy(default_conf) 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): def test_strategy_override_use_sell_profit_only(caplog, default_conf):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
}) })
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
assert not strategy.sell_profit_only 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'] assert not default_conf['sell_profit_only']
default_conf.update({ default_conf.update({
'strategy': 'StrategyTestV2', 'strategy': CURRENT_TEST_STRATEGY,
'sell_profit_only': True, 'sell_profit_only': True,
}) })
strategy = StrategyResolver.load_strategy(default_conf) 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: with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered. # Cause all warnings to always be triggered.
warnings.simplefilter("always") warnings.simplefilter("always")
strategy.advise_buy(indicators, {'pair': 'ETH/BTC'}) strategy.advise_entry(indicators, {'pair': 'ETH/BTC'})
assert len(w) == 1 assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning) assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \ 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: with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered. # Cause all warnings to always be triggered.
warnings.simplefilter("always") warnings.simplefilter("always")
strategy.advise_sell(indicators, {'pair': 'ETH_BTC'}) strategy.advise_exit(indicators, {'pair': 'ETH_BTC'})
assert len(w) == 1 assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning) assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \ 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") @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" default_location = Path(__file__).parent / "strats"
del default_conf['timeframe'] del default_conf['timeframe']
default_conf.update({'strategy': 'TestStrategyLegacyV1', 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 isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns 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 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 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'.", assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.",
caplog) caplog)
@ -411,13 +413,13 @@ def test_strategy_interface_versioning(result, default_conf):
assert isinstance(indicator_df, DataFrame) assert isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns 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 isinstance(enterdf, DataFrame)
assert 'buy' not in enterdf.columns assert 'buy' not in enterdf.columns
assert 'enter_long' 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 isinstance(exitdf, DataFrame)
assert 'sell' not in exitdf assert 'sell' not in exitdf
assert 'exit_long' in exitdf assert 'exit_long' in exitdf

View File

@ -7,6 +7,7 @@ import pytest
from freqtrade.commands import Arguments from freqtrade.commands import Arguments
from freqtrade.commands.cli_options import check_int_nonzero, check_int_positive from freqtrade.commands.cli_options import check_int_nonzero, check_int_positive
from tests.conftest import CURRENT_TEST_STRATEGY
# Parse common command-line-arguments. Used for all tools # Parse common command-line-arguments. Used for all tools
@ -123,7 +124,7 @@ def test_parse_args_backtesting_custom() -> None:
'-c', 'test_conf.json', '-c', 'test_conf.json',
'--ticker-interval', '1m', '--ticker-interval', '1m',
'--strategy-list', '--strategy-list',
'StrategyTestV2', CURRENT_TEST_STRATEGY,
'SampleStrategy' 'SampleStrategy'
] ]
call_args = Arguments(args).get_parsed_arg() call_args = Arguments(args).get_parsed_arg()

View File

@ -23,7 +23,8 @@ from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.loggers import _set_loggers, setup_logging, setup_logging_pre from freqtrade.loggers import _set_loggers, setup_logging, setup_logging_pre
from tests.conftest import log_has, log_has_re, patched_configuration_load_config_file from tests.conftest import (CURRENT_TEST_STRATEGY, log_has, log_has_re,
patched_configuration_load_config_file)
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
@ -403,7 +404,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
arglist = [ arglist = [
'backtesting', 'backtesting',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
] ]
args = Arguments(arglist).get_parsed_arg() args = Arguments(arglist).get_parsed_arg()
@ -440,7 +441,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
arglist = [ arglist = [
'backtesting', 'backtesting',
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'StrategyTestV2', '--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar', '--datadir', '/foo/bar',
'--userdir', "/tmp/freqtrade", '--userdir', "/tmp/freqtrade",
'--ticker-interval', '1m', '--ticker-interval', '1m',
@ -497,7 +498,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
'--ticker-interval', '1m', '--ticker-interval', '1m',
'--export', 'trades', '--export', 'trades',
'--strategy-list', '--strategy-list',
'StrategyTestV2', CURRENT_TEST_STRATEGY,
'TestStrategy' 'TestStrategy'
] ]