Merge pull request #10544 from freqtrade/feat/strategy_star_import

Improved imports for strategy
This commit is contained in:
Matthias
2024-08-18 09:53:45 +02:00
committed by GitHub
15 changed files with 329 additions and 226 deletions

View File

@@ -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
```

View File

@@ -24,6 +24,8 @@ Currently available callbacks:
!!! Tip "Callback calling sequence" !!! Tip "Callback calling sequence"
You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic) You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic)
--8<-- "includes/strategy-imports.md"
## Bot start ## Bot start
A simple callback which is called once when the strategy is loaded. 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. Called only once after bot instantiation.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
""" """
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.* # Assign this to the class by using self.*
# can then be used by populate_* methods # 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. This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc.
``` python ``` python
# Default imports
import requests import requests
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -71,10 +74,10 @@ class AwesomeStrategy(IStrategy):
:param current_time: datetime object, containing the current datetime :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. :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.* # Assign this to the class by using self.*
# can then be used by populate_* methods # 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. Called before entering a trade, makes it possible to manage your position size when placing a new trade.
```python ```python
# Default imports
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: Optional[float], max_stake: 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) dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze() current_candle = dataframe.iloc[-1].squeeze()
if current_candle['fastk_rsi_1h'] > current_candle['fastd_rsi_1h']: if current_candle["fastk_rsi_1h"] > current_candle["fastd_rsi_1h"]:
if self.config['stake_amount'] == 'unlimited': if self.config["stake_amount"] == "unlimited":
# Use entire available wallet during favorable conditions when in compounding mode. # Use entire available wallet during favorable conditions when in compounding mode.
return max_stake return max_stake
else: else:
# Compound profits during favorable conditions instead of using a static stake. # 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. # Use default stake amount.
return proposed_stake 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: 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 ``` python
# Default imports
class AwesomeStrategy(IStrategy): 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): current_profit: float, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze() last_candle = dataframe.iloc[-1].squeeze()
# Above 20% profit, sell when rsi < 80 # Above 20% profit, sell when rsi < 80
if current_profit > 0.2: if current_profit > 0.2:
if last_candle['rsi'] < 80: if last_candle["rsi"] < 80:
return 'rsi_below_80' return "rsi_below_80"
# Between 2% and 10%, sell if EMA-long above EMA-short # Between 2% and 10%, sell if EMA-long above EMA-short
if 0.02 < current_profit < 0.1: if 0.02 < current_profit < 0.1:
if last_candle['emalong'] > last_candle['emashort']: if last_candle["emalong"] > last_candle["emashort"]:
return 'ema_long_below_80' return "ema_long_below_80"
# Sell any positions at a loss if they are held for more than one day. # 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: 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. 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. 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`). `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)). 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" !!! 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: To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method:
``` python ``` python
# additional imports required # Default imports
from datetime import datetime
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -206,7 +210,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True 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, current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]: **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. 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 ``` python
from datetime import datetime, timedelta # Default imports
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -245,7 +248,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True 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, current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]: **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)). If an additional order fills, set stoploss to -10% below the new `open_rate` ([Averaged across all entries](#position-adjust-calculations)).
``` python ``` python
from datetime import datetime, timedelta # Default imports
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -272,7 +274,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True 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, current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]: **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. 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 ``` python
from datetime import datetime # Default imports
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -302,13 +303,13 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True 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, current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]: **kwargs) -> Optional[float]:
if pair in ('ETH/BTC', 'XRP/BTC'): if pair in ("ETH/BTC", "XRP/BTC"):
return -0.10 return -0.10
elif pair in ('LTC/BTC'): elif pair in ("LTC/BTC"):
return -0.05 return -0.05
return -0.15 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. Please note that the stoploss can only increase, values lower than the current stoploss are ignored.
``` python ``` python
from datetime import datetime, timedelta # Default imports
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -329,7 +329,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True 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, current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]: **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. * Once profit is > 40% - set stoploss to 25% above open price.
``` python ``` python
from datetime import datetime # Default imports
from freqtrade.persistence import Trade
from freqtrade.strategy import stoploss_from_open
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -363,7 +361,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True 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, current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]: **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. Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss.
``` python ``` python
# Default imports
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# <...> # <...>
dataframe['sar'] = ta.SAR(dataframe) dataframe["sar"] = ta.SAR(dataframe)
use_custom_stoploss = True 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, current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]: **kwargs) -> Optional[float]:
@@ -400,7 +400,7 @@ class AwesomeStrategy(IStrategy):
last_candle = dataframe.iloc[-1].squeeze() last_candle = dataframe.iloc[-1].squeeze()
# Use parabolic sar as absolute stoploss price # 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 # Convert absolute price to percentage relative to current_rate
if stoploss_price < current_rate: if stoploss_price < current_rate:
@@ -429,10 +429,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
``` python ``` python
# Default imports
from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, stoploss_from_open
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -440,7 +437,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
use_custom_stoploss = True 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, current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]: **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" ??? 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. 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 ``` python
# Default imports
from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, stoploss_from_absolute, timeframe_to_prev_date
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
use_custom_stoploss = True use_custom_stoploss = True
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
return dataframe 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, current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]: **kwargs) -> Optional[float]:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc) trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
candle = dataframe.iloc[-1].squeeze() candle = dataframe.iloc[-1].squeeze()
side = 1 if trade.is_short else -1 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, current_rate=current_rate,
is_short=trade.is_short, is_short=trade.is_short,
leverage=trade.leverage) leverage=trade.leverage)
``` ```
--- ---
## Custom order price rules ## 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 ### Custom order entry and exit price example
``` python ``` python
from datetime import datetime, timedelta, timezone # Default imports
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
# ... populate_* methods # ... 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: entry_tag: Optional[str], side: str, **kwargs) -> float:
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe) timeframe=self.timeframe)
new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1] new_entryprice = dataframe["bollinger_10_lowerband"].iat[-1]
return new_entryprice return new_entryprice
@@ -542,7 +534,7 @@ class AwesomeStrategy(IStrategy):
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe) timeframe=self.timeframe)
new_exitprice = dataframe['bollinger_10_upperband'].iat[-1] new_exitprice = dataframe["bollinger_10_upperband"].iat[-1]
return new_exitprice 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). The function must return either `True` (cancel order) or `False` (keep order alive).
``` python ``` python
from datetime import datetime, timedelta # Default imports
from freqtrade.persistence import Trade, Order
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -588,11 +579,11 @@ class AwesomeStrategy(IStrategy):
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
unfilledtimeout = { unfilledtimeout = {
'entry': 60 * 25, "entry": 60 * 25,
'exit': 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: current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
return True return True
@@ -603,7 +594,7 @@ class AwesomeStrategy(IStrategy):
return False 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: current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
return True return True
@@ -620,8 +611,7 @@ class AwesomeStrategy(IStrategy):
### Custom order timeout example (using additional data) ### Custom order timeout example (using additional data)
``` python ``` python
from datetime import datetime # Default imports
from freqtrade.persistence import Trade, Order
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -629,24 +619,24 @@ class AwesomeStrategy(IStrategy):
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
unfilledtimeout = { unfilledtimeout = {
'entry': 60 * 25, "entry": 60 * 25,
'exit': 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: current_time: datetime, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1) 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. # Cancel buy order if price is more than 2% above the order.
if current_price > order.price * 1.02: if current_price > order.price * 1.02:
return True return True
return False 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: current_time: datetime, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1) 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. # Cancel sell order if price is more than 2% below the order.
if current_price < order.price * 0.98: if current_price < order.price * 0.98:
return True 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). `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 ``` python
# Default imports
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
# ... populate_* methods # ... populate_* methods
@@ -689,7 +681,7 @@ class AwesomeStrategy(IStrategy):
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :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. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the buy-order is placed on the exchange. :return bool: When True is returned, then the buy-order is placed on the exchange.
False aborts the process False aborts the process
@@ -711,8 +703,7 @@ The exit-reasons (if applicable) will be in the following sequence:
* `trailing_stop_loss` * `trailing_stop_loss`
``` python ``` python
from freqtrade.persistence import Trade # Default imports
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -738,14 +729,14 @@ class AwesomeStrategy(IStrategy):
or current rate for market orders. or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param exit_reason: Exit reason. :param exit_reason: Exit reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', Can be any of ["roi", "stop_loss", "stoploss_on_exchange", "trailing_stop_loss",
'exit_signal', 'force_exit', 'emergency_exit'] "exit_signal", "force_exit", "emergency_exit"]
:param current_time: datetime object, containing the current datetime :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. :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. :return bool: When True, then the exit-order is placed on the exchange.
False aborts the process 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 # Reject force-sells with negative profit
# This is just a sample, please adjust to your needs # This is just a sample, please adjust to your needs
# (this does not necessarily make sense, assuming you know when you're force-selling) # (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. `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. 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. 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" !!! Note "About stake size"
Using fixed stake size means it will be the amount used for the first order, just like without position adjustment. 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. 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" !!! Warning "Stoploss calculation"
Stoploss is still calculated from the initial opening price, not averaged price. 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. 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 ``` python
from freqtrade.persistence import Trade # Default imports
from typing import Optional, Tuple, Union
class DigDeeperStrategy(IStrategy): class DigDeeperStrategy(IStrategy):
@@ -876,7 +865,7 @@ class DigDeeperStrategy(IStrategy):
if current_profit > 0.05 and trade.nr_of_successful_exits == 0: if current_profit > 0.05 and trade.nr_of_successful_exits == 0:
# Take half of the profit at +5% # 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: if current_profit > -0.05:
return None return None
@@ -886,7 +875,7 @@ class DigDeeperStrategy(IStrategy):
# Only buy when not actively falling price. # Only buy when not actively falling price.
last_candle = dataframe.iloc[-1].squeeze() last_candle = dataframe.iloc[-1].squeeze()
previous_candle = dataframe.iloc[-2].squeeze() previous_candle = dataframe.iloc[-2].squeeze()
if last_candle['close'] < previous_candle['close']: if last_candle["close"] < previous_candle["close"]:
return None return None
filled_entries = trade.select_filled_orders(trade.entry_side) filled_entries = trade.select_filled_orders(trade.entry_side)
@@ -904,7 +893,7 @@ class DigDeeperStrategy(IStrategy):
stake_amount = filled_entries[0].stake_amount stake_amount = filled_entries[0].stake_amount
# This then calculates current safety order size # This then calculates current safety order size
stake_amount = stake_amount * (1 + (count_of_entries * 0.25)) 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: except Exception as exception:
return None 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. 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 ```python
from freqtrade.persistence import Trade # Default imports
from datetime import timedelta, datetime
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@@ -977,13 +965,18 @@ class AwesomeStrategy(IStrategy):
:param proposed_rate: Rate, calculated based on pricing settings in entry_pricing. :param proposed_rate: Rate, calculated based on pricing settings in entry_pricing.
:param current_order_rate: Rate of the existing order in place. :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 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. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New entry price value if provided :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. # 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 # just cancel the order if it has been filled more than half of the amount
if order.filled > order.remaining: if order.filled > order.remaining:
return None return None
@@ -991,7 +984,7 @@ class AwesomeStrategy(IStrategy):
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze() current_candle = dataframe.iloc[-1].squeeze()
# desired price # desired price
return current_candle['sma_200'] return current_candle["sma_200"]
# default: maintain existing order # default: maintain existing order
return current_order_rate 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. For markets / exchanges that don't support leverage, this method is ignored.
``` python ``` python
# Default imports
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
def leverage(self, pair: str, current_time: datetime, current_rate: float, def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, 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 proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair :param max_leverage: Max leverage allowed on this pair
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :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: A leverage amount, which is between 1.0 and max_leverage.
""" """
return 1.0 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. 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 ``` python
# Default imports
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None: 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() last_candle = dataframe.iloc[-1].squeeze()
if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side): 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 return None

View File

@@ -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. 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. Instead, have a look at the [Storing information](strategy-advanced.md#storing-information-persistent) section.
--8<-- "includes/strategy-imports.md"
## Strategy file loading ## Strategy file loading
By default, freqtrade will attempt to load strategies from all `.py` files within `user_data/strategies`. By default, freqtrade will attempt to load strategies from all `.py` files within `user_data/strategies`.

View File

@@ -6,6 +6,7 @@ from freqtrade.exchange import (
timeframe_to_prev_date, timeframe_to_prev_date,
timeframe_to_seconds, timeframe_to_seconds,
) )
from freqtrade.persistence import Order, PairLocks, Trade
from freqtrade.strategy.informative_decorator import informative from freqtrade.strategy.informative_decorator import informative
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.parameters import ( from freqtrade.strategy.parameters import (
@@ -20,3 +21,27 @@ from freqtrade.strategy.strategy_helper import (
stoploss_from_absolute, stoploss_from_absolute,
stoploss_from_open, 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",
]

View File

@@ -1,15 +1,34 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401 # flake8: noqa: F401
# isort: skip_file # isort: skip_file
# --- Do not remove these libs --- # --- Do not remove these imports ---
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame from pandas import DataFrame
from datetime import datetime from typing import Dict, Optional, Union, Tuple
from typing import Optional, Union
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, from freqtrade.strategy import (
IntParameter, IStrategy, merge_informative_pair) 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 # Add your lib to import here
@@ -40,7 +59,7 @@ class {{ strategy }}(IStrategy):
INTERFACE_VERSION = 3 INTERFACE_VERSION = 3
# Optimal timeframe for the strategy. # Optimal timeframe for the strategy.
timeframe = '5m' timeframe = "5m"
# Can this strategy go short? # Can this strategy go short?
can_short: bool = False can_short: bool = False
@@ -78,8 +97,8 @@ class {{ strategy }}(IStrategy):
buy_rsi = IntParameter(10, 40, default=30, space="buy") buy_rsi = IntParameter(10, 40, default=30, space="buy")
sell_rsi = IntParameter(60, 90, default=70, space="sell") sell_rsi = IntParameter(60, 90, default=70, space="sell")
{{ attributes | indent(4) }} {{- attributes | indent(4) }}
{{ plot_config | indent(4) }} {{- plot_config | indent(4) }}
def informative_pairs(self): def informative_pairs(self):
""" """
@@ -105,7 +124,7 @@ class {{ strategy }}(IStrategy):
:param metadata: Additional information, like the currently traded pair :param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies :return: a Dataframe with all mandatory indicators for the strategies
""" """
{{ indicators | indent(8) }} {{- indicators | indent(8) }}
return dataframe return dataframe
@@ -119,9 +138,9 @@ class {{ strategy }}(IStrategy):
dataframe.loc[ dataframe.loc[
( (
{{ buy_trend | indent(16) }} {{ 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) # Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info)
""" """
dataframe.loc[ dataframe.loc[
@@ -144,9 +163,9 @@ class {{ strategy }}(IStrategy):
dataframe.loc[ dataframe.loc[
( (
{{ sell_trend | indent(16) }} {{ 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) # Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info)
""" """
dataframe.loc[ dataframe.loc[
@@ -157,4 +176,4 @@ class {{ strategy }}(IStrategy):
'exit_short'] = 1 'exit_short'] = 1
""" """
return dataframe return dataframe
{{ additional_methods | indent(4) }} {{- additional_methods | indent(4) }}

View File

@@ -1,24 +1,39 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401 # flake8: noqa: F401
# isort: skip_file # isort: skip_file
# --- Do not remove these libs --- # --- Do not remove these imports ---
import numpy as np # noqa import numpy as np
import pandas as pd # noqa import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame from pandas import DataFrame
from typing import Optional, Union from typing import Dict, Optional, Union, Tuple
from freqtrade.strategy import ( from freqtrade.strategy import (
IStrategy,
Trade,
Order,
PairLocks,
informative, # @informative decorator
# Hyperopt Parameters
BooleanParameter, BooleanParameter,
CategoricalParameter, CategoricalParameter,
DecimalParameter, DecimalParameter,
IStrategy,
IntParameter, 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 # Add your lib to import here
import talib.abstract as ta 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. # This class is a sample. Feel free to customize it.

View File

@@ -1,3 +1,3 @@
(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
(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle (dataframe["tema"] <= dataframe["bb_middleband"]) & # Guard: tema below BB middle
(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising (dataframe["tema"] > dataframe["tema"].shift(1)) & # Guard: tema is raising

View File

@@ -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

View File

@@ -3,24 +3,24 @@
# ------------------------------------ # ------------------------------------
# ADX # ADX
dataframe['adx'] = ta.ADX(dataframe) dataframe["adx"] = ta.ADX(dataframe)
# # Plus Directional Indicator / Movement # # Plus Directional Indicator / Movement
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe) # dataframe["plus_dm"] = ta.PLUS_DM(dataframe)
# dataframe['plus_di'] = ta.PLUS_DI(dataframe) # dataframe["plus_di"] = ta.PLUS_DI(dataframe)
# # Minus Directional Indicator / Movement # # Minus Directional Indicator / Movement
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe) # dataframe["minus_dm"] = ta.MINUS_DM(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe) # dataframe["minus_di"] = ta.MINUS_DI(dataframe)
# # Aroon, Aroon Oscillator # # Aroon, Aroon Oscillator
# aroon = ta.AROON(dataframe) # aroon = ta.AROON(dataframe)
# dataframe['aroonup'] = aroon['aroonup'] # dataframe["aroonup"] = aroon["aroonup"]
# dataframe['aroondown'] = aroon['aroondown'] # dataframe["aroondown"] = aroon["aroondown"]
# dataframe['aroonosc'] = ta.AROONOSC(dataframe) # dataframe["aroonosc"] = ta.AROONOSC(dataframe)
# # Awesome Oscillator # # Awesome Oscillator
# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) # dataframe["ao"] = qtpylib.awesome_oscillator(dataframe)
# # Keltner Channel # # Keltner Channel
# keltner = qtpylib.keltner_channel(dataframe) # keltner = qtpylib.keltner_channel(dataframe)
@@ -36,58 +36,58 @@ dataframe['adx'] = ta.ADX(dataframe)
# ) # )
# # Ultimate Oscillator # # Ultimate Oscillator
# dataframe['uo'] = ta.ULTOSC(dataframe) # dataframe["uo"] = ta.ULTOSC(dataframe)
# # Commodity Channel Index: values [Oversold:-100, Overbought:100] # # Commodity Channel Index: values [Oversold:-100, Overbought:100]
# dataframe['cci'] = ta.CCI(dataframe) # dataframe["cci"] = ta.CCI(dataframe)
# RSI # 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) # # Inverse Fisher transform on RSI: values [-1.0, 1.0] (https://goo.gl/2JGGoy)
# rsi = 0.1 * (dataframe['rsi'] - 50) # rsi = 0.1 * (dataframe["rsi"] - 50)
# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) # 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) # # 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 # # Stochastic Slow
# stoch = ta.STOCH(dataframe) # stoch = ta.STOCH(dataframe)
# dataframe['slowd'] = stoch['slowd'] # dataframe["slowd"] = stoch["slowd"]
# dataframe['slowk'] = stoch['slowk'] # dataframe["slowk"] = stoch["slowk"]
# Stochastic Fast # Stochastic Fast
stoch_fast = ta.STOCHF(dataframe) stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd'] dataframe["fastd"] = stoch_fast["fastd"]
dataframe['fastk'] = stoch_fast['fastk'] dataframe["fastk"] = stoch_fast["fastk"]
# # Stochastic RSI # # Stochastic RSI
# Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this. # 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. # STOCHRSI is NOT aligned with tradingview, which may result in non-expected results.
# stoch_rsi = ta.STOCHRSI(dataframe) # stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd'] # dataframe["fastd_rsi"] = stoch_rsi["fastd"]
# dataframe['fastk_rsi'] = stoch_rsi['fastk'] # dataframe["fastk_rsi"] = stoch_rsi["fastk"]
# MACD # MACD
macd = ta.MACD(dataframe) macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd'] dataframe["macd"] = macd["macd"]
dataframe['macdsignal'] = macd['macdsignal'] dataframe["macdsignal"] = macd["macdsignal"]
dataframe['macdhist'] = macd['macdhist'] dataframe["macdhist"] = macd["macdhist"]
# MFI # MFI
dataframe['mfi'] = ta.MFI(dataframe) dataframe["mfi"] = ta.MFI(dataframe)
# # ROC # # ROC
# dataframe['roc'] = ta.ROC(dataframe) # dataframe["roc"] = ta.ROC(dataframe)
# Overlap Studies # Overlap Studies
# ------------------------------------ # ------------------------------------
# Bollinger Bands # Bollinger Bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower'] dataframe["bb_lowerband"] = bollinger["lower"]
dataframe['bb_middleband'] = bollinger['mid'] dataframe["bb_middleband"] = bollinger["mid"]
dataframe['bb_upperband'] = bollinger['upper'] dataframe["bb_upperband"] = bollinger["upper"]
dataframe["bb_percent"] = ( dataframe["bb_percent"] = (
(dataframe["close"] - dataframe["bb_lowerband"]) / (dataframe["close"] - dataframe["bb_lowerband"]) /
(dataframe["bb_upperband"] - dataframe["bb_lowerband"]) (dataframe["bb_upperband"] - dataframe["bb_lowerband"])
@@ -112,95 +112,95 @@ dataframe["bb_width"] = (
# ) # )
# # EMA - Exponential Moving Average # # EMA - Exponential Moving Average
# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) # dataframe["ema3"] = ta.EMA(dataframe, timeperiod=3)
# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) # dataframe["ema5"] = ta.EMA(dataframe, timeperiod=5)
# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) # dataframe["ema10"] = ta.EMA(dataframe, timeperiod=10)
# dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21) # dataframe["ema21"] = ta.EMA(dataframe, timeperiod=21)
# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) # dataframe["ema50"] = ta.EMA(dataframe, timeperiod=50)
# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) # dataframe["ema100"] = ta.EMA(dataframe, timeperiod=100)
# # SMA - Simple Moving Average # # SMA - Simple Moving Average
# dataframe['sma3'] = ta.SMA(dataframe, timeperiod=3) # dataframe["sma3"] = ta.SMA(dataframe, timeperiod=3)
# dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5) # dataframe["sma5"] = ta.SMA(dataframe, timeperiod=5)
# dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10) # dataframe["sma10"] = ta.SMA(dataframe, timeperiod=10)
# dataframe['sma21'] = ta.SMA(dataframe, timeperiod=21) # dataframe["sma21"] = ta.SMA(dataframe, timeperiod=21)
# dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50) # dataframe["sma50"] = ta.SMA(dataframe, timeperiod=50)
# dataframe['sma100'] = ta.SMA(dataframe, timeperiod=100) # dataframe["sma100"] = ta.SMA(dataframe, timeperiod=100)
# Parabolic SAR # Parabolic SAR
dataframe['sar'] = ta.SAR(dataframe) dataframe["sar"] = ta.SAR(dataframe)
# TEMA - Triple Exponential Moving Average # TEMA - Triple Exponential Moving Average
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
# Cycle Indicator # Cycle Indicator
# ------------------------------------ # ------------------------------------
# Hilbert Transform Indicator - SineWave # Hilbert Transform Indicator - SineWave
hilbert = ta.HT_SINE(dataframe) hilbert = ta.HT_SINE(dataframe)
dataframe['htsine'] = hilbert['sine'] dataframe["htsine"] = hilbert["sine"]
dataframe['htleadsine'] = hilbert['leadsine'] dataframe["htleadsine"] = hilbert["leadsine"]
# Pattern Recognition - Bullish candlestick patterns # Pattern Recognition - Bullish candlestick patterns
# ------------------------------------ # ------------------------------------
# # Hammer: values [0, 100] # # Hammer: values [0, 100]
# dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) # dataframe["CDLHAMMER"] = ta.CDLHAMMER(dataframe)
# # Inverted Hammer: values [0, 100] # # Inverted Hammer: values [0, 100]
# dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) # dataframe["CDLINVERTEDHAMMER"] = ta.CDLINVERTEDHAMMER(dataframe)
# # Dragonfly Doji: values [0, 100] # # Dragonfly Doji: values [0, 100]
# dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) # dataframe["CDLDRAGONFLYDOJI"] = ta.CDLDRAGONFLYDOJI(dataframe)
# # Piercing Line: values [0, 100] # # 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] # # 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] # # 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 # Pattern Recognition - Bearish candlestick patterns
# ------------------------------------ # ------------------------------------
# # Hanging Man: values [0, 100] # # Hanging Man: values [0, 100]
# dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) # dataframe["CDLHANGINGMAN"] = ta.CDLHANGINGMAN(dataframe)
# # Shooting Star: values [0, 100] # # Shooting Star: values [0, 100]
# dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) # dataframe["CDLSHOOTINGSTAR"] = ta.CDLSHOOTINGSTAR(dataframe)
# # Gravestone Doji: values [0, 100] # # Gravestone Doji: values [0, 100]
# dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) # dataframe["CDLGRAVESTONEDOJI"] = ta.CDLGRAVESTONEDOJI(dataframe)
# # Dark Cloud Cover: values [0, 100] # # Dark Cloud Cover: values [0, 100]
# dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) # dataframe["CDLDARKCLOUDCOVER"] = ta.CDLDARKCLOUDCOVER(dataframe)
# # Evening Doji Star: values [0, 100] # # Evening Doji Star: values [0, 100]
# dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) # dataframe["CDLEVENINGDOJISTAR"] = ta.CDLEVENINGDOJISTAR(dataframe)
# # Evening Star: values [0, 100] # # Evening Star: values [0, 100]
# dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) # dataframe["CDLEVENINGSTAR"] = ta.CDLEVENINGSTAR(dataframe)
# Pattern Recognition - Bullish/Bearish candlestick patterns # Pattern Recognition - Bullish/Bearish candlestick patterns
# ------------------------------------ # ------------------------------------
# # Three Line Strike: values [0, -100, 100] # # Three Line Strike: values [0, -100, 100]
# dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) # dataframe["CDL3LINESTRIKE"] = ta.CDL3LINESTRIKE(dataframe)
# # Spinning Top: values [0, -100, 100] # # 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] # # 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] # # 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] # # 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] # # 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 # # Chart type
# # ------------------------------------ # # ------------------------------------
# # Heikin Ashi Strategy # # Heikin Ashi Strategy
# heikinashi = qtpylib.heikinashi(dataframe) # heikinashi = qtpylib.heikinashi(dataframe)
# dataframe['ha_open'] = heikinashi['open'] # dataframe["ha_open"] = heikinashi["open"]
# dataframe['ha_close'] = heikinashi['close'] # dataframe["ha_close"] = heikinashi["close"]
# dataframe['ha_high'] = heikinashi['high'] # dataframe["ha_high"] = heikinashi["high"]
# dataframe['ha_low'] = heikinashi['low'] # dataframe["ha_low"] = heikinashi["low"]
# Retrieve best bid and best ask from the orderbook # Retrieve best bid and best ask from the orderbook
# ------------------------------------ # ------------------------------------
""" """
# first check if dataprovider is available # first check if dataprovider is available
if self.dp: if self.dp:
if self.dp.runmode.value in ('live', 'dry_run'): if self.dp.runmode.value in ("live", "dry_run"):
ob = self.dp.orderbook(metadata['pair'], 1) ob = self.dp.orderbook(metadata["pair"], 1)
dataframe['best_bid'] = ob['bids'][0][0] dataframe["best_bid"] = ob["bids"][0][0]
dataframe['best_ask'] = ob['asks'][0][0] dataframe["best_ask"] = ob["asks"][0][0]
""" """

View File

@@ -3,15 +3,15 @@
# ------------------------------------ # ------------------------------------
# RSI # RSI
dataframe['rsi'] = ta.RSI(dataframe) dataframe["rsi"] = ta.RSI(dataframe)
# Retrieve best bid and best ask from the orderbook # Retrieve best bid and best ask from the orderbook
# ------------------------------------ # ------------------------------------
""" """
# first check if dataprovider is available # first check if dataprovider is available
if self.dp: if self.dp:
if self.dp.runmode.value in ('live', 'dry_run'): if self.dp.runmode.value in ("live", "dry_run"):
ob = self.dp.orderbook(metadata['pair'], 1) ob = self.dp.orderbook(metadata["pair"], 1)
dataframe['best_bid'] = ob['bids'][0][0] dataframe["best_bid"] = ob["bids"][0][0]
dataframe['best_ask'] = ob['asks'][0][0] dataframe["best_ask"] = ob["asks"][0][0]
""" """

View File

@@ -3,18 +3,18 @@
def plot_config(self): def plot_config(self):
return { return {
# Main plot indicators (Moving averages, ...) # Main plot indicators (Moving averages, ...)
'main_plot': { "main_plot": {
'tema': {}, "tema": {},
'sar': {'color': 'white'}, "sar": {"color": "white"},
}, },
'subplots': { "subplots": {
# Subplots - each dict defines one additional plot # Subplots - each dict defines one additional plot
"MACD": { "MACD": {
'macd': {'color': 'blue'}, "macd": {"color": "blue"},
'macdsignal': {'color': 'orange'}, "macdsignal": {"color": "orange"},
}, },
"RSI": { "RSI": {
'rsi': {'color': 'red'}, "rsi": {"color": "red"},
} }
} }
} }

View File

@@ -1,3 +1,3 @@
(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
(dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle (dataframe["tema"] > dataframe["bb_middleband"]) & # Guard: tema above BB middle
(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling (dataframe["tema"] < dataframe["tema"].shift(1)) & # Guard: tema is falling

View File

@@ -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

View File

@@ -1,13 +1,13 @@
# Optional order type mapping. # Optional order type mapping.
order_types = { order_types = {
'entry': 'limit', "entry": "limit",
'exit': 'limit', "exit": "limit",
'stoploss': 'market', "stoploss": "market",
'stoploss_on_exchange': False "stoploss_on_exchange": False
} }
# Optional order time in force. # Optional order time in force.
order_time_in_force = { order_time_in_force = {
'entry': 'GTC', "entry": "GTC",
'exit': 'GTC' "exit": "GTC"
} }

View File

@@ -13,9 +13,9 @@ def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
""" """
pass pass
def custom_entry_price(self, pair: str, trade: Optional['Trade'], def custom_entry_price(self, pair: str, trade: Optional[Trade],
current_time: 'datetime', proposed_rate: float, current_time: datetime, proposed_rate: float,
entry_tag: 'Optional[str]', side: str, **kwargs) -> float: entry_tag: Optional[str], side: str, **kwargs) -> float:
""" """
Custom entry price logic, returning the new entry price. 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 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, current_time: datetime, proposed_rate: float, current_order_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> 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 return current_order_rate
def custom_exit_price(self, pair: str, trade: 'Trade', def custom_exit_price(self, pair: str, trade: Trade,
current_time: 'datetime', proposed_rate: float, current_time: datetime, proposed_rate: float,
current_profit: float, exit_tag: Optional[str], **kwargs) -> float: current_profit: float, exit_tag: Optional[str], **kwargs) -> float:
""" """
Custom exit price logic, returning the new exit price. 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 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: current_profit: float, after_fill: bool, **kwargs) -> float:
""" """
Custom stoploss logic, returning the new distance relative to current_rate (as ratio). 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 :return float: New stoploss value, relative to the current_rate
""" """
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) -> 'Optional[Union[str, bool]]': current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
""" """
Custom exit signal logic indicating that specified position should be sold. Returning a 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 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 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, 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. Called right before placing a regular exit order.
Timing for this function is critical, so avoid doing heavy computations or Timing for this function is critical, so avoid doing heavy computations or
@@ -206,7 +206,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
""" """
return True 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: current_time: datetime, **kwargs) -> bool:
""" """
Check entry timeout function callback. Check entry timeout function callback.
@@ -228,7 +228,7 @@ def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
""" """
return False 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: current_time: datetime, **kwargs) -> bool:
""" """
Check exit timeout function callback. Check exit timeout function callback.
@@ -250,7 +250,7 @@ def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order',
""" """
return False 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, current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float, min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: 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 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: current_time: datetime, **kwargs) -> None:
""" """
Called right after an order fills. Called right after an order fills.