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
# 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
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
# 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
echo "failed running backtest"

View File

@ -539,9 +539,10 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
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
network requests in this method.
@ -549,12 +550,13 @@ class AwesomeStrategy(IStrategy):
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 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 time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
: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.
:return bool: When True is returned, then the buy-order is placed on the exchange.
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):
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: 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)
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
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

View File

@ -31,6 +31,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'profit_ratio', 'profit_abs', 'sell_reason',
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
'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:

View File

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

View File

@ -15,8 +15,7 @@ class SignalTagType(Enum):
"""
Enum for signal columns
"""
BUY_TAG = "buy_tag"
SHORT_TAG = "short_tag"
ENTER_TAG = "enter_tag"
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
add it to params
"""
if leverage > 1.0:
self._params['leverage'] = leverage
else:
if 'leverage' in self._params:
del self._params['leverage']
return
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
# 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
)
if side:
if signal:
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {})
@ -585,7 +585,9 @@ class FreqtradeBot(LoggingMixin):
default_retval=stake_amount)(
pair=pair, current_time=datetime.now(timezone.utc),
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)
if not stake_amount:
@ -603,10 +605,13 @@ class FreqtradeBot(LoggingMixin):
order_type = self.strategy.order_types.get('forcebuy', order_type)
# 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)(
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
time_in_force=time_in_force, current_time=datetime.now(timezone.utc)):
logger.info(f"User requested abortion of {name.lower()}ing {pair}")
time_in_force=time_in_force, current_time=datetime.now(timezone.utc),
side='short' if is_short else 'long'
):
logger.info(f"User requested abortion of buying {pair}")
return False
amount = self.exchange.amount_to_precision(pair, amount)
order = self.exchange.create_order(

View File

@ -45,8 +45,7 @@ LONG_IDX = 5
ELONG_IDX = 6 # Exit long
SHORT_IDX = 7
ESHORT_IDX = 8 # Exit short
BUY_TAG_IDX = 9
SHORT_TAG_IDX = 10
ENTER_TAG_IDX = 9
class Backtesting:
@ -139,6 +138,10 @@ class Backtesting:
self.config['startup_candle_count'] = self.required_startup
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.abort = False
@ -249,7 +252,7 @@ class Backtesting:
# Every change to this headers list must evaluate further usages of the resulting tuple
# and eventually change the constants for indexes at the top
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 = {}
self.progress.init_step(BacktestState.CONVERT, len(processed))
@ -260,18 +263,10 @@ class Backtesting:
if not pair_data.empty:
# Cleanup from prior runs
# TODO-lev: The below is not 100% compatible with the interface compatibility layer
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
pair_data.drop(headers[5:] + ['buy', 'sell'], axis=1, errors='ignore')
df_analyzed = self.strategy.advise_sell(
self.strategy.advise_buy(pair_data, {'pair': pair}),
df_analyzed = self.strategy.advise_exit(
self.strategy.advise_entry(pair_data, {'pair': pair}),
{'pair': pair}
).copy()
# Trim startup period from analyzed dataframe
@ -279,11 +274,11 @@ class Backtesting:
startup_candles=self.required_startup)
# To avoid using data from future, we use buy/sell signals shifted
# from the previous candle
df_analyzed.loc[:, 'enter_long'] = df_analyzed.loc[:, 'enter_long'].shift(1)
df_analyzed.loc[:, 'enter_short'] = df_analyzed.loc[:, 'enter_short'].shift(1)
df_analyzed.loc[:, 'exit_long'] = df_analyzed.loc[:, 'exit_long'].shift(1)
df_analyzed.loc[:, 'exit_short'] = df_analyzed.loc[:, 'exit_short'].shift(1)
df_analyzed.loc[:, 'long_tag'] = df_analyzed.loc[:, 'long_tag'].shift(1)
for col in headers[5:]:
if col in df_analyzed.columns:
df_analyzed.loc[:, col] = df_analyzed.loc[:, col].shift(1)
else:
df_analyzed.loc[:, col] = 0 if col != 'enter_tag' else None
# Update dataprovider cache
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,
default_retval=stake_amount)(
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)
if not stake_amount:
@ -445,12 +441,13 @@ class Backtesting:
# Confirm trade entry:
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],
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
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
# Enter trade
has_buy_tag = len(row) >= BUY_TAG_IDX + 1
has_enter_tag = len(row) >= ENTER_TAG_IDX + 1
trade = LocalTrade(
pair=pair,
open_rate=row[OPEN_IDX],
@ -460,7 +457,7 @@ class Backtesting:
fee_open=self.fee,
fee_close=self.fee,
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,
is_short=(direction == 'short'),
)
@ -499,8 +496,8 @@ class Backtesting:
def check_for_trade_entry(self, row) -> Optional[str]:
enter_long = row[LONG_IDX] == 1
exit_long = row[ELONG_IDX] == 1
enter_short = row[SHORT_IDX] == 1
exit_short = row[ESHORT_IDX] == 1
enter_short = self._can_short and row[SHORT_IDX] == 1
exit_short = self._can_short and row[ESHORT_IDX] == 1
if enter_long == 1 and not any([exit_long, enter_short]):
# Long

View File

@ -230,9 +230,9 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
pass
# TODO-lev: add side
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.
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 time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
: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.
:return bool: When True is returned, then the buy-order is placed on the exchange.
False aborts the process
@ -365,13 +366,11 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
return None
# TODO-lev: add side
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: 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
enabled.
Customize stake size for each new trade.
:param pair: Pair that's currently analyzed
: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 min_stake: Minimal stake size allowed by exchange.
: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 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:
"""
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")
dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata)
dataframe = self.advise_entry(dataframe, metadata)
dataframe = self.advise_exit(dataframe, metadata)
return 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.ENTER_SHORT.value] = 0
dataframe[SignalType.EXIT_SHORT.value] = 0
dataframe[SignalTagType.BUY_TAG.value] = None
dataframe[SignalTagType.SHORT_TAG.value] = None
dataframe[SignalTagType.ENTER_TAG.value] = None
# Other Defs in strategy that want to be called every loop here
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
@ -563,9 +579,8 @@ class IStrategy(ABC, HyperStrategyMixin):
message = ""
if dataframe is None:
message = "No dataframe returned (return statement missing?)."
elif 'buy' not in dataframe:
# TODO-lev: Something?
message = "Buy column not set."
elif 'enter_long' not in dataframe:
message = "enter_long/buy column not set."
elif df_len != len(dataframe):
message = message_template.format("length")
elif df_close != dataframe["close"].iloc[-1]:
@ -675,10 +690,10 @@ class IStrategy(ABC, HyperStrategyMixin):
enter_tag_value: Optional[str] = None
if enter_long == 1 and not any([exit_long, enter_short]):
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]):
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)
@ -897,7 +912,7 @@ class IStrategy(ABC, HyperStrategyMixin):
def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
"""
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.
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.
@ -929,7 +944,7 @@ class IStrategy(ABC, HyperStrategyMixin):
else:
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
This method should not be overridden.
@ -944,15 +959,15 @@ class IStrategy(ABC, HyperStrategyMixin):
if self._buy_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning)
return self.populate_buy_trend(dataframe) # type: ignore
df = self.populate_buy_trend(dataframe) # type: ignore
else:
df = self.populate_buy_trend(dataframe, metadata)
if 'enter_long' not in df.columns:
df = df.rename({'buy': 'enter_long', 'buy_tag': 'long_tag'}, axis='columns')
if 'enter_long' not in df.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
This method should not be overridden.
@ -966,26 +981,9 @@ class IStrategy(ABC, HyperStrategyMixin):
if self._sell_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning)
return self.populate_sell_trend(dataframe) # type: ignore
df = self.populate_sell_trend(dataframe) # type: ignore
else:
df = self.populate_sell_trend(dataframe, metadata)
if 'exit_long' not in df.columns:
df = df.rename({'sell': 'exit_long'}, axis='columns')
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
if 'exit_long' not in df.columns:
df = df.rename({'sell': 'exit_long'}, axis='columns')
return df

View File

@ -15,8 +15,9 @@ import talib.abstract as ta
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.
class SampleStrategy(IStrategy):
class SampleShortStrategy(IStrategy):
"""
This is a sample strategy to inspire you.
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
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,
**kwargs) -> float:
side: str, **kwargs) -> float:
"""
Customize stake size for each new trade. This method is not called when edge module is
enabled.
Customize stake size for each new trade.
:param pair: Pair that's currently analyzed
: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 min_stake: Minimal stake size allowed by exchange.
: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 proposed_stake
@ -80,9 +80,10 @@ def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', curre
return None
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
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).
: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 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 time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
: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.
:return bool: When True is returned, then the buy-order is placed on the exchange.
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.enums import RunMode
from freqtrade.exceptions import OperationalException
from tests.conftest import (create_mock_trades, get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_args, log_has,
log_has_re, patch_exchange, patched_configuration_load_config_file)
from tests.conftest_trades import MOCK_TRADE_COUNT
@ -774,7 +774,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
captured = capsys.readouterr()
assert "TestStrategyLegacyV1" 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
args = [
@ -789,7 +789,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
captured = capsys.readouterr()
assert "TestStrategyLegacyV1" 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
args = [
@ -803,7 +803,7 @@ def test_start_list_strategies(mocker, caplog, capsys):
captured = capsys.readouterr()
assert "TestStrategyLegacyV1" 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

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
np.seterr(all='raise')
CURRENT_TEST_STRATEGY = 'StrategyTestV3'
TRADE_SIDES = ('long', 'short')
def pytest_addoption(parser):
parser.addoption('--longrun', action='store_true', dest="longrun",
@ -414,7 +417,7 @@ def get_default_conf(testdatadir):
"user_data_dir": Path("user_data"),
"verbosity": 3,
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
"strategy": "StrategyTestV2",
"strategy": CURRENT_TEST_STRATEGY,
"disableparamexport": True,
"internals": {},
"export": "none",

View File

@ -45,7 +45,7 @@ def mock_trade_1(fee, is_short: bool):
open_rate=0.123,
exchange='binance',
open_order_id=f'dry_run_buy_{direc(is_short)}_12345',
strategy='StrategyTestV2',
strategy='StrategyTestV3',
timeframe=5,
is_short=is_short
)
@ -100,7 +100,7 @@ def mock_trade_2(fee, is_short: bool):
exchange='binance',
is_open=False,
open_order_id=f'dry_run_sell_{direc(is_short)}_12345',
strategy='StrategyTestV2',
strategy='StrategyTestV3',
timeframe=5,
sell_reason='sell_signal',
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,
exchange='binance',
is_open=False,
strategy='StrategyTestV2',
strategy='StrategyTestV3',
timeframe=5,
sell_reason='roi',
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,
exchange='binance',
open_order_id=f'prod_buy_{direc(is_short)}_12345',
strategy='StrategyTestV2',
strategy='StrategyTestV3',
timeframe=5,
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,
load_trades_from_db)
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
@ -129,7 +129,7 @@ def test_load_trades_from_db(default_conf, fee, is_short, mocker):
for col in BT_DATA_COLUMNS:
if col not in ['index', 'open_at_end']:
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
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy')
assert len(trades) == 0
@ -187,7 +187,7 @@ def test_load_trades(default_conf, mocker):
db_url=default_conf.get('db_url'),
exportfilename=default_conf.get('exportfilename'),
no_trades=False,
strategy="StrategyTestV2",
strategy=CURRENT_TEST_STRATEGY,
)
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.misc import file_dump_json
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
@ -380,7 +381,7 @@ def test_file_dump_json_tofile(testdatadir) -> None:
def test_get_timerange(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker)
default_conf.update({'strategy': 'StrategyTestV2'})
default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
strategy = StrategyResolver.load_strategy(default_conf)
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:
patch_exchange(mocker)
default_conf.update({'strategy': 'StrategyTestV2'})
default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
strategy = StrategyResolver.load_strategy(default_conf)
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:
patch_exchange(mocker)
default_conf.update({'strategy': 'StrategyTestV2'})
default_conf.update({'strategy': CURRENT_TEST_STRATEGY})
strategy = StrategyResolver.load_strategy(default_conf)
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 == {}
Exchange._headers = {}
# TODO-lev: Test with options in ccxt_config
def test_destroy(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG)

View File

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

View File

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

View File

@ -532,7 +532,7 @@ tc33 = BTContainer(data=[
sell_reason=SellType.TRAILING_STOP_LOSS,
open_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._set_strategy(backtesting.strategylist[0])
backtesting.required_startup = 0
backtesting.strategy.advise_buy = lambda a, m: frame
backtesting.strategy.advise_sell = lambda a, m: frame
backtesting.strategy.advise_entry = lambda a, m: frame
backtesting.strategy.advise_exit = lambda a, m: frame
backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss
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):
res = results.iloc[c]
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.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.persistence import LocalTrade
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)
@ -159,7 +159,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--export', 'none'
]
@ -194,7 +194,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar',
'--timeframe', '1m',
'--enable-position-stacking',
@ -244,7 +244,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1',
'--starting-balance', '2'
]
@ -255,7 +255,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1',
'--starting-balance', '0.5'
]
@ -273,7 +273,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
pargs = get_args(args)
start_backtesting(pargs)
@ -295,8 +295,8 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
assert backtesting.config == default_conf
assert backtesting.timeframe == '5m'
assert callable(backtesting.strategy.advise_all_indicators)
assert callable(backtesting.strategy.advise_buy)
assert callable(backtesting.strategy.advise_sell)
assert callable(backtesting.strategy.advise_entry)
assert callable(backtesting.strategy.advise_exit)
assert isinstance(backtesting.strategy.dp, DataProvider)
get_fee.assert_called()
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:
patch_exchange(mocker)
del default_conf['timeframe']
default_conf['strategy_list'] = ['StrategyTestV2',
default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY,
'SampleStrategy']
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
# 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)
processed2 = strategy.advise_all_indicators(data)
@ -486,7 +485,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
Backtesting(default_conf)
# Multiple strategies
default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1']
default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'TestStrategyLegacyV1']
with pytest.raises(OperationalException,
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
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):
# Override the default buy trend function in our StrategyTestV2
# Override the default buy trend function in our StrategyTest
def fun(dataframe=None, pair=None):
buy_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)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override
backtesting.strategy.advise_entry = fun # Override
backtesting.strategy.advise_exit = fun # Override
result = backtesting.backtest(**backtest_conf)
assert result['results'].empty
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):
buy_value = 0
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)
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override
backtesting.strategy.advise_entry = fun # Override
backtesting.strategy.advise_exit = fun # Override
result = backtesting.backtest(**backtest_conf)
assert result['results'].empty
@ -843,8 +842,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
backtesting = Backtesting(default_conf)
backtesting.required_startup = 0
backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate # Override
backtesting.strategy.advise_sell = _trend_alternate # Override
backtesting.strategy.advise_entry = _trend_alternate # Override
backtesting.strategy.advise_exit = _trend_alternate # Override
result = backtesting.backtest(**backtest_conf)
# 200 candles in backtest data
# 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._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate_hold # Override
backtesting.strategy.advise_sell = _trend_alternate_hold # Override
backtesting.strategy.advise_entry = _trend_alternate_hold # Override
backtesting.strategy.advise_exit = _trend_alternate_hold # Override
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
@ -948,7 +947,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
args = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', str(testdatadir),
'--timeframe', '1m',
'--timerange', '1510694220-1510700340',
@ -1019,7 +1018,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'--enable-position-stacking',
'--disable-max-market-positions',
'--strategy-list',
'StrategyTestV2',
CURRENT_TEST_STRATEGY,
'TestStrategyLegacyV1',
]
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 '
'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy StrategyTestV2',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
'Running backtesting for Strategy TestStrategyLegacyV1',
]
@ -1123,7 +1122,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'--enable-position-stacking',
'--disable-max-market-positions',
'--strategy-list',
'StrategyTestV2',
CURRENT_TEST_STRATEGY,
'TestStrategyLegacyV1',
]
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 '
'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy StrategyTestV2',
f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}',
'Running backtesting for Strategy TestStrategyLegacyV1',
]
@ -1228,7 +1227,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'--timeframe', '5m',
'--timeframe-detail', '1m',
'--strategy-list',
'StrategyTestV2'
CURRENT_TEST_STRATEGY
]
args = get_args(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).',
'Backtesting with data from 2019-10-11 01:40:00 '
'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:

View File

@ -6,7 +6,7 @@ from unittest.mock import MagicMock
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge
from freqtrade.enums import RunMode
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)
@ -16,7 +16,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
args = [
'edge',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
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 = [
'edge',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar',
'--timeframe', '1m',
'--timerange', ':100',
@ -80,7 +80,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
args = [
'edge',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
pargs = get_args(args)
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.space import SKDecimal
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)
@ -125,7 +125,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
args = [
'hyperopt',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--stake-amount', '1',
'--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
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
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 dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
@ -772,8 +772,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
assert dumper.called
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")
@ -821,8 +821,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
assert dumper.called
assert dumper.call_count == 1
assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry")
assert hasattr(hyperopt, "max_open_trades")
assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades']
assert hasattr(hyperopt, "position_stacking")

View File

@ -10,7 +10,7 @@ import rapidjson
from freqtrade.constants import FTHYPT_FILEVERSION
from freqtrade.exceptions import OperationalException
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
@ -167,9 +167,9 @@ def test__pprint_dict():
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 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')
assert x is None
@ -177,7 +177,7 @@ def test_get_strategy_filename(default_conf):
def test_export_params(tmpdir):
filename = Path(tmpdir) / "StrategyTestV2.json"
filename = Path(tmpdir) / f"{CURRENT_TEST_STRATEGY}.json"
assert not filename.is_file()
params = {
"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()
content = rapidjson.load(filename.open('r'))
assert content['strategy_name'] == 'StrategyTestV2'
assert content['strategy_name'] == CURRENT_TEST_STRATEGY
assert 'params' in content
assert "buy" 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
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()
params = {
"params_details": {
@ -252,17 +252,17 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
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 export_mock.call_count == 0
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_args_list[0][0][1] == 'StrategyTestV2'
assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json'
assert export_mock.call_args_list[0][0][1] == CURRENT_TEST_STRATEGY
assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v3.json'
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_strategy)
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
@ -52,7 +53,7 @@ def test_text_table_bt_results():
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)
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.api_auth import create_token, get_user_from_token
from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer
from tests.conftest import (create_mock_trades, get_mock_coro, get_patched_freqtradebot, log_has,
log_has_re, patch_get_signal)
from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_mock_coro,
get_patched_freqtradebot, log_has, log_has_re, patch_get_signal)
BASE_URI = "/api/v1"
@ -895,7 +895,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'open_trade_value': 15.1668225,
'sell_reason': None,
'sell_order_status': None,
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None,
'timeframe': 5,
'exchange': 'binance',
@ -1000,7 +1000,7 @@ def test_api_forcebuy(botclient, mocker, fee):
close_rate=0.265441,
id=22,
timeframe=5,
strategy="StrategyTestV2"
strategy=CURRENT_TEST_STRATEGY
))
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,
'sell_reason': None,
'sell_order_status': None,
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None,
'timeframe': 5,
'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}")
assert_response(rc)
assert 'strategy' in rc.json()
assert rc.json()['strategy'] == 'StrategyTestV2'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert 'columns' in rc.json()
assert 'data_start_ts' in rc.json()
assert 'data_start' in rc.json()
@ -1155,19 +1155,19 @@ def test_api_pair_history(botclient, ohlcv_history):
# No pair
rc = client_get(client,
f"{BASE_URI}/pair_history?timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=StrategyTestV2")
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422)
# No Timeframe
rc = client_get(client,
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)
# No timerange
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&strategy=StrategyTestV2")
f"&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422)
# No strategy
@ -1179,14 +1179,14 @@ def test_api_pair_history(botclient, ohlcv_history):
# Working
rc = client_get(client,
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 rc.json()['length'] == 289
assert len(rc.json()['data']) == rc.json()['length']
assert 'columns' in rc.json()
assert 'data' in rc.json()
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_ts'] == 1515628800000
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
rc = client_get(client,
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 rc.json()['error'] == ("Error querying /api/v1/pair_history: "
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
@ -1236,19 +1236,20 @@ def test_api_strategies(botclient):
'HyperoptableStrategy',
'InformativeDecoratorTest',
'StrategyTestV2',
'TestStrategyLegacyV1'
'StrategyTestV3',
'TestStrategyLegacyV1',
]}
def test_api_strategy(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 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
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
@ -1305,7 +1306,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog):
# start backtesting
data = {
"strategy": "StrategyTestV2",
"strategy": CURRENT_TEST_STRATEGY,
"timeframe": "5m",
"timerange": "20180110-20180111",
"max_open_trades": 3,

View File

@ -25,8 +25,8 @@ from freqtrade.loggers import setup_logging
from freqtrade.persistence import PairLocks, Trade
from freqtrade.rpc import RPC
from freqtrade.rpc.telegram import Telegram, authorized_only
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, log_has_re,
patch_exchange, patch_get_signal, patch_whitelist)
from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_patched_freqtradebot,
log_has, log_has_re, patch_exchange, patch_get_signal, patch_whitelist)
class DummyCls(Telegram):
@ -1242,7 +1242,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
assert msg_mock.call_count == 1
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 '*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]
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 '*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 '*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]

View File

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

View File

@ -4,7 +4,7 @@
import talib.abstract as ta
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
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import 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 .strats.strategy_test_v2 import StrategyTestV2
from .strats.strategy_test_v3 import StrategyTestV3
def test_strategy_test_v2_structure():
assert hasattr(StrategyTestV2, 'minimal_roi')
assert hasattr(StrategyTestV2, 'stoploss')
assert hasattr(StrategyTestV2, 'timeframe')
assert hasattr(StrategyTestV2, 'populate_indicators')
assert hasattr(StrategyTestV2, 'populate_buy_trend')
assert hasattr(StrategyTestV2, 'populate_sell_trend')
assert hasattr(StrategyTestV3, 'minimal_roi')
assert hasattr(StrategyTestV3, 'stoploss')
assert hasattr(StrategyTestV3, 'timeframe')
assert hasattr(StrategyTestV3, 'populate_indicators')
assert hasattr(StrategyTestV3, 'populate_buy_trend')
assert hasattr(StrategyTestV3, 'populate_sell_trend')
def test_strategy_test_v2(result, fee):
strategy = StrategyTestV2({})
strategy = StrategyTestV3({})
metadata = {'pair': 'ETH/BTC'}
assert type(strategy.minimal_roi) is dict
@ -37,7 +37,7 @@ def test_strategy_test_v2(result, fee):
assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc',
current_time=datetime.utcnow()) is True
current_time=datetime.utcnow(), side='long') is True
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc', sell_reason='roi',
current_time=datetime.utcnow()) is True

View File

@ -21,13 +21,13 @@ from freqtrade.strategy.hyper import (BaseParameter, BooleanParameter, Categoric
DecimalParameter, IntParameter, RealParameter)
from freqtrade.strategy.interface import SellCheckTuple
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from tests.conftest import log_has, log_has_re
from tests.conftest import CURRENT_TEST_STRATEGY, TRADE_SIDES, log_has, log_has_re
from .strats.strategy_test_v2 import StrategyTestV2
from .strats.strategy_test_v3 import StrategyTestV3
# Avoid to reinit the same object again and again
_STRATEGY = StrategyTestV2(config={})
_STRATEGY = StrategyTestV3(config={})
_STRATEGY.dp = DataProvider({}, None, None)
@ -59,7 +59,7 @@ def test_returns_latest_signal(ohlcv_history):
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False)
mocked_history.loc[1, 'exit_long'] = 0
mocked_history.loc[1, 'enter_long'] = 1
mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01'
mocked_history.loc[1, 'enter_tag'] = 'buy_signal_01'
assert _STRATEGY.get_entry_signal(
'ETH/BTC', '5m', mocked_history) == (SignalDirection.LONG, 'buy_signal_01')
@ -70,8 +70,10 @@ def test_returns_latest_signal(ohlcv_history):
mocked_history.loc[1, 'enter_long'] = 0
mocked_history.loc[1, 'enter_short'] = 1
mocked_history.loc[1, 'exit_short'] = 0
mocked_history.loc[1, 'enter_tag'] = 'sell_signal_01'
assert _STRATEGY.get_entry_signal(
'ETH/BTC', '5m', mocked_history) == (SignalDirection.SHORT, None)
'ETH/BTC', '5m', mocked_history) == (SignalDirection.SHORT, 'sell_signal_01')
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False)
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (True, False)
@ -177,7 +179,6 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
def test_ignore_expired_candle(default_conf):
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.ignore_buying_expired_candle_after = 60
@ -224,8 +225,8 @@ def test_assert_df_raise(mocker, caplog, ohlcv_history):
def test_assert_df(ohlcv_history, caplog):
df_len = len(ohlcv_history) - 1
ohlcv_history.loc[:, 'buy'] = 0
ohlcv_history.loc[:, 'sell'] = 0
ohlcv_history.loc[:, 'enter_long'] = 0
ohlcv_history.loc[:, 'exit_long'] = 0
# Ensure it's running when passed correctly
_STRATEGY.assert_df(ohlcv_history, len(ohlcv_history),
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[df_len, 'date'])
@ -248,8 +249,8 @@ def test_assert_df(ohlcv_history, caplog):
_STRATEGY.assert_df(None, len(ohlcv_history),
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date'])
with pytest.raises(StrategyError,
match="Buy column not set"):
_STRATEGY.assert_df(ohlcv_history.drop('buy', axis=1), len(ohlcv_history),
match="enter_long/buy column not set."):
_STRATEGY.assert_df(ohlcv_history.drop('enter_long', axis=1), len(ohlcv_history),
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date'])
_STRATEGY.disable_dataframe_checks = True
@ -262,7 +263,6 @@ def test_assert_df(ohlcv_history, caplog):
def test_advise_all_indicators(default_conf, testdatadir) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
timerange = TimeRange.parse_timerange('1510694220-1510700340')
@ -273,7 +273,6 @@ def test_advise_all_indicators(default_conf, testdatadir) -> None:
def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators')
timerange = TimeRange.parse_timerange('1510694220-1510700340')
@ -291,7 +290,6 @@ def test_min_roi_reached(default_conf, fee) -> None:
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
{0: 0.1, 20: 0.05, 55: 0.01}]
for roi in min_roi_list:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = roi
trade = Trade(
@ -330,7 +328,6 @@ def test_min_roi_reached2(default_conf, fee) -> None:
},
]
for roi in min_roi_list:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = roi
trade = Trade(
@ -365,7 +362,6 @@ def test_min_roi_reached3(default_conf, fee) -> None:
30: 0.05,
55: 0.30,
}
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = min_roi
trade = Trade(
@ -418,8 +414,6 @@ def test_min_roi_reached3(default_conf, fee) -> None:
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom,
profit2, adjusted2, expected2, custom_stop) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
trade = Trade(
pair='ETH/BTC',
@ -466,8 +460,6 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
def test_custom_sell(default_conf, fee, caplog) -> None:
default_conf.update({'strategy': 'StrategyTestV2'})
strategy = StrategyResolver.load_strategy(default_conf)
trade = Trade(
pair='ETH/BTC',
@ -516,23 +508,49 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog)
@pytest.mark.parametrize('side', TRADE_SIDES)
def test_leverage_callback(default_conf, side) -> None:
default_conf['strategy'] = 'StrategyTestV2'
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.leverage(
pair='XRP/USDT',
current_time=datetime.now(timezone.utc),
current_rate=2.2,
proposed_leverage=1.0,
max_leverage=5.0,
side=side,
) == 1
default_conf['strategy'] = CURRENT_TEST_STRATEGY
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.leverage(
pair='XRP/USDT',
current_time=datetime.now(timezone.utc),
current_rate=2.2,
proposed_leverage=1.0,
max_leverage=5.0,
side=side,
) == 3
def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x)
buy_mock = MagicMock(side_effect=lambda x, meta: x)
sell_mock = MagicMock(side_effect=lambda x, meta: x)
entry_mock = MagicMock(side_effect=lambda x, meta: x)
exit_mock = MagicMock(side_effect=lambda x, meta: x)
mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy',
advise_indicators=ind_mock,
advise_buy=buy_mock,
advise_sell=sell_mock,
advise_entry=entry_mock,
advise_exit=exit_mock,
)
strategy = StrategyTestV2({})
strategy = StrategyTestV3({})
strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
assert ind_mock.call_count == 1
assert buy_mock.call_count == 1
assert buy_mock.call_count == 1
assert entry_mock.call_count == 1
assert entry_mock.call_count == 1
assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
@ -541,8 +559,8 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
# No analysis happens as process_only_new_candles is true
assert ind_mock.call_count == 2
assert buy_mock.call_count == 2
assert buy_mock.call_count == 2
assert entry_mock.call_count == 2
assert entry_mock.call_count == 2
assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
@ -550,16 +568,16 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> None:
caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x)
buy_mock = MagicMock(side_effect=lambda x, meta: x)
sell_mock = MagicMock(side_effect=lambda x, meta: x)
entry_mock = MagicMock(side_effect=lambda x, meta: x)
exit_mock = MagicMock(side_effect=lambda x, meta: x)
mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy',
advise_indicators=ind_mock,
advise_buy=buy_mock,
advise_sell=sell_mock,
advise_entry=entry_mock,
advise_exit=exit_mock,
)
strategy = StrategyTestV2({})
strategy = StrategyTestV3({})
strategy.dp = DataProvider({}, None, None)
strategy.process_only_new_candles = True
@ -569,8 +587,8 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
assert 'close' in ret.columns
assert isinstance(ret, DataFrame)
assert ind_mock.call_count == 1
assert buy_mock.call_count == 1
assert buy_mock.call_count == 1
assert entry_mock.call_count == 1
assert entry_mock.call_count == 1
assert log_has('TA Analysis Launched', caplog)
assert not log_has('Skipping TA Analysis for already analyzed candle', caplog)
caplog.clear()
@ -578,20 +596,19 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
ret = strategy._analyze_ticker_internal(ohlcv_history, {'pair': 'ETH/BTC'})
# No analysis happens as process_only_new_candles is true
assert ind_mock.call_count == 1
assert buy_mock.call_count == 1
assert buy_mock.call_count == 1
assert entry_mock.call_count == 1
assert entry_mock.call_count == 1
# only skipped analyze adds buy and sell columns, otherwise it's all mocked
assert 'buy' in ret.columns
assert 'sell' in ret.columns
assert ret['buy'].sum() == 0
assert ret['sell'].sum() == 0
assert 'enter_long' in ret.columns
assert 'exit_long' in ret.columns
assert ret['enter_long'].sum() == 0
assert ret['exit_long'].sum() == 0
assert not log_has('TA Analysis Launched', caplog)
assert log_has('Skipping TA Analysis for already analyzed candle', caplog)
@pytest.mark.usefixtures("init_persistence")
def test_is_pair_locked(default_conf):
default_conf.update({'strategy': 'StrategyTestV2'})
PairLocks.timeframe = default_conf['timeframe']
PairLocks.use_db = True
strategy = StrategyResolver.load_strategy(default_conf)

View File

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

View File

@ -7,6 +7,7 @@ import pytest
from freqtrade.commands import Arguments
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
@ -123,7 +124,7 @@ def test_parse_args_backtesting_custom() -> None:
'-c', 'test_conf.json',
'--ticker-interval', '1m',
'--strategy-list',
'StrategyTestV2',
CURRENT_TEST_STRATEGY,
'SampleStrategy'
]
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.exceptions import OperationalException
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")
@ -403,7 +404,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
arglist = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
]
args = Arguments(arglist).get_parsed_arg()
@ -440,7 +441,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
arglist = [
'backtesting',
'--config', 'config.json',
'--strategy', 'StrategyTestV2',
'--strategy', CURRENT_TEST_STRATEGY,
'--datadir', '/foo/bar',
'--userdir', "/tmp/freqtrade",
'--ticker-interval', '1m',
@ -497,7 +498,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
'--ticker-interval', '1m',
'--export', 'trades',
'--strategy-list',
'StrategyTestV2',
CURRENT_TEST_STRATEGY,
'TestStrategy'
]