freqtrade / freqtrade-strategies

Free trading strategies for Freqtrade bot
GNU General Public License v3.0
3.34k stars 1.12k forks source link

scanning indicator parameters for Hyperopt optimization #61

Closed hikmetsezen closed 3 years ago

hikmetsezen commented 4 years ago

Dear community,

I wrote a optimization code for scanning parameters of indicator (customized one) for Hyperopt optimization. The indicator is based on a nonlinear smoothing filter on momentum. Buy-sell conditions are sign changes of its first derivative. It takes two parameters, ie. length, power. I compare backtest results on both Freqtrade and Tradingview to validate that indicator working properly.

For optimization I used this approach: link , basically scan two parameters for certain ranges. However, I could not get any result from Hyperopt optimization. The code is below. Thanks in advance, Best, Hikmet

Code:

from pandas import DataFrame
from typing import Dict, Any, Callable, List
from functools import reduce

from skopt.space import Categorical, Dimension, Integer, Real

import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.optimize.hyperopt_interface import IHyperOpt

jleni = 24
jlenf = 25
jpowi = 10
jpowf = 11

class jnonlinopt(IHyperOpt):

    @staticmethod
    def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
        for lenrange in range(jleni, jlenf):
            for powrange in range(jpowi, jpowf):
                dataframe[f'len({lenrange})pow({powrange})'] = qtpylib.jnonlin(dataframe['close'], lenrange, powrange)
        return dataframe

    @staticmethod
    def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
        """
        Define the buy strategy parameters to be used by hyperopt
        """
        def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
            """
            Buy strategy Hyperopt will build and use
            """
            conditions = []

            # TRIGGERS
            if 'trigger' in params:
                for lenrange in range(jleni, jlenf):
                    for powrange in range(jpowi, jpowf):
                        if params['trigger'] == f'len({lenrange})pow({powrange})':
                            conditions.append(dataframe[f'len({lenrange})pow({powrange})']>dataframe[f'len({lenrange})pow({powrange})'].shift(1)
                                & dataframe[f'len({lenrange})pow({powrange})'].shift(1)<dataframe[f'len({lenrange})pow({powrange})'].shift(2))

            if conditions:
                dataframe.loc[
                    reduce(lambda x, y: x & y, conditions),
                    'buy'] = 1

            return dataframe

        return populate_buy_trend

    @staticmethod
    def indicator_space() -> List[Dimension]:
        buyTriggerList = []
        for lenrange in range(jleni, jlenf):
            for powrange in range(jpowi, jpowf):
                buyTriggerList.append(
                    f'lenrange_({lenrange})_powrange_({powrange}')
        return [
            Categorical(buyTriggerList, name='trigger')
        ]

    @staticmethod
    def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
        """
        Define the sell strategy parameters to be used by hyperopt
        """
        def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
            """
            Sell strategy Hyperopt will build and use
            """
            # print(params)
            conditions = []

            # TRIGGERS
            if 'sell-trigger' in params:
                for lenrange in range(jleni, jlenf):
                    for powrange in range(jpowi, jpowf):
                        if params['trigger'] == f'len({lenrange})pow({powrange})':
                            conditions.append(dataframe[f'len({lenrange})pow({powrange})']<dataframe[f'len({lenrange})pow({powrange})'].shift(1)
                                & dataframe[f'len({lenrange})pow({powrange})'].shift(1)>dataframe[f'len({lenrange})pow({powrange})'].shift(2))
            if conditions:
                dataframe.loc[
                    reduce(lambda x, y: x & y, conditions),
                    'sell'] = 1

            return dataframe

        return populate_sell_trend

    @staticmethod
    def sell_indicator_space() -> List[Dimension]:
        """
        Define your Hyperopt space for searching sell strategy parameters
        """
        sellTriggerList = []
        for lenrange in range(jleni, jlenf):
            for powrange in range(jpowi, jpowf):
                sellTriggerList.append(
                    f'lenrange_({lenrange})_powrange_({powrange}')

        return [
            Categorical(sellTriggerList, name='sell-trigger')
        ]

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe['jnonlin'] > dataframe['jnonlin'].shift(1) )
                    & (dataframe['jnonlin'].shift(1) < dataframe['jnonlin'].shift(2) )
            ),
            'buy'] = 1

        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators. Should be a copy of from strategy
        must align to populate_indicators in this file
        Only used when --spaces does not include sell
        """
        dataframe.loc[
            (
                (dataframe['jnonlin'] < dataframe['jnonlin'].shift(1) )
                    & (dataframe['jnonlin'].shift(1) > dataframe['jnonlin'].shift(2) )
            ),
            'sell'] = 1

        return dataframe

    @staticmethod
    def generate_roi_table(params: Dict) -> Dict[int, float]:
        """
        Generate the ROI table that will be used by Hyperopt
        """
        roi_table = {}
        roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
        roi_table[params['roi_t3']] = params['roi_p1'] + params['roi_p2']
        roi_table[params['roi_t3'] + params['roi_t2']] = params['roi_p1']
        roi_table[params['roi_t3'] + params['roi_t2'] + params['roi_t1']] = 0

        return roi_table

    @staticmethod
    def stoploss_space() -> List[Dimension]:
        """
        Stoploss Value to search
        """
        return [
            Real(-0.5, -0.02, name='stoploss'),
        ]

    @staticmethod
    def roi_space() -> List[Dimension]:
        """
        Values to search for each ROI steps
        """
        return [
             Integer(10, 120, name='roi_t1'),
             Integer(10, 60, name='roi_t2'),
             Integer(10, 40, name='roi_t3'),
             Real(0.01, 0.04, name='roi_p1'),
             Real(0.01, 0.07, name='roi_p2'),
             Real(0.01, 0.20, name='roi_p3'),
        ]
xmatthias commented 3 years ago

There are now multiple possibilities for this (AverageHyperopt) - or by using the new parameter interface (which i personally would recommend).