diff --git a/docs/configuration.md b/docs/configuration.md index 9610b5866..147f0b672 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -86,7 +86,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade).
*Defaults to `false`.*
**Datatype:** Boolean | `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade).
*Defaults to `0.5`.*
**Datatype:** Float (as ratio) | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
**Datatype:** Positive Float as ratio. -| `timeframe` | The timeframe (former ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String +| `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
**Datatype:** String | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
**Datatype:** Boolean | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.
*Defaults to `1000`.*
**Datatype:** Float diff --git a/docs/hyperopt.md b/docs/hyperopt.md index f2ec4f875..bab81fb31 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -153,8 +153,8 @@ Checklist on all tasks / possibilities in hyperopt Depending on the space you want to optimize, only some of the below are required: -* define parameters with `space='buy'` - for buy signal optimization -* define parameters with `space='sell'` - for sell signal optimization +* define parameters with `space='buy'` - for entry signal optimization +* define parameters with `space='sell'` - for exit signal optimization !!! Note `populate_indicators` needs to create all indicators any of the spaces may use, otherwise hyperopt will not work. @@ -204,7 +204,7 @@ There you have two different types of indicators: 1. `guards` and 2. `triggers`. Hyper-optimization will, for each epoch round, pick one trigger and possibly multiple guards. -#### Sell optimization +#### Exit signal optimization Similar to the entry-signal above, exit-signals can also be optimized. Place the corresponding settings into the following methods @@ -216,7 +216,7 @@ The configuration and rules are the same than for buy signals. ## Solving a Mystery -Let's say you are curious: should you use MACD crossings or lower Bollinger Bands to trigger your buys. +Let's say you are curious: should you use MACD crossings or lower Bollinger Bands to trigger your buys. And you also wonder should you use RSI or ADX to help with those buy decisions. If you decide to use RSI or ADX, which values should I use for them? @@ -296,7 +296,7 @@ So let's write the buy strategy using these values: if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), - 'buy'] = 1 + 'enter_long'] = 1 return dataframe ``` @@ -376,7 +376,7 @@ class MyAwesomeStrategy(IStrategy): if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), - 'buy'] = 1 + 'enter_long'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -391,7 +391,7 @@ class MyAwesomeStrategy(IStrategy): if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), - 'sell'] = 1 + 'exit_long'] = 1 return dataframe ``` diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index ec91c91a7..b5d0ef8b9 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -89,7 +89,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: (dataframe['rsi'] < 35) & (dataframe['volume'] > 0) ), - ['buy', 'enter_tag']] = (1, 'buy_signal_rsi') + ['enter_long', 'enter_tag']] = (1, 'buy_signal_rsi') return dataframe @@ -117,7 +117,7 @@ def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame (dataframe['rsi'] > 70) & (dataframe['volume'] > 0) ), - ['sell', 'exit_tag']] = (1, 'exit_rsi') + ['exit_long', 'exit_tag']] = (1, 'exit_rsi') return dataframe ``` diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index fb7bea5f3..38c1e7fa5 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -664,7 +664,7 @@ class DigDeeperStrategy(IStrategy): if last_candle['close'] < previous_candle['close']: return None - filled_buys = trade.select_filled_orders('buy') + filled_entries = trade.select_filled_orders(trade.enter_side) count_of_entries = trade.nr_of_successful_entries # Allow up to 3 additional increasingly larger buys (4 in total) # Initial buy is 1x @@ -676,7 +676,7 @@ class DigDeeperStrategy(IStrategy): # Hope you have a deep wallet! try: # This returns first order stake size - stake_amount = filled_buys[0].cost + stake_amount = filled_entries[0].cost # This then calculates current safety order size stake_amount = stake_amount * (1 + (count_of_entries * 0.25)) return stake_amount diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 3a8425b4e..fb598282e 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -26,8 +26,8 @@ This will create a new strategy file from a template, which will be located unde A strategy file contains all the information needed to build a good strategy: - Indicators -- Buy strategy rules -- Sell strategy rules +- Entry strategy rules +- Exit strategy rules - Minimal ROI recommended - Stoploss strongly recommended @@ -82,7 +82,7 @@ As a dataframe is a table, simple python comparisons like the following will not ``` python if dataframe['rsi'] > 30: - dataframe['buy'] = 1 + dataframe['enter_long'] = 1 ``` The above section will fail with `The truth value of a Series is ambiguous. [...]`. @@ -92,7 +92,7 @@ This must instead be written in a pandas-compatible way, so the operation is per ``` python dataframe.loc[ (dataframe['rsi'] > 30) - , 'buy'] = 1 + , 'enter_long'] = 1 ``` With this section, you have a new column in your dataframe, which has `1` assigned whenever RSI is above 30. @@ -199,13 +199,13 @@ If this data is available, indicators will be calculated with this extended time !!! Note If data for the startup period is not available, then the timerange will be adjusted to account for this startup period - so Backtesting would start at 2019-01-01 08:30:00. -### Buy signal rules +### Entry signal rules -Edit the method `populate_buy_trend()` in your strategy file to update your buy strategy. +Edit the method `populate_buy_trend()` in your strategy file to update your entry strategy. It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. -This method will also define a new column, `"buy"`, which needs to contain 1 for buys, and 0 for "no action". +This method will also define a new column, `"enter_long"`, which needs to contain 1 for entries, and 0 for "no action". Sample from `user_data/strategies/sample_strategy.py`: @@ -224,22 +224,50 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard (dataframe['volume'] > 0) # Make sure Volume is not 0 ), - 'buy'] = 1 + ['enter_long', 'enter_tag']] = (1, 'rsi_cross') return dataframe ``` +??? Note "Enter short trades" + Short-entries can be created by setting `enter_short` (corresponds to `enter_long` for long trades). + The `enter_tag` column remains identical. + Short-trades need to be supported by your exchange and market configuration! + + ```python + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30 + (dataframe['tema'] <= dataframe['bb_middleband']) & # Guard + (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['enter_long', 'enter_tag']] = (1, 'rsi_cross') + + dataframe.loc[ + ( + (qtpylib.crossed_below(dataframe['rsi'], 70)) & # Signal: RSI crosses below 70 + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['enter_short', 'enter_tag']] = (1, 'rsi_cross') + + return dataframe + ``` + !!! Note Buying requires sellers to buy from - therefore volume needs to be > 0 (`dataframe['volume'] > 0`) to make sure that the bot does not buy/sell in no-activity periods. -### Sell signal rules +### Exit signal rules Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy. Please note that the sell-signal is only used if `use_sell_signal` is set to true in the configuration. It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. -This method will also define a new column, `"sell"`, which needs to contain 1 for sells, and 0 for "no action". +This method will also define a new column, `"exit_long"`, which needs to contain 1 for sells, and 0 for "no action". Sample from `user_data/strategies/sample_strategy.py`: @@ -258,10 +286,36 @@ def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard (dataframe['volume'] > 0) # Make sure Volume is not 0 ), - 'sell'] = 1 + ['exit_long', 'exit_tag']] = (1, 'rsi_too_high') return dataframe ``` +??? Note "Exit short trades" + Short-exits can be created by setting `exit_short` (corresponds to `exit_long`). + The `exit_tag` column remains identical. + Short-trades need to be supported by your exchange and market configuration! + + ```python + def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70 + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['exit_long', 'exit_tag']] = (1, 'rsi_too_high') + dataframe.loc[ + ( + (qtpylib.crossed_below(dataframe['rsi'], 30)) & # Signal: RSI crosses below 30 + (dataframe['tema'] < dataframe['bb_middleband']) & # Guard + (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + ['exit_short', 'exit_tag']] = (1, 'rsi_too_low') + return dataframe + ``` + ### Minimal ROI This dict defines the minimal Return On Investment (ROI) a trade should reach before selling, independent from the sell signal. @@ -325,7 +379,7 @@ stoploss = -0.10 For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md). -### Timeframe (formerly ticker interval) +### Timeframe This is the set of candles the bot should download and use for the analysis. Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work. @@ -454,7 +508,7 @@ for more information. # Define BTC/STAKE informative pair. Available in populate_indicators and other methods as # 'btc_rsi_1h'. Current stake currency should be specified as {stake} format variable - # instead of hardcoding actual stake currency. Available in populate_indicators and other + # instead of hard-coding actual stake currency. Available in populate_indicators and other # methods as 'btc_usdt_rsi_1h' (when stake currency is USDT). @informative('1h', 'BTC/{stake}') def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -501,7 +555,7 @@ for more information. & (dataframe['volume'] > 0) ), - ['buy', 'enter_tag']] = (1, 'buy_signal_rsi') + ['enter_long', 'enter_tag']] = (1, 'buy_signal_rsi') return dataframe ``` @@ -716,7 +770,7 @@ class SampleStrategy(IStrategy): (dataframe['rsi_1d'] < 30) & # Ensure daily RSI is < 30 (dataframe['volume'] > 0) # Ensure this candle had volume (important for backtesting) ), - 'buy'] = 1 + ['enter_long', 'enter_tag']] = (1, 'rsi_cross') ``` @@ -922,7 +976,7 @@ if self.config['runmode'].value in ('live', 'dry_run'): Sample return value: ETH/BTC had 5 trades, with a total profit of 1.5% (ratio of 0.015). ``` json -{'pair': "ETH/BTC", 'profit': 0.015, 'count': 5} +{"pair": "ETH/BTC", "profit": 0.015, "count": 5} ``` !!! Warning @@ -985,7 +1039,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: ( #>> whatever condition<<< ), - 'buy'] = 1 + ['enter_long', 'enter_tag']] = (1, 'somestring') # Print the Analyzed pair print(f"result for {metadata['pair']}") @@ -1014,7 +1068,12 @@ The following lists some common patterns which should be avoided to prevent frus ### Colliding signals -When buy and sell signals collide (both `'buy'` and `'sell'` are 1), freqtrade will do nothing and ignore the entry (buy) signal. This will avoid trades that buy, and sell immediately. Obviously, this can potentially lead to missed entries. +When conflicting signals collide (e.g. both `'enter_long'` and `'exit_long'` are 1), freqtrade will do nothing and ignore the entry signal. This will avoid trades that buy, and sell immediately. Obviously, this can potentially lead to missed entries. + +The following rules apply, and entry signals will be ignored if more than one of the 3 signals is set: + +- `enter_long` -> `exit_long`, `exit_short` +- `enter_short` -> `exit_short`, `enter_long` ## Further strategy ideas