diff --git a/docs/includes/strategy-imports.md b/docs/includes/strategy-imports.md new file mode 100644 index 000000000..63cda329c --- /dev/null +++ b/docs/includes/strategy-imports.md @@ -0,0 +1,45 @@ +## 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, timedelta, timezone +from pandas import DataFrame +from typing import Dict, Optional, Union, Tuple + +from freqtrade.strategy import ( + IStrategy, + Trade, + Order, + PairLocks, + informative, # @informative decorator + # Hyperopt 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, + stoploss_from_open, +) + +# -------------------------------- +# Add your lib to import here +import talib.abstract as ta +from technical import qtpylib +``` diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 74eef53c1..ce1b9907c 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. @@ -41,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") ``` @@ -57,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): @@ -71,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") ``` @@ -83,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, @@ -92,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 @@ -129,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. @@ -168,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" @@ -196,9 +202,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): @@ -206,7 +210,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]: """ @@ -236,8 +240,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): @@ -245,7 +248,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]: @@ -263,8 +266,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): @@ -272,7 +274,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]: @@ -293,8 +295,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): @@ -302,13 +303,13 @@ 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]: - 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 ``` @@ -320,8 +321,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): @@ -329,7 +329,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]: @@ -353,9 +353,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): @@ -363,7 +361,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]: @@ -384,15 +382,17 @@ 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: # <...> - dataframe['sar'] = ta.SAR(dataframe) + dataframe["sar"] = ta.SAR(dataframe) 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]: @@ -400,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: @@ -429,10 +429,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): @@ -440,7 +437,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]: @@ -469,38 +466,34 @@ 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 - - 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): 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, + 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) 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) ``` - --- ## Custom order price rules @@ -520,19 +513,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 @@ -542,7 +534,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 @@ -579,8 +571,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): @@ -588,11 +579,11 @@ 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: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -603,7 +594,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 @@ -620,8 +611,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): @@ -629,24 +619,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 @@ -665,6 +655,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 @@ -689,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 @@ -711,8 +703,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): @@ -738,14 +729,14 @@ 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. 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) @@ -771,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. @@ -793,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. @@ -811,9 +802,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 -from freqtrade.persistence import Trade -from typing import Optional, Tuple, Union - +# Default imports class DigDeeperStrategy(IStrategy): @@ -876,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 @@ -886,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) @@ -904,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 @@ -951,8 +940,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): @@ -977,13 +965,18 @@ 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 """ # 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 @@ -991,7 +984,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 ``` @@ -1006,6 +999,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, @@ -1019,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 @@ -1036,6 +1031,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: """ @@ -1052,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 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`. diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index bb21100c4..e99473b4e 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -6,6 +6,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 +21,27 @@ from freqtrade.strategy.strategy_helper import ( stoploss_from_absolute, stoploss_from_open, ) + + +# Imports to be used for `from freqtrade.strategy import *` +__all__ = [ + "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", + "stoploss_from_open", +] diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index a4e0a2b24..fe577826a 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -1,15 +1,34 @@ # 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, timedelta, timezone from pandas import DataFrame -from datetime import datetime -from typing import Optional, Union +from typing import Dict, Optional, Union, Tuple -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, + # timeframe helpers + timeframe_to_minutes, + timeframe_to_next_date, + timeframe_to_prev_date, + # Strategy helper functions + merge_informative_pair, + stoploss_from_absolute, + stoploss_from_open, +) # -------------------------------- # Add your lib to import here @@ -40,7 +59,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 @@ -78,8 +97,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): """ @@ -105,7 +124,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 @@ -119,9 +138,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[ @@ -144,9 +163,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[ @@ -157,4 +176,4 @@ class {{ strategy }}(IStrategy): 'exit_short'] = 1 """ return dataframe - {{ additional_methods | indent(4) }} + {{- additional_methods | indent(4) }} diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 033c0d24e..835e6fa91 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -1,24 +1,39 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # flake8: noqa: F401 # isort: skip_file -# --- Do not remove these libs --- -import numpy as np # noqa -import pandas as pd # noqa +# --- Do not remove these imports --- +import numpy as np +import pandas as pd +from datetime import datetime, timedelta, timezone from pandas import DataFrame -from typing import Optional, Union +from typing import Dict, Optional, Union, Tuple from freqtrade.strategy import ( + IStrategy, + Trade, + Order, + PairLocks, + informative, # @informative decorator + # Hyperopt Parameters BooleanParameter, CategoricalParameter, DecimalParameter, - IStrategy, IntParameter, + RealParameter, + # timeframe helpers + timeframe_to_minutes, + timeframe_to_next_date, + timeframe_to_prev_date, + # 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 +from technical import qtpylib # This class is a sample. Feel free to customize it. 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" } 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.