mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-01-20 05:50:36 +00:00
Merge pull request #10544 from freqtrade/feat/strategy_star_import
Improved imports for strategy
This commit is contained in:
45
docs/includes/strategy-imports.md
Normal file
45
docs/includes/strategy-imports.md
Normal 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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|||||||
@@ -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",
|
||||||
|
]
|
||||||
|
|||||||
@@ -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) }}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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]
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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]
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user