From f0a25ea4858f66db5c440edefdc04ba815c28a4b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 9 Apr 2024 20:32:15 +0200 Subject: [PATCH 01/14] feat: Add __all__ export to strategy's init file --- freqtrade/strategy/__init__.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index bb21100c4..0a492a29e 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -1,4 +1,6 @@ # flake8: noqa: F401 +from typing import Dict, List, Optional, Union + from freqtrade.exchange import ( timeframe_to_minutes, timeframe_to_msecs, @@ -6,6 +8,7 @@ from freqtrade.exchange import ( timeframe_to_prev_date, timeframe_to_seconds, ) +from freqtrade.persistence import Order, PairLocks, Trade from freqtrade.strategy.informative_decorator import informative from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.parameters import ( @@ -20,3 +23,30 @@ from freqtrade.strategy.strategy_helper import ( stoploss_from_absolute, stoploss_from_open, ) + + +__all__ = [ + "timeframe_to_minutes", + "timeframe_to_next_date", + "timeframe_to_prev_date", + "informative", + "IStrategy", + "Trade", + "Order", + "PairLocks", + # Parameters + "BooleanParameter", + "CategoricalParameter", + "DecimalParameter", + "IntParameter", + "RealParameter", + # Strategy helper functions + "merge_informative_pair", + "stoploss_from_absolute", + "stoploss_from_open", + # Typings + "List", + "Optional", + "Union", + "Dict", +] From 27a4a502d7893924051c0ce7a3cea9275a38aa64 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:24:50 +0200 Subject: [PATCH 02/14] docs: Add section explaining strategy imports --- docs/includes/strategy-imports.md | 41 +++++++++++++++++++++++++++++++ docs/strategy-callbacks.md | 2 ++ docs/strategy-customization.md | 2 ++ 3 files changed, 45 insertions(+) create mode 100644 docs/includes/strategy-imports.md diff --git a/docs/includes/strategy-imports.md b/docs/includes/strategy-imports.md new file mode 100644 index 000000000..818af3a52 --- /dev/null +++ b/docs/includes/strategy-imports.md @@ -0,0 +1,41 @@ +## Imports necessary for a strategy + +When creating a strategy, you will need to import the necessary modules and classes. The following imports are required for a strategy: + +By default, we recommend the following imports as a base line for your strategy: +This will cover all imports necessary for freqtrade functions to work. +Obviously you can add more imports as needed for your strategy. + +``` python +# flake8: noqa: F401 +# isort: skip_file +# --- Do not remove these imports --- +import numpy as np +import pandas as pd +from datetime import datetime +from pandas import DataFrame +from typing import Optional, Union + +from freqtrade.strategy import ( + IStrategy, + Trade, + Order, + PairLocks, + informative, # @informative decorator + # Hyperopt Parameters + BooleanParameter, + CategoricalParameter, + DecimalParameter, + IntParameter, + RealParameter, + # Strategy helper functions + merge_informative_pair, + stoploss_from_absolute, + stoploss_from_open, +) + +# -------------------------------- +# Add your lib to import here +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +``` diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 74eef53c1..a090749cc 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -24,6 +24,8 @@ Currently available callbacks: !!! Tip "Callback calling sequence" You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic) +--8<-- "includes/strategy-imports.md" + ## Bot start A simple callback which is called once when the strategy is loaded. diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 98d7ae9d2..a8b9dcb4c 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -407,6 +407,8 @@ Currently this is `pair`, which can be accessed using `metadata['pair']` - and w The Metadata-dict should not be modified and does not persist information across multiple calls. Instead, have a look at the [Storing information](strategy-advanced.md#storing-information-persistent) section. +--8<-- "includes/strategy-imports.md" + ## Strategy file loading By default, freqtrade will attempt to load strategies from all `.py` files within `user_data/strategies`. From 6c131b56486f84b8e006ed9d6b72d02375940935 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:25:47 +0200 Subject: [PATCH 03/14] chore: add comment to better explain imports --- freqtrade/strategy/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index 0a492a29e..6e8fb5da8 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -25,6 +25,7 @@ from freqtrade.strategy.strategy_helper import ( ) +# Imports to be used for `from freqtrade.strategy import *` __all__ = [ "timeframe_to_minutes", "timeframe_to_next_date", From d6f96b2c53870e04c525193b5195e3a7a1ae29fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:26:21 +0200 Subject: [PATCH 04/14] chore: remove typing imports These shouldn't be star imported, but should be explicitly imported. --- freqtrade/strategy/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index 6e8fb5da8..d5fb9b7ae 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -1,5 +1,4 @@ # flake8: noqa: F401 -from typing import Dict, List, Optional, Union from freqtrade.exchange import ( timeframe_to_minutes, @@ -45,9 +44,4 @@ __all__ = [ "merge_informative_pair", "stoploss_from_absolute", "stoploss_from_open", - # Typings - "List", - "Optional", - "Union", - "Dict", ] From 5bc8b02b0febf2be0c97a57536f4c93fbf7167b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:28:19 +0200 Subject: [PATCH 05/14] feat: Update imports for sample strategy --- docs/includes/strategy-imports.md | 2 +- freqtrade/templates/sample_strategy.py | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/includes/strategy-imports.md b/docs/includes/strategy-imports.md index 818af3a52..b3d75a5e3 100644 --- a/docs/includes/strategy-imports.md +++ b/docs/includes/strategy-imports.md @@ -14,7 +14,7 @@ import numpy as np import pandas as pd from datetime import datetime from pandas import DataFrame -from typing import Optional, Union +from typing import Dict, Optional, Union from freqtrade.strategy import ( IStrategy, diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 033c0d24e..950e1f225 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -2,17 +2,28 @@ # flake8: noqa: F401 # isort: skip_file # --- Do not remove these libs --- -import numpy as np # noqa -import pandas as pd # noqa +import numpy as np +import pandas as pd +from datetime import datetime from pandas import DataFrame -from typing import Optional, Union +from typing import Dict, Optional, Union from freqtrade.strategy import ( + IStrategy, + Trade, + Order, + PairLocks, + informative, # @informative decorator + # Hyperopt Parameters BooleanParameter, CategoricalParameter, DecimalParameter, - IStrategy, IntParameter, + RealParameter, + # Strategy helper functions + merge_informative_pair, + stoploss_from_absolute, + stoploss_from_open, ) # -------------------------------- From e7b57d8dee4230ae03ec89b8fce34cffd48eba85 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:28:56 +0200 Subject: [PATCH 06/14] chore: Update import for qtpylib to technical --- docs/includes/strategy-imports.md | 2 +- freqtrade/templates/sample_strategy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/includes/strategy-imports.md b/docs/includes/strategy-imports.md index b3d75a5e3..14dde6c88 100644 --- a/docs/includes/strategy-imports.md +++ b/docs/includes/strategy-imports.md @@ -37,5 +37,5 @@ from freqtrade.strategy import ( # -------------------------------- # Add your lib to import here import talib.abstract as ta -import freqtrade.vendor.qtpylib.indicators as qtpylib +from technical import qtpylib ``` diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 950e1f225..3a9235bc2 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -29,7 +29,7 @@ from freqtrade.strategy import ( # -------------------------------- # Add your lib to import here import talib.abstract as ta -import freqtrade.vendor.qtpylib.indicators as qtpylib +from technical import qtpylib # This class is a sample. Feel free to customize it. From c2ac70ff10c3e6bf783f39e0571225bb058cb876 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:30:06 +0200 Subject: [PATCH 07/14] feat: update base_strategy to include all imports --- freqtrade/templates/base_strategy.py.j2 | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index a4e0a2b24..5c0e5e177 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -4,12 +4,27 @@ # --- Do not remove these libs --- import numpy as np import pandas as pd -from pandas import DataFrame from datetime import datetime -from typing import Optional, Union +from pandas import DataFrame +from typing import Dict, Optional, Union -from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, - IntParameter, IStrategy, merge_informative_pair) +from freqtrade.strategy import ( + IStrategy, + Trade, + Order, + PairLocks, + informative, # @informative decorator + # Hyperopt Parameters + BooleanParameter, + CategoricalParameter, + DecimalParameter, + IntParameter, + RealParameter, + # Strategy helper functions + merge_informative_pair, + stoploss_from_absolute, + stoploss_from_open, +) # -------------------------------- # Add your lib to import here From b3a042a63b70b4ba1a32ab10e27ecc5d6191a1d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:32:38 +0200 Subject: [PATCH 08/14] feat: don't use commented typehints Imports are correct now --- .../strategy_methods_advanced.j2 | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 index 1783e818c..07b72610a 100644 --- a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 @@ -13,9 +13,9 @@ def bot_loop_start(self, current_time: datetime, **kwargs) -> None: """ pass -def custom_entry_price(self, pair: str, trade: Optional['Trade'], - current_time: 'datetime', proposed_rate: float, - entry_tag: 'Optional[str]', side: str, **kwargs) -> float: +def custom_entry_price(self, pair: str, trade: Optional[Trade], + current_time: datetime, proposed_rate: float, + entry_tag: Optional[str], side: str, **kwargs) -> float: """ Custom entry price logic, returning the new entry price. @@ -33,7 +33,7 @@ def custom_entry_price(self, pair: str, trade: Optional['Trade'], """ return proposed_rate -def adjust_entry_price(self, trade: 'Trade', order: 'Optional[Order]', pair: str, +def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str, current_time: datetime, proposed_rate: float, current_order_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ @@ -61,8 +61,8 @@ def adjust_entry_price(self, trade: 'Trade', order: 'Optional[Order]', pair: str """ return current_order_rate -def custom_exit_price(self, pair: str, trade: 'Trade', - current_time: 'datetime', proposed_rate: float, +def custom_exit_price(self, pair: str, trade: Trade, + current_time: datetime, proposed_rate: float, current_profit: float, exit_tag: Optional[str], **kwargs) -> float: """ Custom exit price logic, returning the new exit price. @@ -104,7 +104,7 @@ def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: f use_custom_stoploss = True -def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, +def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> float: """ Custom stoploss logic, returning the new distance relative to current_rate (as ratio). @@ -126,8 +126,8 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', c :return float: New stoploss value, relative to the current_rate """ -def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, - current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]': +def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, + current_profit: float, **kwargs) -> Optional[Union[str, bool]]: """ Custom exit signal logic indicating that specified position should be sold. Returning a string or True from this method is equal to setting sell signal on a candle at specified @@ -177,9 +177,9 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f """ return True -def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float, +def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, - current_time: 'datetime', **kwargs) -> bool: + current_time: datetime, **kwargs) -> bool: """ Called right before placing a regular exit order. Timing for this function is critical, so avoid doing heavy computations or @@ -206,7 +206,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: """ return True -def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', +def check_entry_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ Check entry timeout function callback. @@ -228,7 +228,7 @@ def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', """ return False -def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', +def check_exit_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ Check exit timeout function callback. @@ -250,7 +250,7 @@ def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', """ return False -def adjust_trade_position(self, trade: 'Trade', current_time: datetime, +def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: Optional[float], max_stake: float, current_entry_rate: float, current_exit_rate: float, @@ -302,7 +302,7 @@ def leverage(self, pair: str, current_time: datetime, current_rate: float, return 1.0 -def order_filled(self, pair: str, trade: 'Trade', order: 'Order', +def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None: """ Called right after an order fills. From 0995164110d110eafc758056007342d18d83b663 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:36:21 +0200 Subject: [PATCH 09/14] feat: improve formatting of generated strategy --- freqtrade/templates/base_strategy.py.j2 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 5c0e5e177..2e8250dd2 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -93,8 +93,8 @@ class {{ strategy }}(IStrategy): buy_rsi = IntParameter(10, 40, default=30, space="buy") sell_rsi = IntParameter(60, 90, default=70, space="sell") - {{ attributes | indent(4) }} - {{ plot_config | indent(4) }} + {{- attributes | indent(4) }} + {{- plot_config | indent(4) }} def informative_pairs(self): """ @@ -120,7 +120,7 @@ class {{ strategy }}(IStrategy): :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ - {{ indicators | indent(8) }} + {{- indicators | indent(8) }} return dataframe @@ -172,4 +172,4 @@ class {{ strategy }}(IStrategy): 'exit_short'] = 1 """ return dataframe - {{ additional_methods | indent(4) }} + {{- additional_methods | indent(4) }} From 9408e858cd4c5ba54fcffb35907695a0224f87c7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Aug 2024 16:43:46 +0200 Subject: [PATCH 10/14] chore: use aligned quoting strategy for templtae --- freqtrade/templates/base_strategy.py.j2 | 10 +- .../strategy_subtemplates/buy_trend_full.j2 | 6 +- .../buy_trend_minimal.j2 | 2 +- .../strategy_subtemplates/indicators_full.j2 | 142 +++++++++--------- .../indicators_minimal.j2 | 10 +- .../strategy_subtemplates/plot_config_full.j2 | 14 +- .../strategy_subtemplates/sell_trend_full.j2 | 6 +- .../sell_trend_minimal.j2 | 2 +- .../strategy_attributes_full.j2 | 12 +- 9 files changed, 102 insertions(+), 102 deletions(-) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 2e8250dd2..a61093ebd 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -55,7 +55,7 @@ class {{ strategy }}(IStrategy): INTERFACE_VERSION = 3 # Optimal timeframe for the strategy. - timeframe = '5m' + timeframe = "5m" # Can this strategy go short? can_short: bool = False @@ -134,9 +134,9 @@ class {{ strategy }}(IStrategy): dataframe.loc[ ( {{ buy_trend | indent(16) }} - (dataframe['volume'] > 0) # Make sure Volume is not 0 + (dataframe["volume"] > 0) # Make sure Volume is not 0 ), - 'enter_long'] = 1 + "enter_long"] = 1 # Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info) """ dataframe.loc[ @@ -159,9 +159,9 @@ class {{ strategy }}(IStrategy): dataframe.loc[ ( {{ sell_trend | indent(16) }} - (dataframe['volume'] > 0) # Make sure Volume is not 0 + (dataframe["volume"] > 0) # Make sure Volume is not 0 ), - 'exit_long'] = 1 + "exit_long"] = 1 # Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info) """ dataframe.loc[ diff --git a/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2 b/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2 index aac8325a7..7a50fd4dc 100644 --- a/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2 +++ b/freqtrade/templates/strategy_subtemplates/buy_trend_full.j2 @@ -1,3 +1,3 @@ -(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi -(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle -(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising +(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi +(dataframe["tema"] <= dataframe["bb_middleband"]) & # Guard: tema below BB middle +(dataframe["tema"] > dataframe["tema"].shift(1)) & # Guard: tema is raising diff --git a/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2 b/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2 index e89d3779e..bcecacc3c 100644 --- a/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2 +++ b/freqtrade/templates/strategy_subtemplates/buy_trend_minimal.j2 @@ -1 +1 @@ -(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi +(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi diff --git a/freqtrade/templates/strategy_subtemplates/indicators_full.j2 b/freqtrade/templates/strategy_subtemplates/indicators_full.j2 index a497b47cb..e4c4daac4 100644 --- a/freqtrade/templates/strategy_subtemplates/indicators_full.j2 +++ b/freqtrade/templates/strategy_subtemplates/indicators_full.j2 @@ -3,24 +3,24 @@ # ------------------------------------ # ADX -dataframe['adx'] = ta.ADX(dataframe) +dataframe["adx"] = ta.ADX(dataframe) # # Plus Directional Indicator / Movement -# dataframe['plus_dm'] = ta.PLUS_DM(dataframe) -# dataframe['plus_di'] = ta.PLUS_DI(dataframe) +# dataframe["plus_dm"] = ta.PLUS_DM(dataframe) +# dataframe["plus_di"] = ta.PLUS_DI(dataframe) # # Minus Directional Indicator / Movement -# dataframe['minus_dm'] = ta.MINUS_DM(dataframe) -# dataframe['minus_di'] = ta.MINUS_DI(dataframe) +# dataframe["minus_dm"] = ta.MINUS_DM(dataframe) +# dataframe["minus_di"] = ta.MINUS_DI(dataframe) # # Aroon, Aroon Oscillator # aroon = ta.AROON(dataframe) -# dataframe['aroonup'] = aroon['aroonup'] -# dataframe['aroondown'] = aroon['aroondown'] -# dataframe['aroonosc'] = ta.AROONOSC(dataframe) +# dataframe["aroonup"] = aroon["aroonup"] +# dataframe["aroondown"] = aroon["aroondown"] +# dataframe["aroonosc"] = ta.AROONOSC(dataframe) # # Awesome Oscillator -# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) +# dataframe["ao"] = qtpylib.awesome_oscillator(dataframe) # # Keltner Channel # keltner = qtpylib.keltner_channel(dataframe) @@ -36,58 +36,58 @@ dataframe['adx'] = ta.ADX(dataframe) # ) # # Ultimate Oscillator -# dataframe['uo'] = ta.ULTOSC(dataframe) +# dataframe["uo"] = ta.ULTOSC(dataframe) # # Commodity Channel Index: values [Oversold:-100, Overbought:100] -# dataframe['cci'] = ta.CCI(dataframe) +# dataframe["cci"] = ta.CCI(dataframe) # RSI -dataframe['rsi'] = ta.RSI(dataframe) +dataframe["rsi"] = ta.RSI(dataframe) # # Inverse Fisher transform on RSI: values [-1.0, 1.0] (https://goo.gl/2JGGoy) -# rsi = 0.1 * (dataframe['rsi'] - 50) -# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) +# rsi = 0.1 * (dataframe["rsi"] - 50) +# dataframe["fisher_rsi"] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) # # Inverse Fisher transform on RSI normalized: values [0.0, 100.0] (https://goo.gl/2JGGoy) -# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) +# dataframe["fisher_rsi_norma"] = 50 * (dataframe["fisher_rsi"] + 1) # # Stochastic Slow # stoch = ta.STOCH(dataframe) -# dataframe['slowd'] = stoch['slowd'] -# dataframe['slowk'] = stoch['slowk'] +# dataframe["slowd"] = stoch["slowd"] +# dataframe["slowk"] = stoch["slowk"] # Stochastic Fast stoch_fast = ta.STOCHF(dataframe) -dataframe['fastd'] = stoch_fast['fastd'] -dataframe['fastk'] = stoch_fast['fastk'] +dataframe["fastd"] = stoch_fast["fastd"] +dataframe["fastk"] = stoch_fast["fastk"] # # Stochastic RSI # Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this. # STOCHRSI is NOT aligned with tradingview, which may result in non-expected results. # stoch_rsi = ta.STOCHRSI(dataframe) -# dataframe['fastd_rsi'] = stoch_rsi['fastd'] -# dataframe['fastk_rsi'] = stoch_rsi['fastk'] +# dataframe["fastd_rsi"] = stoch_rsi["fastd"] +# dataframe["fastk_rsi"] = stoch_rsi["fastk"] # MACD macd = ta.MACD(dataframe) -dataframe['macd'] = macd['macd'] -dataframe['macdsignal'] = macd['macdsignal'] -dataframe['macdhist'] = macd['macdhist'] +dataframe["macd"] = macd["macd"] +dataframe["macdsignal"] = macd["macdsignal"] +dataframe["macdhist"] = macd["macdhist"] # MFI -dataframe['mfi'] = ta.MFI(dataframe) +dataframe["mfi"] = ta.MFI(dataframe) # # ROC -# dataframe['roc'] = ta.ROC(dataframe) +# dataframe["roc"] = ta.ROC(dataframe) # Overlap Studies # ------------------------------------ # 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'] +dataframe["bb_lowerband"] = bollinger["lower"] +dataframe["bb_middleband"] = bollinger["mid"] +dataframe["bb_upperband"] = bollinger["upper"] dataframe["bb_percent"] = ( (dataframe["close"] - dataframe["bb_lowerband"]) / (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) @@ -112,95 +112,95 @@ dataframe["bb_width"] = ( # ) # # EMA - Exponential Moving Average -# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) -# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) -# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) -# dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21) -# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) -# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) +# dataframe["ema3"] = ta.EMA(dataframe, timeperiod=3) +# dataframe["ema5"] = ta.EMA(dataframe, timeperiod=5) +# dataframe["ema10"] = ta.EMA(dataframe, timeperiod=10) +# dataframe["ema21"] = ta.EMA(dataframe, timeperiod=21) +# dataframe["ema50"] = ta.EMA(dataframe, timeperiod=50) +# dataframe["ema100"] = ta.EMA(dataframe, timeperiod=100) # # SMA - Simple Moving Average -# dataframe['sma3'] = ta.SMA(dataframe, timeperiod=3) -# dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5) -# dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10) -# dataframe['sma21'] = ta.SMA(dataframe, timeperiod=21) -# dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50) -# dataframe['sma100'] = ta.SMA(dataframe, timeperiod=100) +# dataframe["sma3"] = ta.SMA(dataframe, timeperiod=3) +# dataframe["sma5"] = ta.SMA(dataframe, timeperiod=5) +# dataframe["sma10"] = ta.SMA(dataframe, timeperiod=10) +# dataframe["sma21"] = ta.SMA(dataframe, timeperiod=21) +# dataframe["sma50"] = ta.SMA(dataframe, timeperiod=50) +# dataframe["sma100"] = ta.SMA(dataframe, timeperiod=100) # Parabolic SAR -dataframe['sar'] = ta.SAR(dataframe) +dataframe["sar"] = ta.SAR(dataframe) # TEMA - Triple Exponential Moving Average -dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) +dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9) # Cycle Indicator # ------------------------------------ # Hilbert Transform Indicator - SineWave hilbert = ta.HT_SINE(dataframe) -dataframe['htsine'] = hilbert['sine'] -dataframe['htleadsine'] = hilbert['leadsine'] +dataframe["htsine"] = hilbert["sine"] +dataframe["htleadsine"] = hilbert["leadsine"] # Pattern Recognition - Bullish candlestick patterns # ------------------------------------ # # Hammer: values [0, 100] -# dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) +# dataframe["CDLHAMMER"] = ta.CDLHAMMER(dataframe) # # Inverted Hammer: values [0, 100] -# dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) +# dataframe["CDLINVERTEDHAMMER"] = ta.CDLINVERTEDHAMMER(dataframe) # # Dragonfly Doji: values [0, 100] -# dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) +# dataframe["CDLDRAGONFLYDOJI"] = ta.CDLDRAGONFLYDOJI(dataframe) # # Piercing Line: values [0, 100] -# dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] +# dataframe["CDLPIERCING"] = ta.CDLPIERCING(dataframe) # values [0, 100] # # Morningstar: values [0, 100] -# dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] +# dataframe["CDLMORNINGSTAR"] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] # # Three White Soldiers: values [0, 100] -# dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] +# dataframe["CDL3WHITESOLDIERS"] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] # Pattern Recognition - Bearish candlestick patterns # ------------------------------------ # # Hanging Man: values [0, 100] -# dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) +# dataframe["CDLHANGINGMAN"] = ta.CDLHANGINGMAN(dataframe) # # Shooting Star: values [0, 100] -# dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) +# dataframe["CDLSHOOTINGSTAR"] = ta.CDLSHOOTINGSTAR(dataframe) # # Gravestone Doji: values [0, 100] -# dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) +# dataframe["CDLGRAVESTONEDOJI"] = ta.CDLGRAVESTONEDOJI(dataframe) # # Dark Cloud Cover: values [0, 100] -# dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) +# dataframe["CDLDARKCLOUDCOVER"] = ta.CDLDARKCLOUDCOVER(dataframe) # # Evening Doji Star: values [0, 100] -# dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) +# dataframe["CDLEVENINGDOJISTAR"] = ta.CDLEVENINGDOJISTAR(dataframe) # # Evening Star: values [0, 100] -# dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) +# dataframe["CDLEVENINGSTAR"] = ta.CDLEVENINGSTAR(dataframe) # Pattern Recognition - Bullish/Bearish candlestick patterns # ------------------------------------ # # Three Line Strike: values [0, -100, 100] -# dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) +# dataframe["CDL3LINESTRIKE"] = ta.CDL3LINESTRIKE(dataframe) # # Spinning Top: values [0, -100, 100] -# dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] +# dataframe["CDLSPINNINGTOP"] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] # # Engulfing: values [0, -100, 100] -# dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] +# dataframe["CDLENGULFING"] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] # # Harami: values [0, -100, 100] -# dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] +# dataframe["CDLHARAMI"] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] # # Three Outside Up/Down: values [0, -100, 100] -# dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] +# dataframe["CDL3OUTSIDE"] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] # # Three Inside Up/Down: values [0, -100, 100] -# dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] +# dataframe["CDL3INSIDE"] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] # # Chart type # # ------------------------------------ # # Heikin Ashi Strategy # heikinashi = qtpylib.heikinashi(dataframe) -# dataframe['ha_open'] = heikinashi['open'] -# dataframe['ha_close'] = heikinashi['close'] -# dataframe['ha_high'] = heikinashi['high'] -# dataframe['ha_low'] = heikinashi['low'] +# dataframe["ha_open"] = heikinashi["open"] +# dataframe["ha_close"] = heikinashi["close"] +# dataframe["ha_high"] = heikinashi["high"] +# dataframe["ha_low"] = heikinashi["low"] # Retrieve best bid and best ask from the orderbook # ------------------------------------ """ # first check if dataprovider is available if self.dp: - if self.dp.runmode.value in ('live', 'dry_run'): - ob = self.dp.orderbook(metadata['pair'], 1) - dataframe['best_bid'] = ob['bids'][0][0] - dataframe['best_ask'] = ob['asks'][0][0] + if self.dp.runmode.value in ("live", "dry_run"): + ob = self.dp.orderbook(metadata["pair"], 1) + dataframe["best_bid"] = ob["bids"][0][0] + dataframe["best_ask"] = ob["asks"][0][0] """ diff --git a/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2 b/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2 index 90f4f4d4a..1594a8988 100644 --- a/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2 +++ b/freqtrade/templates/strategy_subtemplates/indicators_minimal.j2 @@ -3,15 +3,15 @@ # ------------------------------------ # RSI -dataframe['rsi'] = ta.RSI(dataframe) +dataframe["rsi"] = ta.RSI(dataframe) # Retrieve best bid and best ask from the orderbook # ------------------------------------ """ # first check if dataprovider is available if self.dp: - if self.dp.runmode.value in ('live', 'dry_run'): - ob = self.dp.orderbook(metadata['pair'], 1) - dataframe['best_bid'] = ob['bids'][0][0] - dataframe['best_ask'] = ob['asks'][0][0] + if self.dp.runmode.value in ("live", "dry_run"): + ob = self.dp.orderbook(metadata["pair"], 1) + dataframe["best_bid"] = ob["bids"][0][0] + dataframe["best_ask"] = ob["asks"][0][0] """ diff --git a/freqtrade/templates/strategy_subtemplates/plot_config_full.j2 b/freqtrade/templates/strategy_subtemplates/plot_config_full.j2 index e3f9e7ca0..08eb3c29f 100644 --- a/freqtrade/templates/strategy_subtemplates/plot_config_full.j2 +++ b/freqtrade/templates/strategy_subtemplates/plot_config_full.j2 @@ -3,18 +3,18 @@ def plot_config(self): return { # Main plot indicators (Moving averages, ...) - 'main_plot': { - 'tema': {}, - 'sar': {'color': 'white'}, + "main_plot": { + "tema": {}, + "sar": {"color": "white"}, }, - 'subplots': { + "subplots": { # Subplots - each dict defines one additional plot "MACD": { - 'macd': {'color': 'blue'}, - 'macdsignal': {'color': 'orange'}, + "macd": {"color": "blue"}, + "macdsignal": {"color": "orange"}, }, "RSI": { - 'rsi': {'color': 'red'}, + "rsi": {"color": "red"}, } } } diff --git a/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2 b/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2 index 3068d8d57..08cb68cd1 100644 --- a/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2 +++ b/freqtrade/templates/strategy_subtemplates/sell_trend_full.j2 @@ -1,3 +1,3 @@ -(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi -(dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle -(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling +(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi +(dataframe["tema"] > dataframe["bb_middleband"]) & # Guard: tema above BB middle +(dataframe["tema"] < dataframe["tema"].shift(1)) & # Guard: tema is falling diff --git a/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2 b/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2 index 5dabc5910..821b547c3 100644 --- a/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2 +++ b/freqtrade/templates/strategy_subtemplates/sell_trend_minimal.j2 @@ -1 +1 @@ -(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi +(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi diff --git a/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2 b/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2 index 86445510d..5ae361996 100644 --- a/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2 +++ b/freqtrade/templates/strategy_subtemplates/strategy_attributes_full.j2 @@ -1,13 +1,13 @@ # Optional order type mapping. order_types = { - 'entry': 'limit', - 'exit': 'limit', - 'stoploss': 'market', - 'stoploss_on_exchange': False + "entry": "limit", + "exit": "limit", + "stoploss": "market", + "stoploss_on_exchange": False } # Optional order time in force. order_time_in_force = { - 'entry': 'GTC', - 'exit': 'GTC' + "entry": "GTC", + "exit": "GTC" } From b1ae09c00350bec103f9ba1a8ae6d0cfc3081bfb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 08:37:34 +0200 Subject: [PATCH 11/14] docs: remove callback examples imports --- docs/strategy-callbacks.md | 106 +++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 56 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index a090749cc..8bb3753de 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -198,9 +198,7 @@ Of course, many more things are possible, and all examples can be combined at wi To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method: ``` python -# additional imports required -from datetime import datetime -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): @@ -208,7 +206,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: """ @@ -238,8 +236,7 @@ class AwesomeStrategy(IStrategy): Use the initial stoploss for the first 60 minutes, after this change to 10% trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss. ``` python -from datetime import datetime, timedelta -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): @@ -247,7 +244,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -265,8 +262,7 @@ Use the initial stoploss for the first 60 minutes, after this change to 10% trai If an additional order fills, set stoploss to -10% below the new `open_rate` ([Averaged across all entries](#position-adjust-calculations)). ``` python -from datetime import datetime, timedelta -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): @@ -274,7 +270,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -295,8 +291,7 @@ Use a different stoploss depending on the pair. In this example, we'll trail the highest price with 10% trailing stoploss for `ETH/BTC` and `XRP/BTC`, with 5% trailing stoploss for `LTC/BTC` and with 15% for all other pairs. ``` python -from datetime import datetime -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): @@ -304,7 +299,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -322,8 +317,7 @@ Use the initial stoploss until the profit is above 4%, then use a trailing stopl Please note that the stoploss can only increase, values lower than the current stoploss are ignored. ``` python -from datetime import datetime, timedelta -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): @@ -331,7 +325,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -355,9 +349,7 @@ Instead of continuously trailing behind the current price, this example sets fix * Once profit is > 40% - set stoploss to 25% above open price. ``` python -from datetime import datetime -from freqtrade.persistence import Trade -from freqtrade.strategy import stoploss_from_open +# Default imports class AwesomeStrategy(IStrategy): @@ -365,7 +357,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -386,6 +378,8 @@ class AwesomeStrategy(IStrategy): Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss. ``` python +# Default imports + class AwesomeStrategy(IStrategy): def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -394,7 +388,7 @@ class AwesomeStrategy(IStrategy): use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -431,10 +425,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela ``` python - - from datetime import datetime - from freqtrade.persistence import Trade - from freqtrade.strategy import IStrategy, stoploss_from_open + # Default imports class AwesomeStrategy(IStrategy): @@ -442,7 +433,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela use_custom_stoploss = True - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: @@ -475,10 +466,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab For futures, we need to adjust the direction (up or down), as well as adjust for leverage, since the [`custom_stoploss`](strategy-callbacks.md#custom-stoploss) callback returns the ["risk for this trade"](stoploss.md#stoploss-and-leverage) - not the relative price movement. ``` python - - from datetime import datetime - from freqtrade.persistence import Trade - from freqtrade.strategy import IStrategy, stoploss_from_absolute, timeframe_to_prev_date + # Default imports class AwesomeStrategy(IStrategy): @@ -488,7 +476,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) return dataframe - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) @@ -502,7 +490,6 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab ``` - --- ## Custom order price rules @@ -522,19 +509,18 @@ Each of these methods are called right before placing an order on the exchange. ### Custom order entry and exit price example ``` python -from datetime import datetime, timedelta, timezone -from freqtrade.persistence import Trade +# Default imports class AwesomeStrategy(IStrategy): # ... populate_* methods - def custom_entry_price(self, pair: str, trade: Optional['Trade'], current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) - new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1] + new_entryprice = dataframe["bollinger_10_lowerband"].iat[-1] return new_entryprice @@ -544,7 +530,7 @@ class AwesomeStrategy(IStrategy): dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) - new_exitprice = dataframe['bollinger_10_upperband'].iat[-1] + new_exitprice = dataframe["bollinger_10_upperband"].iat[-1] return new_exitprice @@ -581,8 +567,7 @@ It applies a tight timeout for higher priced assets, while allowing more time to The function must return either `True` (cancel order) or `False` (keep order alive). ``` python -from datetime import datetime, timedelta -from freqtrade.persistence import Trade, Order + # Default imports class AwesomeStrategy(IStrategy): @@ -594,7 +579,7 @@ class AwesomeStrategy(IStrategy): 'exit': 60 * 25 } - def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', + def check_entry_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -605,7 +590,7 @@ class AwesomeStrategy(IStrategy): return False - def check_exit_timeout(self, pair: str, trade: Trade, order: 'Order', + def check_exit_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -622,8 +607,7 @@ class AwesomeStrategy(IStrategy): ### Custom order timeout example (using additional data) ``` python -from datetime import datetime -from freqtrade.persistence import Trade, Order + # Default imports class AwesomeStrategy(IStrategy): @@ -631,24 +615,24 @@ class AwesomeStrategy(IStrategy): # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. unfilledtimeout = { - 'entry': 60 * 25, - 'exit': 60 * 25 + "entry": 60 * 25, + "exit": 60 * 25 } - def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', + def check_entry_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) - current_price = ob['bids'][0][0] + current_price = ob["bids"][0][0] # Cancel buy order if price is more than 2% above the order. if current_price > order.price * 1.02: return True return False - def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', + def check_exit_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) - current_price = ob['asks'][0][0] + current_price = ob["asks"][0][0] # Cancel sell order if price is more than 2% below the order. if current_price < order.price * 0.98: return True @@ -667,6 +651,8 @@ This are the last methods that will be called before an order is placed. `confirm_trade_entry()` can be used to abort a trade entry at the latest second (maybe because the price is not what we expect). ``` python +# Default imports + class AwesomeStrategy(IStrategy): # ... populate_* methods @@ -713,8 +699,7 @@ The exit-reasons (if applicable) will be in the following sequence: * `trailing_stop_loss` ``` python -from freqtrade.persistence import Trade - +# Default imports class AwesomeStrategy(IStrategy): @@ -747,7 +732,7 @@ class AwesomeStrategy(IStrategy): :return bool: When True, then the exit-order is placed on the exchange. False aborts the process """ - if exit_reason == 'force_exit' and trade.calc_profit_ratio(rate) < 0: + if exit_reason == "force_exit" and trade.calc_profit_ratio(rate) < 0: # Reject force-sells with negative profit # This is just a sample, please adjust to your needs # (this does not necessarily make sense, assuming you know when you're force-selling) @@ -813,6 +798,7 @@ Returning a value more than the above (so remaining stake_amount would become ne Trades with long duration and 10s or even 100ds of position adjustments are therefore not recommended, and should be closed at regular intervals to not affect performance. ``` python +# Default imports from freqtrade.persistence import Trade from typing import Optional, Tuple, Union @@ -953,8 +939,7 @@ If the cancellation of the original order fails, then the order will not be repl Entry Orders that are cancelled via the above methods will not have this callback called. Be sure to update timeout values to match your expectations. ```python -from freqtrade.persistence import Trade -from datetime import timedelta, datetime +# Default imports class AwesomeStrategy(IStrategy): @@ -985,7 +970,12 @@ class AwesomeStrategy(IStrategy): """ # Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair. - if pair == 'BTC/USDT' and entry_tag == 'long_sma200' and side == 'long' and (current_time - timedelta(minutes=10)) > trade.open_date_utc: + if ( + pair == "BTC/USDT" + and entry_tag == "long_sma200" + and side == "long" + and (current_time - timedelta(minutes=10)) > trade.open_date_utc + ): # just cancel the order if it has been filled more than half of the amount if order.filled > order.remaining: return None @@ -993,7 +983,7 @@ class AwesomeStrategy(IStrategy): dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) current_candle = dataframe.iloc[-1].squeeze() # desired price - return current_candle['sma_200'] + return current_candle["sma_200"] # default: maintain existing order return current_order_rate ``` @@ -1008,6 +998,8 @@ 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 +# Default imports + class AwesomeStrategy(IStrategy): def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, @@ -1038,6 +1030,8 @@ It will be called independent of the order type (entry, exit, stoploss or positi Assuming that your strategy needs to store the high value of the candle at trade entry, this is possible with this callback as the following example show. ``` python +# Default imports + class AwesomeStrategy(IStrategy): def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None: """ From 768b4e5e2bf89a701b0632eedf5cb2f2eaae940c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 08:38:11 +0200 Subject: [PATCH 12/14] chore: Update formatting of default export sequence --- freqtrade/strategy/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index d5fb9b7ae..e99473b4e 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -1,5 +1,4 @@ # flake8: noqa: F401 - from freqtrade.exchange import ( timeframe_to_minutes, timeframe_to_msecs, @@ -26,20 +25,21 @@ from freqtrade.strategy.strategy_helper import ( # Imports to be used for `from freqtrade.strategy import *` __all__ = [ - "timeframe_to_minutes", - "timeframe_to_next_date", - "timeframe_to_prev_date", - "informative", "IStrategy", "Trade", "Order", "PairLocks", + "informative", # Parameters "BooleanParameter", "CategoricalParameter", "DecimalParameter", "IntParameter", "RealParameter", + # timeframe helpers + "timeframe_to_minutes", + "timeframe_to_next_date", + "timeframe_to_prev_date", # Strategy helper functions "merge_informative_pair", "stoploss_from_absolute", From d754a2e295c0bd0e8efb4ab0cda45cb652393dc7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 08:38:59 +0200 Subject: [PATCH 13/14] feat: improve default imports --- docs/includes/strategy-imports.md | 8 ++++++-- freqtrade/templates/base_strategy.py.j2 | 10 +++++++--- freqtrade/templates/sample_strategy.py | 10 +++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/includes/strategy-imports.md b/docs/includes/strategy-imports.md index 14dde6c88..63cda329c 100644 --- a/docs/includes/strategy-imports.md +++ b/docs/includes/strategy-imports.md @@ -12,9 +12,9 @@ Obviously you can add more imports as needed for your strategy. # --- Do not remove these imports --- import numpy as np import pandas as pd -from datetime import datetime +from datetime import datetime, timedelta, timezone from pandas import DataFrame -from typing import Dict, Optional, Union +from typing import Dict, Optional, Union, Tuple from freqtrade.strategy import ( IStrategy, @@ -28,6 +28,10 @@ from freqtrade.strategy import ( DecimalParameter, IntParameter, RealParameter, + # timeframe helpers + timeframe_to_minutes, + timeframe_to_next_date, + timeframe_to_prev_date, # Strategy helper functions merge_informative_pair, stoploss_from_absolute, diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index a61093ebd..fe577826a 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -1,12 +1,12 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # flake8: noqa: F401 # isort: skip_file -# --- Do not remove these libs --- +# --- Do not remove these imports --- import numpy as np import pandas as pd -from datetime import datetime +from datetime import datetime, timedelta, timezone from pandas import DataFrame -from typing import Dict, Optional, Union +from typing import Dict, Optional, Union, Tuple from freqtrade.strategy import ( IStrategy, @@ -20,6 +20,10 @@ from freqtrade.strategy import ( DecimalParameter, IntParameter, RealParameter, + # timeframe helpers + timeframe_to_minutes, + timeframe_to_next_date, + timeframe_to_prev_date, # Strategy helper functions merge_informative_pair, stoploss_from_absolute, diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 3a9235bc2..835e6fa91 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -1,12 +1,12 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # flake8: noqa: F401 # isort: skip_file -# --- Do not remove these libs --- +# --- Do not remove these imports --- import numpy as np import pandas as pd -from datetime import datetime +from datetime import datetime, timedelta, timezone from pandas import DataFrame -from typing import Dict, Optional, Union +from typing import Dict, Optional, Union, Tuple from freqtrade.strategy import ( IStrategy, @@ -20,6 +20,10 @@ from freqtrade.strategy import ( DecimalParameter, IntParameter, RealParameter, + # timeframe helpers + timeframe_to_minutes, + timeframe_to_next_date, + timeframe_to_prev_date, # Strategy helper functions merge_informative_pair, stoploss_from_absolute, From 7952712c5e11c141089aa244c930f357bd5e6aa1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Aug 2024 08:44:37 +0200 Subject: [PATCH 14/14] chore: update samples to use doublequotes --- docs/strategy-callbacks.md | 77 +++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 8bb3753de..ce1b9907c 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -43,10 +43,10 @@ class AwesomeStrategy(IStrategy): Called only once after bot instantiation. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ - if self.config['runmode'].value in ('live', 'dry_run'): + if self.config["runmode"].value in ("live", "dry_run"): # Assign this to the class by using self.* # can then be used by populate_* methods - self.custom_remote_data = requests.get('https://some_remote_source.example.com') + self.custom_remote_data = requests.get("https://some_remote_source.example.com") ``` @@ -59,6 +59,7 @@ seconds, unless configured differently) or once per candle in backtest/hyperopt This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc. ``` python +# Default imports import requests class AwesomeStrategy(IStrategy): @@ -73,10 +74,10 @@ class AwesomeStrategy(IStrategy): :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ - if self.config['runmode'].value in ('live', 'dry_run'): + if self.config["runmode"].value in ("live", "dry_run"): # Assign this to the class by using self.* # can then be used by populate_* methods - self.remote_data = requests.get('https://some_remote_source.example.com') + self.remote_data = requests.get("https://some_remote_source.example.com") ``` @@ -85,6 +86,8 @@ class AwesomeStrategy(IStrategy): Called before entering a trade, makes it possible to manage your position size when placing a new trade. ```python +# Default imports + class AwesomeStrategy(IStrategy): def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: Optional[float], max_stake: float, @@ -94,13 +97,13 @@ class AwesomeStrategy(IStrategy): dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) current_candle = dataframe.iloc[-1].squeeze() - if current_candle['fastk_rsi_1h'] > current_candle['fastd_rsi_1h']: - if self.config['stake_amount'] == 'unlimited': + if current_candle["fastk_rsi_1h"] > current_candle["fastd_rsi_1h"]: + if self.config["stake_amount"] == "unlimited": # Use entire available wallet during favorable conditions when in compounding mode. return max_stake else: # Compound profits during favorable conditions instead of using a static stake. - return self.wallets.get_total_stake_amount() / self.config['max_open_trades'] + return self.wallets.get_total_stake_amount() / self.config["max_open_trades"] # Use default stake amount. return proposed_stake @@ -131,25 +134,27 @@ Using `custom_exit()` signals in place of stoploss though *is not recommended*. An example of how we can use different indicators depending on the current profit and also exit trades that were open longer than one day: ``` python +# Default imports + class AwesomeStrategy(IStrategy): - def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, + def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() # Above 20% profit, sell when rsi < 80 if current_profit > 0.2: - if last_candle['rsi'] < 80: - return 'rsi_below_80' + if last_candle["rsi"] < 80: + return "rsi_below_80" # Between 2% and 10%, sell if EMA-long above EMA-short if 0.02 < current_profit < 0.1: - if last_candle['emalong'] > last_candle['emashort']: - return 'ema_long_below_80' + if last_candle["emalong"] > last_candle["emashort"]: + return "ema_long_below_80" # Sell any positions at a loss if they are held for more than one day. if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1: - return 'unclog' + return "unclog" ``` See [Dataframe access](strategy-advanced.md#dataframe-access) for more information about dataframe use in strategy callbacks. @@ -170,7 +175,6 @@ The absolute value of the return value is used (the sign is ignored), so returni Returning `None` will be interpreted as "no desire to change", and is the only safe way to return when you'd like to not modify the stoploss. `NaN` and `inf` values are considered invalid and will be ignored (identical to `None`). - Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchangefreqtrade)). !!! Note "Use of dates" @@ -303,9 +307,9 @@ class AwesomeStrategy(IStrategy): current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> Optional[float]: - if pair in ('ETH/BTC', 'XRP/BTC'): + if pair in ("ETH/BTC", "XRP/BTC"): return -0.10 - elif pair in ('LTC/BTC'): + elif pair in ("LTC/BTC"): return -0.05 return -0.15 ``` @@ -384,7 +388,7 @@ class AwesomeStrategy(IStrategy): def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # <...> - dataframe['sar'] = ta.SAR(dataframe) + dataframe["sar"] = ta.SAR(dataframe) use_custom_stoploss = True @@ -396,7 +400,7 @@ class AwesomeStrategy(IStrategy): last_candle = dataframe.iloc[-1].squeeze() # Use parabolic sar as absolute stoploss price - stoploss_price = last_candle['sar'] + stoploss_price = last_candle["sar"] # Convert absolute price to percentage relative to current_rate if stoploss_price < current_rate: @@ -462,7 +466,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab ??? Example "Returning a stoploss using absolute price from the custom stoploss function" - If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`. + If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle["atr"] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`. For futures, we need to adjust the direction (up or down), as well as adjust for leverage, since the [`custom_stoploss`](strategy-callbacks.md#custom-stoploss) callback returns the ["risk for this trade"](stoploss.md#stoploss-and-leverage) - not the relative price movement. ``` python @@ -472,8 +476,8 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab use_custom_stoploss = True - def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe["atr"] = ta.ATR(dataframe, timeperiod=14) return dataframe def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, @@ -483,7 +487,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc) candle = dataframe.iloc[-1].squeeze() side = 1 if trade.is_short else -1 - return stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), + return stoploss_from_absolute(current_rate + (side * candle["atr"] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage) @@ -575,8 +579,8 @@ class AwesomeStrategy(IStrategy): # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. unfilledtimeout = { - 'entry': 60 * 25, - 'exit': 60 * 25 + "entry": 60 * 25, + "exit": 60 * 25 } def check_entry_timeout(self, pair: str, trade: Trade, order: Order, @@ -677,7 +681,7 @@ class AwesomeStrategy(IStrategy): :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param current_time: datetime object, containing the current datetime :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. - :param side: 'long' or 'short' - indicating the direction of the proposed trade + :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 @@ -725,8 +729,8 @@ class AwesomeStrategy(IStrategy): or current rate for market orders. :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param exit_reason: Exit reason. - Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', - 'exit_signal', 'force_exit', 'emergency_exit'] + Can be any of ["roi", "stop_loss", "stoploss_on_exchange", "trailing_stop_loss", + "exit_signal", "force_exit", "emergency_exit"] :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True, then the exit-order is placed on the exchange. @@ -758,7 +762,7 @@ This callback is **not** called when there is an open order (either buy or sell) `adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible. Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position (negative values will decrease your position), no matter if it's a long or short trade. -Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, 'increase_favorable_conditions'`). +Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, "increase_favorable_conditions"`). Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage. @@ -780,7 +784,7 @@ Returning a value more than the above (so remaining stake_amount would become ne !!! Note "About stake size" Using fixed stake size means it will be the amount used for the first order, just like without position adjustment. If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that. - Using 'unlimited' stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order. + Using `"unlimited"` stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order. !!! Warning "Stoploss calculation" Stoploss is still calculated from the initial opening price, not averaged price. @@ -799,9 +803,6 @@ Returning a value more than the above (so remaining stake_amount would become ne ``` python # Default imports -from freqtrade.persistence import Trade -from typing import Optional, Tuple, Union - class DigDeeperStrategy(IStrategy): @@ -864,7 +865,7 @@ class DigDeeperStrategy(IStrategy): if current_profit > 0.05 and trade.nr_of_successful_exits == 0: # Take half of the profit at +5% - return -(trade.stake_amount / 2), 'half_profit_5%' + return -(trade.stake_amount / 2), "half_profit_5%" if current_profit > -0.05: return None @@ -874,7 +875,7 @@ class DigDeeperStrategy(IStrategy): # Only buy when not actively falling price. last_candle = dataframe.iloc[-1].squeeze() previous_candle = dataframe.iloc[-2].squeeze() - if last_candle['close'] < previous_candle['close']: + if last_candle["close"] < previous_candle["close"]: return None filled_entries = trade.select_filled_orders(trade.entry_side) @@ -892,7 +893,7 @@ class DigDeeperStrategy(IStrategy): stake_amount = filled_entries[0].stake_amount # This then calculates current safety order size stake_amount = stake_amount * (1 + (count_of_entries * 0.25)) - return stake_amount, '1/3rd_increase' + return stake_amount, "1/3rd_increase" except Exception as exception: return None @@ -964,7 +965,7 @@ class AwesomeStrategy(IStrategy): :param proposed_rate: Rate, calculated based on pricing settings in entry_pricing. :param current_order_rate: Rate of the existing order in place. :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. - :param side: 'long' or 'short' - indicating the direction of the proposed trade + :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 float: New entry price value if provided @@ -1013,7 +1014,7 @@ class AwesomeStrategy(IStrategy): :param proposed_leverage: A leverage proposed by the bot. :param max_leverage: Max leverage allowed on this pair :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. - :param side: 'long' or 'short' - indicating the direction of the proposed trade + :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 @@ -1048,7 +1049,7 @@ class AwesomeStrategy(IStrategy): last_candle = dataframe.iloc[-1].squeeze() if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side): - trade.set_custom_data(key='entry_candle_high', value=last_candle['high']) + trade.set_custom_data(key="entry_candle_high", value=last_candle["high"]) return None