Decouple strategy from analyse.py
This commit is contained in:
parent
f7e979f3ba
commit
c46d78b4b9
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,6 +5,8 @@ config.json
|
|||||||
*.sqlite
|
*.sqlite
|
||||||
.hyperopt
|
.hyperopt
|
||||||
logfile.txt
|
logfile.txt
|
||||||
|
hyperopt_trials.pickle
|
||||||
|
user_data/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
@ -85,5 +87,3 @@ target/
|
|||||||
.venv
|
.venv
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
hyperopt_trials.pickle
|
|
||||||
|
@ -7,11 +7,10 @@ from enum import Enum
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import talib.abstract as ta
|
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
|
|
||||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
||||||
from freqtrade.exchange import get_ticker_history
|
from freqtrade.exchange import get_ticker_history
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -46,182 +45,8 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
|||||||
you are using. Let uncomment only the indicator you are using in your strategies
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
"""
|
"""
|
||||||
|
strategy = Strategy()
|
||||||
# Momentum Indicator
|
return strategy.populate_indicators(dataframe=dataframe)
|
||||||
# ------------------------------------
|
|
||||||
|
|
||||||
# ADX
|
|
||||||
dataframe['adx'] = ta.ADX(dataframe)
|
|
||||||
|
|
||||||
# Awesome oscillator
|
|
||||||
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
|
||||||
"""
|
|
||||||
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
|
|
||||||
dataframe['cci'] = ta.CCI(dataframe)
|
|
||||||
"""
|
|
||||||
# MACD
|
|
||||||
macd = ta.MACD(dataframe)
|
|
||||||
dataframe['macd'] = macd['macd']
|
|
||||||
dataframe['macdsignal'] = macd['macdsignal']
|
|
||||||
dataframe['macdhist'] = macd['macdhist']
|
|
||||||
|
|
||||||
# MFI
|
|
||||||
dataframe['mfi'] = ta.MFI(dataframe)
|
|
||||||
|
|
||||||
# Minus Directional Indicator / Movement
|
|
||||||
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
|
||||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
|
||||||
|
|
||||||
# Plus Directional Indicator / Movement
|
|
||||||
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
|
||||||
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
|
||||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
|
||||||
|
|
||||||
"""
|
|
||||||
# ROC
|
|
||||||
dataframe['roc'] = ta.ROC(dataframe)
|
|
||||||
"""
|
|
||||||
# RSI
|
|
||||||
dataframe['rsi'] = ta.RSI(dataframe)
|
|
||||||
"""
|
|
||||||
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
|
|
||||||
rsi = 0.1 * (dataframe['rsi'] - 50)
|
|
||||||
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
|
|
||||||
|
|
||||||
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
|
|
||||||
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
|
|
||||||
|
|
||||||
# Stoch
|
|
||||||
stoch = ta.STOCH(dataframe)
|
|
||||||
dataframe['slowd'] = stoch['slowd']
|
|
||||||
dataframe['slowk'] = stoch['slowk']
|
|
||||||
"""
|
|
||||||
# Stoch fast
|
|
||||||
stoch_fast = ta.STOCHF(dataframe)
|
|
||||||
dataframe['fastd'] = stoch_fast['fastd']
|
|
||||||
dataframe['fastk'] = stoch_fast['fastk']
|
|
||||||
"""
|
|
||||||
# Stoch RSI
|
|
||||||
stoch_rsi = ta.STOCHRSI(dataframe)
|
|
||||||
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
|
||||||
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Overlap Studies
|
|
||||||
# ------------------------------------
|
|
||||||
|
|
||||||
# Previous Bollinger bands
|
|
||||||
# Because ta.BBANDS implementation is broken with small numbers, it actually
|
|
||||||
# returns middle band for all the three bands. Switch to qtpylib.bollinger_bands
|
|
||||||
# and use middle band instead.
|
|
||||||
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
|
||||||
"""
|
|
||||||
# Bollinger bands
|
|
||||||
"""
|
|
||||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
|
||||||
dataframe['bb_lowerband'] = bollinger['lower']
|
|
||||||
dataframe['bb_middleband'] = bollinger['mid']
|
|
||||||
dataframe['bb_upperband'] = bollinger['upper']
|
|
||||||
|
|
||||||
# EMA - Exponential Moving Average
|
|
||||||
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
|
||||||
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
|
||||||
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
|
||||||
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
|
||||||
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
|
||||||
|
|
||||||
# SAR Parabol
|
|
||||||
dataframe['sar'] = ta.SAR(dataframe)
|
|
||||||
|
|
||||||
# SMA - Simple Moving Average
|
|
||||||
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
|
||||||
|
|
||||||
# TEMA - Triple Exponential Moving Average
|
|
||||||
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
|
||||||
|
|
||||||
# Cycle Indicator
|
|
||||||
# ------------------------------------
|
|
||||||
# Hilbert Transform Indicator - SineWave
|
|
||||||
hilbert = ta.HT_SINE(dataframe)
|
|
||||||
dataframe['htsine'] = hilbert['sine']
|
|
||||||
dataframe['htleadsine'] = hilbert['leadsine']
|
|
||||||
|
|
||||||
# Pattern Recognition - Bullish candlestick patterns
|
|
||||||
# ------------------------------------
|
|
||||||
"""
|
|
||||||
# Hammer: values [0, 100]
|
|
||||||
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
|
|
||||||
|
|
||||||
# Inverted Hammer: values [0, 100]
|
|
||||||
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
|
|
||||||
|
|
||||||
# Dragonfly Doji: values [0, 100]
|
|
||||||
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
|
|
||||||
|
|
||||||
# Piercing Line: values [0, 100]
|
|
||||||
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
|
|
||||||
|
|
||||||
# Morningstar: values [0, 100]
|
|
||||||
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
|
|
||||||
|
|
||||||
# Three White Soldiers: values [0, 100]
|
|
||||||
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Pattern Recognition - Bearish candlestick patterns
|
|
||||||
# ------------------------------------
|
|
||||||
"""
|
|
||||||
# Hanging Man: values [0, 100]
|
|
||||||
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
|
|
||||||
|
|
||||||
# Shooting Star: values [0, 100]
|
|
||||||
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
|
|
||||||
|
|
||||||
# Gravestone Doji: values [0, 100]
|
|
||||||
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
|
|
||||||
|
|
||||||
# Dark Cloud Cover: values [0, 100]
|
|
||||||
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
|
|
||||||
|
|
||||||
# Evening Doji Star: values [0, 100]
|
|
||||||
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
|
|
||||||
|
|
||||||
# Evening Star: values [0, 100]
|
|
||||||
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Pattern Recognition - Bullish/Bearish candlestick patterns
|
|
||||||
# ------------------------------------
|
|
||||||
"""
|
|
||||||
# Three Line Strike: values [0, -100, 100]
|
|
||||||
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
|
|
||||||
|
|
||||||
# Spinning Top: values [0, -100, 100]
|
|
||||||
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
|
|
||||||
|
|
||||||
# Engulfing: values [0, -100, 100]
|
|
||||||
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
|
|
||||||
|
|
||||||
# Harami: values [0, -100, 100]
|
|
||||||
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
|
|
||||||
|
|
||||||
# Three Outside Up/Down: values [0, -100, 100]
|
|
||||||
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
|
|
||||||
|
|
||||||
# Three Inside Up/Down: values [0, -100, 100]
|
|
||||||
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Chart type
|
|
||||||
# ------------------------------------
|
|
||||||
# Heikinashi stategy
|
|
||||||
heikinashi = qtpylib.heikinashi(dataframe)
|
|
||||||
dataframe['ha_open'] = heikinashi['open']
|
|
||||||
dataframe['ha_close'] = heikinashi['close']
|
|
||||||
dataframe['ha_high'] = heikinashi['high']
|
|
||||||
dataframe['ha_low'] = heikinashi['low']
|
|
||||||
|
|
||||||
return dataframe
|
|
||||||
|
|
||||||
|
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
@ -230,20 +55,8 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
|||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
strategy = Strategy()
|
||||||
(
|
return strategy.populate_buy_trend(dataframe=dataframe)
|
||||||
(dataframe['rsi'] < 35) &
|
|
||||||
(dataframe['fastd'] < 35) &
|
|
||||||
(dataframe['adx'] > 30) &
|
|
||||||
(dataframe['plus_di'] > 0.5)
|
|
||||||
) |
|
|
||||||
(
|
|
||||||
(dataframe['adx'] > 65) &
|
|
||||||
(dataframe['plus_di'] > 0.5)
|
|
||||||
),
|
|
||||||
'buy'] = 1
|
|
||||||
|
|
||||||
return dataframe
|
|
||||||
|
|
||||||
|
|
||||||
def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
@ -252,21 +65,8 @@ def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
|||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
strategy = Strategy()
|
||||||
(
|
return strategy.populate_sell_trend(dataframe=dataframe)
|
||||||
(
|
|
||||||
(qtpylib.crossed_above(dataframe['rsi'], 70)) |
|
|
||||||
(qtpylib.crossed_above(dataframe['fastd'], 70))
|
|
||||||
) &
|
|
||||||
(dataframe['adx'] > 10) &
|
|
||||||
(dataframe['minus_di'] > 0)
|
|
||||||
) |
|
|
||||||
(
|
|
||||||
(dataframe['adx'] > 70) &
|
|
||||||
(dataframe['minus_di'] > 0.5)
|
|
||||||
),
|
|
||||||
'sell'] = 1
|
|
||||||
return dataframe
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_ticker(ticker_history: List[Dict]) -> DataFrame:
|
def analyze_ticker(ticker_history: List[Dict]) -> DataFrame:
|
||||||
|
@ -19,6 +19,7 @@ from freqtrade.fiat_convert import CryptoToFiatConverter
|
|||||||
from freqtrade.misc import (State, get_state, load_config, parse_args,
|
from freqtrade.misc import (State, get_state, load_config, parse_args,
|
||||||
throttle, update_state)
|
throttle, update_state)
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
logger = logging.getLogger('freqtrade')
|
logger = logging.getLogger('freqtrade')
|
||||||
|
|
||||||
@ -235,14 +236,16 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) -
|
|||||||
Based an earlier trade and current price and ROI configuration, decides whether bot should sell
|
Based an earlier trade and current price and ROI configuration, decides whether bot should sell
|
||||||
:return True if bot should sell at current rate
|
:return True if bot should sell at current rate
|
||||||
"""
|
"""
|
||||||
|
strategy = Strategy()
|
||||||
|
|
||||||
current_profit = trade.calc_profit_percent(current_rate)
|
current_profit = trade.calc_profit_percent(current_rate)
|
||||||
if 'stoploss' in _CONF and current_profit < float(_CONF['stoploss']):
|
if strategy.stoploss is not None and current_profit < float(strategy.stoploss):
|
||||||
logger.debug('Stop loss hit.')
|
logger.debug('Stop loss hit.')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check if time matches and current rate is above threshold
|
# Check if time matches and current rate is above threshold
|
||||||
time_diff = (current_time - trade.open_date).total_seconds() / 60
|
time_diff = (current_time - trade.open_date).total_seconds() / 60
|
||||||
for duration, threshold in sorted(_CONF['minimal_roi'].items()):
|
for duration, threshold in sorted(strategy.minimal_roi.items()):
|
||||||
if time_diff > float(duration) and current_profit > threshold:
|
if time_diff > float(duration) and current_profit > threshold:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -378,6 +381,9 @@ def init(config: dict, db_url: Optional[str] = None) -> None:
|
|||||||
persistence.init(config, db_url)
|
persistence.init(config, db_url)
|
||||||
exchange.init(config)
|
exchange.init(config)
|
||||||
|
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
# Set initial application state
|
# Set initial application state
|
||||||
initial_state = config.get('initial_state')
|
initial_state = config.get('initial_state')
|
||||||
if initial_state:
|
if initial_state:
|
||||||
@ -445,6 +451,9 @@ def main(sysargv=sys.argv[1:]) -> None:
|
|||||||
# Load and validate configuration
|
# Load and validate configuration
|
||||||
_CONF = load_config(args.config)
|
_CONF = load_config(args.config)
|
||||||
|
|
||||||
|
# Add the strategy file to use
|
||||||
|
_CONF.update({'strategy': args.strategy})
|
||||||
|
|
||||||
# Initialize all modules and start main loop
|
# Initialize all modules and start main loop
|
||||||
if args.dynamic_whitelist:
|
if args.dynamic_whitelist:
|
||||||
logger.info('Using dynamically generated whitelist. (--dynamic-whitelist detected)')
|
logger.info('Using dynamically generated whitelist. (--dynamic-whitelist detected)')
|
||||||
@ -462,6 +471,7 @@ def main(sysargv=sys.argv[1:]) -> None:
|
|||||||
try:
|
try:
|
||||||
init(_CONF)
|
init(_CONF)
|
||||||
old_state = None
|
old_state = None
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
new_state = get_state()
|
new_state = get_state()
|
||||||
# Log state transition
|
# Log state transition
|
||||||
|
@ -124,6 +124,14 @@ def common_args_parser(description: str):
|
|||||||
type=str,
|
type=str,
|
||||||
metavar='PATH',
|
metavar='PATH',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-s', '--strategy',
|
||||||
|
help='specify strategy file (default: freqtrade/strategy/default_strategy.py)',
|
||||||
|
dest='strategy',
|
||||||
|
default='.default_strategy',
|
||||||
|
type=str,
|
||||||
|
metavar='PATH',
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -380,7 +388,6 @@ CONF_SCHEMA = {
|
|||||||
'stake_amount',
|
'stake_amount',
|
||||||
'fiat_display_currency',
|
'fiat_display_currency',
|
||||||
'dry_run',
|
'dry_run',
|
||||||
'minimal_roi',
|
|
||||||
'bid_strategy',
|
'bid_strategy',
|
||||||
'telegram'
|
'telegram'
|
||||||
]
|
]
|
||||||
|
@ -14,6 +14,7 @@ from freqtrade.analyze import populate_buy_trend, populate_sell_trend
|
|||||||
from freqtrade.exchange import Bittrex
|
from freqtrade.exchange import Bittrex
|
||||||
from freqtrade.main import min_roi_reached
|
from freqtrade.main import min_roi_reached
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -199,6 +200,11 @@ def start(args):
|
|||||||
logger.info('Using max_open_trades: %s ...', config['max_open_trades'])
|
logger.info('Using max_open_trades: %s ...', config['max_open_trades'])
|
||||||
max_open_trades = config['max_open_trades']
|
max_open_trades = config['max_open_trades']
|
||||||
|
|
||||||
|
# init the strategy to use
|
||||||
|
config.update({'strategy': args.strategy})
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
# Monkey patch config
|
# Monkey patch config
|
||||||
from freqtrade import main
|
from freqtrade import main
|
||||||
main._CONF = config
|
main._CONF = config
|
||||||
|
@ -7,11 +7,10 @@ import sys
|
|||||||
import pickle
|
import pickle
|
||||||
import signal
|
import signal
|
||||||
import os
|
import os
|
||||||
from functools import reduce
|
|
||||||
from math import exp
|
from math import exp
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe
|
from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, space_eval, tpe
|
||||||
from hyperopt.mongoexp import MongoTrials
|
from hyperopt.mongoexp import MongoTrials
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ from freqtrade.exchange import Bittrex
|
|||||||
from freqtrade.misc import load_config
|
from freqtrade.misc import load_config
|
||||||
from freqtrade.optimize.backtesting import backtest
|
from freqtrade.optimize.backtesting import backtest
|
||||||
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf
|
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf
|
||||||
from freqtrade.vendor.qtpylib.indicators import crossed_above
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
# Remove noisy log messages
|
# Remove noisy log messages
|
||||||
logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING)
|
logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING)
|
||||||
@ -57,63 +56,6 @@ from freqtrade import main # noqa
|
|||||||
main._CONF = OPTIMIZE_CONFIG
|
main._CONF = OPTIMIZE_CONFIG
|
||||||
|
|
||||||
|
|
||||||
SPACE = {
|
|
||||||
'macd_below_zero': hp.choice('macd_below_zero', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'mfi': hp.choice('mfi', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)}
|
|
||||||
]),
|
|
||||||
'fastd': hp.choice('fastd', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)}
|
|
||||||
]),
|
|
||||||
'adx': hp.choice('adx', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)}
|
|
||||||
]),
|
|
||||||
'rsi': hp.choice('rsi', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
|
||||||
]),
|
|
||||||
'uptrend_long_ema': hp.choice('uptrend_long_ema', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'uptrend_short_ema': hp.choice('uptrend_short_ema', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'over_sar': hp.choice('over_sar', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'green_candle': hp.choice('green_candle', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'uptrend_sma': hp.choice('uptrend_sma', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'trigger': hp.choice('trigger', [
|
|
||||||
{'type': 'lower_bb'},
|
|
||||||
{'type': 'lower_bb_tema'},
|
|
||||||
{'type': 'faststoch10'},
|
|
||||||
{'type': 'ao_cross_zero'},
|
|
||||||
{'type': 'ema3_cross_ema10'},
|
|
||||||
{'type': 'macd_cross_signal'},
|
|
||||||
{'type': 'sar_reversal'},
|
|
||||||
{'type': 'ht_sine'},
|
|
||||||
{'type': 'heiken_reversal_bull'},
|
|
||||||
{'type': 'di_cross'},
|
|
||||||
]),
|
|
||||||
'stoploss': hp.uniform('stoploss', -0.5, -0.02),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def save_trials(trials, trials_path=TRIALS_FILE):
|
def save_trials(trials, trials_path=TRIALS_FILE):
|
||||||
"""Save hyperopt trials to file"""
|
"""Save hyperopt trials to file"""
|
||||||
logger.info('Saving Trials to \'{}\''.format(trials_path))
|
logger.info('Saving Trials to \'{}\''.format(trials_path))
|
||||||
@ -162,7 +104,9 @@ def optimizer(params):
|
|||||||
global _CURRENT_TRIES
|
global _CURRENT_TRIES
|
||||||
|
|
||||||
from freqtrade.optimize import backtesting
|
from freqtrade.optimize import backtesting
|
||||||
backtesting.populate_buy_trend = buy_strategy_generator(params)
|
|
||||||
|
strategy = Strategy()
|
||||||
|
backtesting.populate_buy_trend = strategy.buy_strategy_generator(params)
|
||||||
|
|
||||||
results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'],
|
results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'],
|
||||||
'processed': PROCESSED,
|
'processed': PROCESSED,
|
||||||
@ -208,59 +152,8 @@ def format_results(results: DataFrame):
|
|||||||
results.duration.mean() * 5,
|
results.duration.mean() * 5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def buy_strategy_generator(params):
|
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
|
||||||
conditions = []
|
|
||||||
# GUARDS AND TRENDS
|
|
||||||
if params['uptrend_long_ema']['enabled']:
|
|
||||||
conditions.append(dataframe['ema50'] > dataframe['ema100'])
|
|
||||||
if params['macd_below_zero']['enabled']:
|
|
||||||
conditions.append(dataframe['macd'] < 0)
|
|
||||||
if params['uptrend_short_ema']['enabled']:
|
|
||||||
conditions.append(dataframe['ema5'] > dataframe['ema10'])
|
|
||||||
if params['mfi']['enabled']:
|
|
||||||
conditions.append(dataframe['mfi'] < params['mfi']['value'])
|
|
||||||
if params['fastd']['enabled']:
|
|
||||||
conditions.append(dataframe['fastd'] < params['fastd']['value'])
|
|
||||||
if params['adx']['enabled']:
|
|
||||||
conditions.append(dataframe['adx'] > params['adx']['value'])
|
|
||||||
if params['rsi']['enabled']:
|
|
||||||
conditions.append(dataframe['rsi'] < params['rsi']['value'])
|
|
||||||
if params['over_sar']['enabled']:
|
|
||||||
conditions.append(dataframe['close'] > dataframe['sar'])
|
|
||||||
if params['green_candle']['enabled']:
|
|
||||||
conditions.append(dataframe['close'] > dataframe['open'])
|
|
||||||
if params['uptrend_sma']['enabled']:
|
|
||||||
prevsma = dataframe['sma'].shift(1)
|
|
||||||
conditions.append(dataframe['sma'] > prevsma)
|
|
||||||
|
|
||||||
# TRIGGERS
|
|
||||||
triggers = {
|
|
||||||
'lower_bb': (dataframe['close'] < dataframe['bb_lowerband']),
|
|
||||||
'lower_bb_tema': (dataframe['tema'] < dataframe['bb_lowerband']),
|
|
||||||
'faststoch10': (crossed_above(dataframe['fastd'], 10.0)),
|
|
||||||
'ao_cross_zero': (crossed_above(dataframe['ao'], 0.0)),
|
|
||||||
'ema3_cross_ema10': (crossed_above(dataframe['ema3'], dataframe['ema10'])),
|
|
||||||
'macd_cross_signal': (crossed_above(dataframe['macd'], dataframe['macdsignal'])),
|
|
||||||
'sar_reversal': (crossed_above(dataframe['close'], dataframe['sar'])),
|
|
||||||
'ht_sine': (crossed_above(dataframe['htleadsine'], dataframe['htsine'])),
|
|
||||||
'heiken_reversal_bull': (crossed_above(dataframe['ha_close'], dataframe['ha_open'])) &
|
|
||||||
(dataframe['ha_low'] == dataframe['ha_open']),
|
|
||||||
'di_cross': (crossed_above(dataframe['plus_di'], dataframe['minus_di'])),
|
|
||||||
}
|
|
||||||
conditions.append(triggers.get(params['trigger']['type']))
|
|
||||||
|
|
||||||
dataframe.loc[
|
|
||||||
reduce(lambda x, y: x & y, conditions),
|
|
||||||
'buy'] = 1
|
|
||||||
|
|
||||||
return dataframe
|
|
||||||
return populate_buy_trend
|
|
||||||
|
|
||||||
|
|
||||||
def start(args):
|
def start(args):
|
||||||
global TOTAL_TRIES, PROCESSED, SPACE, TRIALS, _CURRENT_TRIES
|
global TOTAL_TRIES, PROCESSED, TRIALS, _CURRENT_TRIES
|
||||||
|
|
||||||
TOTAL_TRIES = args.epochs
|
TOTAL_TRIES = args.epochs
|
||||||
|
|
||||||
@ -275,6 +168,12 @@ def start(args):
|
|||||||
logger.info('Using config: %s ...', args.config)
|
logger.info('Using config: %s ...', args.config)
|
||||||
config = load_config(args.config)
|
config = load_config(args.config)
|
||||||
pairs = config['exchange']['pair_whitelist']
|
pairs = config['exchange']['pair_whitelist']
|
||||||
|
|
||||||
|
# init the strategy to use
|
||||||
|
config.update({'strategy': args.strategy})
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
timerange = misc.parse_timerange(args.timerange)
|
timerange = misc.parse_timerange(args.timerange)
|
||||||
data = optimize.load_data(args.datadir, pairs=pairs,
|
data = optimize.load_data(args.datadir, pairs=pairs,
|
||||||
ticker_interval=args.ticker_interval,
|
ticker_interval=args.ticker_interval,
|
||||||
@ -303,7 +202,7 @@ def start(args):
|
|||||||
try:
|
try:
|
||||||
best_parameters = fmin(
|
best_parameters = fmin(
|
||||||
fn=optimizer,
|
fn=optimizer,
|
||||||
space=SPACE,
|
space=strategy.hyperopt_space(),
|
||||||
algo=tpe.suggest,
|
algo=tpe.suggest,
|
||||||
max_evals=TOTAL_TRIES,
|
max_evals=TOTAL_TRIES,
|
||||||
trials=TRIALS
|
trials=TRIALS
|
||||||
@ -319,7 +218,10 @@ def start(args):
|
|||||||
|
|
||||||
# Improve best parameter logging display
|
# Improve best parameter logging display
|
||||||
if best_parameters:
|
if best_parameters:
|
||||||
best_parameters = space_eval(SPACE, best_parameters)
|
best_parameters = space_eval(
|
||||||
|
strategy.hyperopt_space(),
|
||||||
|
best_parameters
|
||||||
|
)
|
||||||
|
|
||||||
logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4))
|
logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4))
|
||||||
logger.info('Best Result:\n%s', best_result)
|
logger.info('Best Result:\n%s', best_result)
|
||||||
|
0
freqtrade/strategy/__init__.py
Normal file
0
freqtrade/strategy/__init__.py
Normal file
262
freqtrade/strategy/default_strategy.py
Normal file
262
freqtrade/strategy/default_strategy.py
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
import talib.abstract as ta
|
||||||
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from pandas import DataFrame
|
||||||
|
from hyperopt import hp
|
||||||
|
from functools import reduce
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
class_name = 'DefaultStrategy'
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultStrategy(IStrategy):
|
||||||
|
"""
|
||||||
|
Default Strategy provided by freqtrade bot.
|
||||||
|
You can override it with your own strategy
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Minimal ROI designed for the strategy
|
||||||
|
minimal_roi = {
|
||||||
|
"40": 0.0,
|
||||||
|
"30": 0.01,
|
||||||
|
"20": 0.02,
|
||||||
|
"0": 0.04
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optimal stoploss designed for the strategy
|
||||||
|
stoploss = -0.10
|
||||||
|
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
|
||||||
|
Performance Note: For the best performance be frugal on the number of indicators
|
||||||
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Momentum Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# ADX
|
||||||
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
|
|
||||||
|
# Awesome oscillator
|
||||||
|
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
||||||
|
|
||||||
|
# MACD
|
||||||
|
macd = ta.MACD(dataframe)
|
||||||
|
dataframe['macd'] = macd['macd']
|
||||||
|
dataframe['macdsignal'] = macd['macdsignal']
|
||||||
|
dataframe['macdhist'] = macd['macdhist']
|
||||||
|
|
||||||
|
# MFI
|
||||||
|
dataframe['mfi'] = ta.MFI(dataframe)
|
||||||
|
|
||||||
|
# Minus Directional Indicator / Movement
|
||||||
|
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
||||||
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
# Plus Directional Indicator / Movement
|
||||||
|
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
||||||
|
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||||
|
|
||||||
|
# RSI
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe)
|
||||||
|
|
||||||
|
# Stoch fast
|
||||||
|
stoch_fast = ta.STOCHF(dataframe)
|
||||||
|
dataframe['fastd'] = stoch_fast['fastd']
|
||||||
|
dataframe['fastk'] = stoch_fast['fastk']
|
||||||
|
|
||||||
|
# Overlap Studies
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# Previous Bollinger bands
|
||||||
|
# Because ta.BBANDS implementation is broken with small numbers, it actually
|
||||||
|
# returns middle band for all the three bands. Switch to qtpylib.bollinger_bands
|
||||||
|
# and use middle band instead.
|
||||||
|
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
||||||
|
"""
|
||||||
|
# Bollinger bands
|
||||||
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
"""
|
||||||
|
|
||||||
|
# EMA - Exponential Moving Average
|
||||||
|
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
||||||
|
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
||||||
|
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
||||||
|
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
||||||
|
|
||||||
|
# SAR Parabol
|
||||||
|
dataframe['sar'] = ta.SAR(dataframe)
|
||||||
|
|
||||||
|
# SMA - Simple Moving Average
|
||||||
|
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||||
|
|
||||||
|
# TEMA - Triple Exponential Moving Average
|
||||||
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
|
|
||||||
|
# Cycle Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
# Hilbert Transform Indicator - SineWave
|
||||||
|
hilbert = ta.HT_SINE(dataframe)
|
||||||
|
dataframe['htsine'] = hilbert['sine']
|
||||||
|
dataframe['htleadsine'] = hilbert['leadsine']
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['rsi'] < 35) &
|
||||||
|
(dataframe['fastd'] < 35) &
|
||||||
|
(dataframe['adx'] > 30) &
|
||||||
|
(dataframe['plus_di'] > 0.5)
|
||||||
|
) |
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 65) &
|
||||||
|
(dataframe['plus_di'] > 0.5)
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(qtpylib.crossed_above(dataframe['rsi'], 70)) |
|
||||||
|
(qtpylib.crossed_above(dataframe['fastd'], 70))
|
||||||
|
) &
|
||||||
|
(dataframe['adx'] > 10) &
|
||||||
|
(dataframe['minus_di'] > 0)
|
||||||
|
) |
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 70) &
|
||||||
|
(dataframe['minus_di'] > 0.5)
|
||||||
|
),
|
||||||
|
'sell'] = 1
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def hyperopt_space(self) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Define your Hyperopt space for the strategy
|
||||||
|
"""
|
||||||
|
space = {
|
||||||
|
'mfi': hp.choice('mfi', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)}
|
||||||
|
]),
|
||||||
|
'fastd': hp.choice('fastd', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)}
|
||||||
|
]),
|
||||||
|
'adx': hp.choice('adx', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)}
|
||||||
|
]),
|
||||||
|
'rsi': hp.choice('rsi', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
||||||
|
]),
|
||||||
|
'uptrend_long_ema': hp.choice('uptrend_long_ema', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'uptrend_short_ema': hp.choice('uptrend_short_ema', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'over_sar': hp.choice('over_sar', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'green_candle': hp.choice('green_candle', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'uptrend_sma': hp.choice('uptrend_sma', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'trigger': hp.choice('trigger', [
|
||||||
|
{'type': 'lower_bb'},
|
||||||
|
{'type': 'faststoch10'},
|
||||||
|
{'type': 'ao_cross_zero'},
|
||||||
|
{'type': 'ema5_cross_ema10'},
|
||||||
|
{'type': 'macd_cross_signal'},
|
||||||
|
{'type': 'sar_reversal'},
|
||||||
|
{'type': 'stochf_cross'},
|
||||||
|
{'type': 'ht_sine'},
|
||||||
|
]),
|
||||||
|
'stoploss': hp.uniform('stoploss', -0.5, -0.02),
|
||||||
|
}
|
||||||
|
return space
|
||||||
|
|
||||||
|
def buy_strategy_generator(self, params) -> None:
|
||||||
|
"""
|
||||||
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
|
"""
|
||||||
|
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
|
conditions = []
|
||||||
|
# GUARDS AND TRENDS
|
||||||
|
if params['uptrend_long_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema50'] > dataframe['ema100'])
|
||||||
|
if params['uptrend_short_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema5'] > dataframe['ema10'])
|
||||||
|
if params['mfi']['enabled']:
|
||||||
|
conditions.append(dataframe['mfi'] < params['mfi']['value'])
|
||||||
|
if params['fastd']['enabled']:
|
||||||
|
conditions.append(dataframe['fastd'] < params['fastd']['value'])
|
||||||
|
if params['adx']['enabled']:
|
||||||
|
conditions.append(dataframe['adx'] > params['adx']['value'])
|
||||||
|
if params['rsi']['enabled']:
|
||||||
|
conditions.append(dataframe['rsi'] < params['rsi']['value'])
|
||||||
|
if params['over_sar']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['sar'])
|
||||||
|
if params['green_candle']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['open'])
|
||||||
|
if params['uptrend_sma']['enabled']:
|
||||||
|
prevsma = dataframe['sma'].shift(1)
|
||||||
|
conditions.append(dataframe['sma'] > prevsma)
|
||||||
|
|
||||||
|
# TRIGGERS
|
||||||
|
triggers = {
|
||||||
|
'lower_bb': dataframe['tema'] <= dataframe['blower'],
|
||||||
|
'faststoch10': (qtpylib.crossed_above(dataframe['fastd'], 10.0)),
|
||||||
|
'ao_cross_zero': (qtpylib.crossed_above(dataframe['ao'], 0.0)),
|
||||||
|
'ema5_cross_ema10': (
|
||||||
|
qtpylib.crossed_above(dataframe['ema5'], dataframe['ema10'])
|
||||||
|
),
|
||||||
|
'macd_cross_signal': (
|
||||||
|
qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])
|
||||||
|
),
|
||||||
|
'sar_reversal': (qtpylib.crossed_above(dataframe['close'], dataframe['sar'])),
|
||||||
|
'stochf_cross': (qtpylib.crossed_above(dataframe['fastk'], dataframe['fastd'])),
|
||||||
|
'ht_sine': (qtpylib.crossed_above(dataframe['htleadsine'], dataframe['htsine'])),
|
||||||
|
}
|
||||||
|
conditions.append(triggers.get(params['trigger']['type']))
|
||||||
|
|
||||||
|
dataframe.loc[
|
||||||
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
return populate_buy_trend
|
56
freqtrade/strategy/interface.py
Normal file
56
freqtrade/strategy/interface.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from pandas import DataFrame
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
|
||||||
|
class IStrategy(ABC):
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""
|
||||||
|
Name of the strategy.
|
||||||
|
:return: str representation of the class name
|
||||||
|
"""
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
|
"""
|
||||||
|
Attributes you can use:
|
||||||
|
minimal_roi -> Dict: Minimal ROI designed for the strategy
|
||||||
|
stoploss -> float: ptimal stoploss designed for the strategy
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Populate indicators that will be used in the Buy and Sell strategy
|
||||||
|
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def hyperopt_space(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Define your Hyperopt space for the strategy
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def buy_strategy_generator(self, params) -> None:
|
||||||
|
"""
|
||||||
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
|
"""
|
165
freqtrade/strategy/strategy.py
Normal file
165
freqtrade/strategy/strategy.py
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
from pandas import DataFrame
|
||||||
|
from typing import Dict
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
|
||||||
|
|
||||||
|
sys.path.insert(0, r'../../user_data/strategies')
|
||||||
|
|
||||||
|
|
||||||
|
class Strategy(object):
|
||||||
|
__instance = None
|
||||||
|
|
||||||
|
DEFAULT_STRATEGY = 'default_strategy'
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
if Strategy.__instance is None:
|
||||||
|
Strategy.__instance = object.__new__(cls)
|
||||||
|
return Strategy.__instance
|
||||||
|
|
||||||
|
def init(self, config):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Verify the strategy is in the configuration, otherwise fallback to the default strategy
|
||||||
|
if 'strategy' in config:
|
||||||
|
strategy = config['strategy']
|
||||||
|
else:
|
||||||
|
strategy = self.DEFAULT_STRATEGY
|
||||||
|
|
||||||
|
# Load the strategy
|
||||||
|
self._load_strategy(strategy)
|
||||||
|
|
||||||
|
# Set attributes
|
||||||
|
# Check if we need to override configuration
|
||||||
|
if 'minimal_roi' in config:
|
||||||
|
self.custom_strategy.minimal_roi = config['minimal_roi']
|
||||||
|
self.logger.info("Override strategy \'minimal_roi\' with value in config file.")
|
||||||
|
|
||||||
|
if 'stoploss' in config:
|
||||||
|
self.custom_strategy.stoploss = config['stoploss']
|
||||||
|
self.logger.info("Override strategy \'stoploss\' with value in config file.")
|
||||||
|
|
||||||
|
self.minimal_roi = self.custom_strategy.minimal_roi
|
||||||
|
self.stoploss = self.custom_strategy.stoploss
|
||||||
|
|
||||||
|
def _load_strategy(self, strategy_name: str) -> None:
|
||||||
|
"""
|
||||||
|
Search and load the custom strategy. If no strategy found, fallback on the default strategy
|
||||||
|
Set the object into self.custom_strategy
|
||||||
|
:param strategy_name: name of the module to import
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Start by sanitizing the file name (remove any extensions)
|
||||||
|
strategy_name = self._sanitize_module_name(filename=strategy_name)
|
||||||
|
|
||||||
|
# Search where can be the strategy file
|
||||||
|
path = self._search_strategy(filename=strategy_name)
|
||||||
|
|
||||||
|
# Load the strategy
|
||||||
|
self.custom_strategy = self._load_class(path + strategy_name)
|
||||||
|
|
||||||
|
# Fallback to the default strategy
|
||||||
|
except (ImportError, TypeError):
|
||||||
|
self.custom_strategy = self._load_class('.' + self.DEFAULT_STRATEGY)
|
||||||
|
|
||||||
|
def _load_class(self, filename: str) -> IStrategy:
|
||||||
|
"""
|
||||||
|
Import a strategy as a module
|
||||||
|
:param filename: path to the strategy (path from freqtrade/strategy/)
|
||||||
|
:return: return the strategy class
|
||||||
|
"""
|
||||||
|
module = importlib.import_module(filename, __package__)
|
||||||
|
custom_strategy = getattr(module, module.class_name)
|
||||||
|
|
||||||
|
self.logger.info("Load strategy class: {} ({}.py)".format(module.class_name, filename))
|
||||||
|
return custom_strategy()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sanitize_module_name(filename: str) -> str:
|
||||||
|
"""
|
||||||
|
Remove any extension from filename
|
||||||
|
:param filename: filename to sanatize
|
||||||
|
:return: return the filename without extensions
|
||||||
|
"""
|
||||||
|
filename = os.path.basename(filename)
|
||||||
|
filename = os.path.splitext(filename)[0]
|
||||||
|
return filename
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _search_strategy(filename: str) -> str:
|
||||||
|
"""
|
||||||
|
Search for the Strategy file in different folder
|
||||||
|
1. search into the user_data/strategies folder
|
||||||
|
2. search into the freqtrade/strategy folder
|
||||||
|
3. if nothing found, return None
|
||||||
|
:param strategy_name: module name to search
|
||||||
|
:return: module path where is the strategy
|
||||||
|
"""
|
||||||
|
pwd = os.path.dirname(os.path.realpath(__file__)) + '/'
|
||||||
|
user_data = os.path.join(pwd, '..', '..', 'user_data', 'strategies', filename + '.py')
|
||||||
|
strategy_folder = os.path.join(pwd, filename + '.py')
|
||||||
|
|
||||||
|
path = None
|
||||||
|
if os.path.isfile(user_data):
|
||||||
|
path = 'user_data.strategies.'
|
||||||
|
elif os.path.isfile(strategy_folder):
|
||||||
|
path = '.'
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def minimal_roi(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Minimal ROI designed for the strategy
|
||||||
|
:return: Dict: Value for the Minimal ROI
|
||||||
|
"""
|
||||||
|
return
|
||||||
|
|
||||||
|
def stoploss(self) -> float:
|
||||||
|
"""
|
||||||
|
Optimal stoploss designed for the strategy
|
||||||
|
:return: float | return None to disable it
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.stoploss
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Populate indicators that will be used in the Buy and Sell strategy
|
||||||
|
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.populate_indicators(dataframe)
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.populate_buy_trend(dataframe)
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.populate_sell_trend(dataframe)
|
||||||
|
|
||||||
|
def hyperopt_space(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Define your Hyperopt space for the strategy
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.hyperopt_space()
|
||||||
|
|
||||||
|
def buy_strategy_generator(self, params) -> None:
|
||||||
|
"""
|
||||||
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.buy_strategy_generator(params)
|
36
freqtrade/tests/strategy/test_default_strategy.py
Normal file
36
freqtrade/tests/strategy/test_default_strategy.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import json
|
||||||
|
import pytest
|
||||||
|
from pandas import DataFrame
|
||||||
|
from freqtrade.strategy.default_strategy import DefaultStrategy, class_name
|
||||||
|
from freqtrade.analyze import parse_ticker_dataframe
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def result():
|
||||||
|
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
||||||
|
return parse_ticker_dataframe(json.load(data_file))
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_strategy_class_name():
|
||||||
|
assert class_name == DefaultStrategy.__name__
|
||||||
|
|
||||||
|
def test_default_strategy_structure():
|
||||||
|
assert hasattr(DefaultStrategy, 'minimal_roi')
|
||||||
|
assert hasattr(DefaultStrategy, 'stoploss')
|
||||||
|
assert hasattr(DefaultStrategy, 'populate_indicators')
|
||||||
|
assert hasattr(DefaultStrategy, 'populate_buy_trend')
|
||||||
|
assert hasattr(DefaultStrategy, 'populate_sell_trend')
|
||||||
|
assert hasattr(DefaultStrategy, 'hyperopt_space')
|
||||||
|
assert hasattr(DefaultStrategy, 'buy_strategy_generator')
|
||||||
|
|
||||||
|
def test_default_strategy(result):
|
||||||
|
strategy = DefaultStrategy()
|
||||||
|
|
||||||
|
assert type(strategy.minimal_roi) is dict
|
||||||
|
assert type(strategy.stoploss) is float
|
||||||
|
indicators = strategy.populate_indicators(result)
|
||||||
|
assert type(indicators) is DataFrame
|
||||||
|
assert type(strategy.populate_buy_trend(indicators)) is DataFrame
|
||||||
|
assert type(strategy.populate_sell_trend(indicators)) is DataFrame
|
||||||
|
assert type(strategy.hyperopt_space()) is dict
|
||||||
|
assert callable(strategy.buy_strategy_generator({}))
|
132
freqtrade/tests/strategy/test_strategy.py
Normal file
132
freqtrade/tests/strategy/test_strategy.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import pytest
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
from freqtrade.analyze import parse_ticker_dataframe
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def result():
|
||||||
|
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
||||||
|
return parse_ticker_dataframe(json.load(data_file))
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanitize_module_name():
|
||||||
|
assert Strategy._sanitize_module_name('default_strategy') == 'default_strategy'
|
||||||
|
assert Strategy._sanitize_module_name('default_strategy.py') == 'default_strategy'
|
||||||
|
assert Strategy._sanitize_module_name('../default_strategy.py') == 'default_strategy'
|
||||||
|
assert Strategy._sanitize_module_name('../default_strategy') == 'default_strategy'
|
||||||
|
assert Strategy._sanitize_module_name('.default_strategy') == '.default_strategy'
|
||||||
|
assert Strategy._sanitize_module_name('foo-bar') == 'foo-bar'
|
||||||
|
assert Strategy._sanitize_module_name('foo/bar') == 'bar'
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_strategy():
|
||||||
|
assert Strategy._search_strategy('default_strategy') == '.'
|
||||||
|
assert Strategy._search_strategy('super_duper') is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_structure():
|
||||||
|
assert hasattr(Strategy, 'init')
|
||||||
|
assert hasattr(Strategy, 'minimal_roi')
|
||||||
|
assert hasattr(Strategy, 'stoploss')
|
||||||
|
assert hasattr(Strategy, 'populate_indicators')
|
||||||
|
assert hasattr(Strategy, 'populate_buy_trend')
|
||||||
|
assert hasattr(Strategy, 'populate_sell_trend')
|
||||||
|
assert hasattr(Strategy, 'hyperopt_space')
|
||||||
|
assert hasattr(Strategy, 'buy_strategy_generator')
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_strategy(result):
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
assert not hasattr(Strategy, 'custom_strategy')
|
||||||
|
strategy._load_strategy('default_strategy')
|
||||||
|
|
||||||
|
assert not hasattr(Strategy, 'custom_strategy')
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'populate_indicators')
|
||||||
|
assert 'adx' in strategy.populate_indicators(result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy(result):
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init({'strategy': 'default_strategy'})
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'minimal_roi')
|
||||||
|
assert strategy.minimal_roi['0'] == 0.04
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'stoploss')
|
||||||
|
assert strategy.stoploss == -0.10
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'populate_indicators')
|
||||||
|
assert 'adx' in strategy.populate_indicators(result)
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'populate_buy_trend')
|
||||||
|
dataframe = strategy.populate_buy_trend(strategy.populate_indicators(result))
|
||||||
|
assert 'buy' in dataframe.columns
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'populate_sell_trend')
|
||||||
|
dataframe = strategy.populate_sell_trend(strategy.populate_indicators(result))
|
||||||
|
assert 'sell' in dataframe.columns
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'hyperopt_space')
|
||||||
|
assert 'adx' in strategy.hyperopt_space()
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'buy_strategy_generator')
|
||||||
|
assert callable(strategy.buy_strategy_generator({}))
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_override_minimal_roi(caplog):
|
||||||
|
config = {
|
||||||
|
'strategy': 'default_strategy',
|
||||||
|
'minimal_roi': {
|
||||||
|
"0": 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'minimal_roi')
|
||||||
|
assert strategy.minimal_roi['0'] == 0.5
|
||||||
|
assert ('freqtrade.strategy.strategy',
|
||||||
|
logging.INFO,
|
||||||
|
'Override strategy \'minimal_roi\' with value in config file.'
|
||||||
|
) in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_override_stoploss(caplog):
|
||||||
|
config = {
|
||||||
|
'strategy': 'default_strategy',
|
||||||
|
'stoploss': -0.5
|
||||||
|
}
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'stoploss')
|
||||||
|
assert strategy.stoploss == -0.5
|
||||||
|
assert ('freqtrade.strategy.strategy',
|
||||||
|
logging.INFO,
|
||||||
|
'Override strategy \'stoploss\' with value in config file.'
|
||||||
|
) in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_fallback_default_strategy():
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
assert not hasattr(Strategy, 'custom_strategy')
|
||||||
|
strategy._load_strategy('../../super_duper')
|
||||||
|
assert not hasattr(Strategy, 'custom_strategy')
|
||||||
|
|
||||||
|
def test_strategy_singleton():
|
||||||
|
strategy1 = Strategy()
|
||||||
|
strategy1.init({'strategy': 'default_strategy'})
|
||||||
|
|
||||||
|
assert hasattr(strategy1.custom_strategy, 'minimal_roi')
|
||||||
|
assert strategy1.minimal_roi['0'] == 0.04
|
||||||
|
|
||||||
|
strategy2 = Strategy()
|
||||||
|
assert hasattr(strategy2.custom_strategy, 'minimal_roi')
|
||||||
|
assert strategy2.minimal_roi['0'] == 0.04
|
@ -9,6 +9,7 @@ from pandas import DataFrame
|
|||||||
from freqtrade.analyze import (get_signal, parse_ticker_dataframe,
|
from freqtrade.analyze import (get_signal, parse_ticker_dataframe,
|
||||||
populate_buy_trend, populate_indicators,
|
populate_buy_trend, populate_indicators,
|
||||||
populate_sell_trend)
|
populate_sell_trend)
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -27,11 +28,17 @@ def test_dataframe_correct_length(result):
|
|||||||
|
|
||||||
|
|
||||||
def test_populates_buy_trend(result):
|
def test_populates_buy_trend(result):
|
||||||
|
# Load the default strategy for the unit test, because this logic is done in main.py
|
||||||
|
Strategy().init({'strategy': 'default_strategy'})
|
||||||
|
|
||||||
dataframe = populate_buy_trend(populate_indicators(result))
|
dataframe = populate_buy_trend(populate_indicators(result))
|
||||||
assert 'buy' in dataframe.columns
|
assert 'buy' in dataframe.columns
|
||||||
|
|
||||||
|
|
||||||
def test_populates_sell_trend(result):
|
def test_populates_sell_trend(result):
|
||||||
|
# Load the default strategy for the unit test, because this logic is done in main.py
|
||||||
|
Strategy().init({'strategy': 'default_strategy'})
|
||||||
|
|
||||||
dataframe = populate_sell_trend(populate_indicators(result))
|
dataframe = populate_sell_trend(populate_indicators(result))
|
||||||
assert 'sell' in dataframe.columns
|
assert 'sell' in dataframe.columns
|
||||||
|
|
||||||
|
0
user_data/data/.gitkeep
Normal file
0
user_data/data/.gitkeep
Normal file
0
user_data/strategies/__init__.py
Normal file
0
user_data/strategies/__init__.py
Normal file
129
user_data/strategies/test_strategy.py
Normal file
129
user_data/strategies/test_strategy.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
|
||||||
|
# --- Do not remove these libs ---
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from typing import Dict, List
|
||||||
|
from hyperopt import hp
|
||||||
|
from functools import reduce
|
||||||
|
from pandas import DataFrame
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
# Add your lib to import here
|
||||||
|
import talib.abstract as ta
|
||||||
|
|
||||||
|
|
||||||
|
# Update this variable if you change the class name
|
||||||
|
class_name = 'TestStrategy'
|
||||||
|
|
||||||
|
|
||||||
|
class TestStrategy(IStrategy):
|
||||||
|
"""
|
||||||
|
This is a test strategy to inspire you.
|
||||||
|
You can:
|
||||||
|
- Rename the class name (Do not forget to update class_name)
|
||||||
|
- Add any methods you want to build your strategy
|
||||||
|
- Add any lib you need to build your strategy
|
||||||
|
|
||||||
|
You must keep:
|
||||||
|
- the lib in the section "Do not remove these libs"
|
||||||
|
- the prototype for the methods: minimal_roi, stoploss, populate_indicators, populate_buy_trend,
|
||||||
|
populate_sell_trend, hyperopt_space, buy_strategy_generator
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Minimal ROI designed for the strategy.
|
||||||
|
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||||
|
minimal_roi = {
|
||||||
|
"40": 0.0,
|
||||||
|
"30": 0.01,
|
||||||
|
"20": 0.02,
|
||||||
|
"0": 0.04
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optimal stoploss designed for the strategy
|
||||||
|
# This attribute will be overridden if the config file contains "stoploss"
|
||||||
|
stoploss = -0.10
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
|
||||||
|
Performance Note: For the best performance be frugal on the number of indicators
|
||||||
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
|
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
||||||
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 30) &
|
||||||
|
(dataframe['tema'] <= dataframe['blower']) &
|
||||||
|
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 70) &
|
||||||
|
(dataframe['tema'] > dataframe['blower']) &
|
||||||
|
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
|
),
|
||||||
|
'sell'] = 1
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def hyperopt_space(self) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Define your Hyperopt space for the strategy
|
||||||
|
"""
|
||||||
|
space = {
|
||||||
|
'adx': hp.choice('adx', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)}
|
||||||
|
]),
|
||||||
|
'trigger': hp.choice('trigger', [
|
||||||
|
{'type': 'lower_bb'},
|
||||||
|
]),
|
||||||
|
'stoploss': hp.uniform('stoploss', -0.5, -0.02),
|
||||||
|
}
|
||||||
|
return space
|
||||||
|
|
||||||
|
def buy_strategy_generator(self, params) -> None:
|
||||||
|
"""
|
||||||
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
|
"""
|
||||||
|
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
|
conditions = []
|
||||||
|
# GUARDS AND TRENDS
|
||||||
|
if params['adx']['enabled']:
|
||||||
|
conditions.append(dataframe['adx'] > params['adx']['value'])
|
||||||
|
|
||||||
|
# TRIGGERS
|
||||||
|
triggers = {
|
||||||
|
'lower_bb': dataframe['tema'] <= dataframe['blower'],
|
||||||
|
}
|
||||||
|
conditions.append(triggers.get(params['trigger']['type']))
|
||||||
|
|
||||||
|
dataframe.loc[
|
||||||
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
return populate_buy_trend
|
Loading…
Reference in New Issue
Block a user