Получение данных:

Вы можете найти полный код в Jupyter Notebbok на моем GitHub. Теперь давайте проверим эксперимент и выводы:

Я обнаружил, что внедрение показателей ТА радикально улучшает предсказание моей модели, а также нашел оптимальный временной диапазон и процентную цель для предсказания прорыва.

Я буду использовать значения свечей при открытии рынка по максимуму-минимуму (OHLC) с точностью до 30 минут по паре ETHUSD вместе с объемом, используя API Binance https://www.binance.com/es/binance- апи.

Используя обертку python https://python-binance.readthedocs.io/en/latest/ делаем примерно так

df = klines = client.get_historical_klines(“ETHUSDT”, Client.KLINE_INTERVAL_30MINUTE, “1 Jun, 2020”, “30 Dec, 2022”)

Индикаторы ТА

Затем вычислим все показатели ТА с помощью библиотеки Ta-lib https://mrjbq7.github.io/ta-lib/

  


cycle_Indicators = ['HT_DCPERIOD', 'HT_DCPHASE',  'HT_TRENDMODE']


stadistics_indicator =  [ "TSF" , "LINEARREG_INTERCEPT"]

#TA indicators
indicators = [
        'CDL2CROWS',
        'CDL3BLACKCROWS',
        'CDL3INSIDE',
        'CDL3LINESTRIKE',
        'CDL3OUTSIDE',
        'CDL3STARSINSOUTH',
        'CDL3WHITESOLDIERS',
        'CDLABANDONEDBABY',
        'CDLADVANCEBLOCK',
        'CDLBELTHOLD',
        'CDLBREAKAWAY',
        'CDLCLOSINGMARUBOZU',
        'CDLCONCEALBABYSWALL',
        'CDLCOUNTERATTACK',
        'CDLDARKCLOUDCOVER',
        'CDLDOJI',
        'CDLDOJISTAR',
        'CDLDRAGONFLYDOJI',
        'CDLENGULFING',
        'CDLEVENINGDOJISTAR',
        'CDLEVENINGSTAR',
        'CDLGAPSIDESIDEWHITE',
        'CDLGRAVESTONEDOJI',
        'CDLHAMMER',
        'CDLHANGINGMAN',
        'CDLHARAMI',
        'CDLHARAMICROSS',
        'CDLHIGHWAVE',
        'CDLHIKKAKE',
        'CDLHIKKAKEMOD',
        'CDLHOMINGPIGEON',
        'CDLIDENTICAL3CROWS',
        'CDLINNECK',
        'CDLINVERTEDHAMMER',
        'CDLKICKING',
        'CDLKICKINGBYLENGTH',
        'CDLLADDERBOTTOM',
        'CDLLONGLEGGEDDOJI',
        'CDLLONGLINE',
        'CDLMARUBOZU',
        'CDLMATCHINGLOW',
        'CDLMATHOLD',
        'CDLMORNINGDOJISTAR',
        'CDLMORNINGSTAR',
        'CDLONNECK',
        'CDLPIERCING',
        'CDLRICKSHAWMAN',
        'CDLRISEFALL3METHODS',
        'CDLSEPARATINGLINES',
        'CDLSHOOTINGSTAR',
        'CDLSHORTLINE',
        'CDLSPINNINGTOP',
        'CDLSTALLEDPATTERN',
        'CDLSTICKSANDWICH',
        'CDLTAKURI',
        'CDLTASUKIGAP',
        'CDLTHRUSTING',
        'CDLTRISTAR',
        'CDLUNIQUE3RIVER',
        'CDLUPSIDEGAP2CROWS',
        'CDLXSIDEGAP3METHODS']


## Used for generating the variables
def get_X  (data_ , model_params, data=False ):
    periods = model_params["periods"]
    time_periods = model_params ["time_periods"]
   
    # It loads the data we get from Binance

    if data :
        df = pd.DataFrame(data_ , columns = ['open_time','open', 'high', 'low', 'close', 'volume','close_time', 'quote_asset_volume','num_trades','taker_base_vol','taker_quote_vol', 'ignore'] )
        
    else: 
        df= pd.read_csv( f"{data_}" )
        df.drop("Unnamed: 0" , axis=1 , inplace=True)
    df = df.astype(float )
    df.sort_values(by="open_time")
    df["open_time" ] = pd.to_datetime(df["open_time" ] , utc=True, unit='ms')  
    df["Typical"] = df[['open', 'high', 'low', 'close'] ].mean(axis = 1)
    for indicator in indicators:
        df[indicator] =   getattr(talib , indicator)( df.open , df.high, df.low , df.close)
    for cyclic in cycle_Indicators:
        df[cyclic] = getattr(talib , cyclic) (df.close)
    for period in periods:
        for stadistics in stadistics_indicator:
            df[stadistics] = getattr(talib ,stadistics )(df.close , timeperiod=period)
        df["SMA_{}".format(period)] = talib.SMA(df["Typical"] ,  timeperiod=period )
        df["DEMA_{}".format(period)] = talib.SMA(df["Typical"] ,  timeperiod=period )
        df["WMA_{}".format(period)] = talib.WMA(df["close"], timeperiod=period)
        [ df["upperband"] , df["middleband"] , df["lowerband"]  ]  = talib.BBANDS(df["close"], timeperiod=5, nbdevup=2, nbdevdn=2, matype=0)
    #volumne indicators
    df["AD"] = talib.AD(  df["high"], df["low"], df["close"], df["volume"])
    df["OBV"] = getattr(talib , "OBV") (df.close , df.volume)
    df["ADOSC"] = talib.ADOSC(  df["high"], df["low"], df["close"], df["volume"] ,  fastperiod=3, slowperiod=10)
    #volatiliy
    df["ATR"] = talib.ATR(  df["high"], df["low"], df["close"], timeperiod=time_periods[1])
    df["NATR"] = talib.NATR(  df["high"], df["low"], df["close"], timeperiod=time_periods[1])
    df["TRANGE"] = talib.NATR(  df["high"], df["low"], df["close"])

    #momentum indicators
    df["RSI"] = talib.RSI( df.close, timeperiod=time_periods[1])
    df["ADX"] = talib.ADX( df.high , df.low , df.close, timeperiod=time_periods[2])
    df["ADXR"] = talib.ADXR( df.high , df.low , df.close, timeperiod=time_periods[2])
    df["APO"] = talib.APO(  df.close, fastperiod=time_periods[1] , slowperiod= time_periods[2])
    df["AROON_up"] , df["AROON_down"]  = talib.AROON(  df.high , df.low , timeperiod=time_periods[2])
    df["AROONOSC"] = talib.AROONOSC(  df.high , df.low , timeperiod=time_periods[2])
    df["BOP"] = talib.BOP(  df.open , df.high , df.low , df.close )
    df["CCI"] = talib.CCI(  df.high , df.low ,  df.close, timeperiod=time_periods[1])
    df["CMO"] = talib.CMO(    df.close, timeperiod=time_periods[1])
    df["DX"] = talib.DX(   df.high , df.low ,  df.close, timeperiod=time_periods[1])
    df["macd"], df["macdsignal"], df["macdhist"] = talib.MACD(df.close, fastperiod=time_periods[1], slowperiod=time_periods[2], signalperiod=time_periods[0])
    df["macd_"], df["macdsignal_"], df["macdhist_"] = talib.MACDFIX(df.close, signalperiod=time_periods[1])
    df["MFI"] = talib.MFI(df.high, df.low, df.close, df.volume, timeperiod=time_periods[2])
    df["MINUS_DI"]= talib. MINUS_DI(df.high, df.low,df. close, timeperiod=time_periods[2])
    df["close_time" ] = pd.to_datetime(df["close_time" ] , utc=True, unit='ms')  
    df.set_index( "open_time"  , inplace=True)
    df = df.dropna(axis=0)
    return df

Есть много индикаторов, которые легко рассчитываются для вас! Некоторые из них требуют некоторых параметров, таких как MACD и другие индикаторы импульса, поэтому мы определили эти параметры.

#Some parameters for calculating the TA metrics
model_params = {"periods":[ 5, 20 ,30 ],
"time_periods" : [5,20,30],
"periods" :[5,10,20],
"percentage" : 0.04,
"space" : 1,
"test" : 8 }

Дизайн эксперимента

Тест — это временной диапазон, который мы используем для измерения того, происходит ли прорыв фиксированного процента после последнего времени, когда 8 означает 4 часа, поскольку значение eack означает 30 минут.

Используя это процентное изменение цены, мы помечаем каждую точку набора данных как 1, если происходит длинный прорыв после или до 8 периодов времени, 2, если происходит короткий прорыв, и 0, если ничего из вышеперечисленного не происходит. Мы создаем фрейм данных со следующим кодом

# Used for classifing 
def get_df (data_ , model_params, data=False ) :
    percentage =model_params["percentage"]
    space  =model_params["space"]
    test  =model_params["test"]
    df = get_X (data_ , model_params, data=False )
    df["label"]= np.nan
    data = pd.DataFrame([])
    total = int(df.shape[0] / space+test)


    for i in range(total):

        try:
            expect = df.iloc[  (space+test)*i :  (space+test) *(i+1),: ].iloc[ -space: ,: ]     
            look = df.iloc[  (space+test)*i :  (space+test) *(i+1),: ].iloc[   :-test, :] 
            
            start =   float(look[ -1: ]["close"])
            low = expect["low"]
            high = expect["high"]

            for i in range (expect.shape[0]):

                try:
                
                    low_percentage = (   low -start )/ (start)
                    high_percentage = (high - start )/start 

                    shorts = low_percentage <=  - percentage
                    longs = high_percentage >= percentage
                    if (shorts.iloc[i]):
                        look.loc[ -1: ,"label"] = 2
                        data = pd.concat([ data  ,  look[ -1: ]])

                        break
                    if (longs.iloc[i]):
                        look.loc[ -1: ,"label"] = 1
                        data = pd.concat([ data  ,  look[ -1: ]])
                        break
                    if (i == expect.shape[0] -1 ):
                        look.loc[ -1: ,"label"] = 0
                        data = pd.concat([ data  ,  look[ -1: ]])
                        break
                except :
                    #traceback.print_exc()

                    pass
        except:
            #traceback.print_exc()
            pass
    return data

Вот снимок нашего набора данных: длинные позиции — это красные зоны, а красные — короткие.

Мы разработаем этот эксперимент как классификацию с несколькими метками. Поэтому я буду использовать классификатор Random-Forest. Я буду тестировать с последними 20% данных

def X_y_train_test( percentil , df):
    #df.reset_index(inplace=True,drop =True)


    X_train = df.iloc[ :int( df.shape[0] *percentil)].drop([ "label"  ] ,axis =1)
    Y_train = df.iloc[ :int( df.shape[0] *percentil)]["label"]
    X_test = df.iloc[ int( df.shape[0] *percentil):].drop([ "label" ] ,axis =1)
    Y_test = df.iloc[ int( df.shape[0] *percentil):]["label"]
    return [ X_train , X_test , Y_train ,   Y_test]

И запустите классификатор:

def estimate_metrics(params, df):

    data_ = df[df['label'].notna()]
    data_.reset_index(inplace=True  )
    #data_.drop("index" , axis=1 , inplace=True)
    data_ =data_.drop(  [ "open_time"  , "close_time"   ], axis=1)


        #data_.drop("index" , axis=1 , inplace=True)
    #data_ =data_.drop(  [ "open_time"  , "close_time"  ], axis=1)
    X_train , X_test , Y_train ,   Y_test = X_y_train_test(0.8 , data_)

    oversample = SMOTE(random_state=42)
    initial_columns = ['open', 'high', 'low', 'close', 'volume', 'quote_asset_volume','num_trades','taker_base_vol','taker_quote_vol', 'ignore']

    X_train, Y_train = oversample.fit_resample(X_train, Y_train)
    X_test ,Y_test = oversample.fit_resample(X_test, Y_test )
    #X_train = X_train[initial_columns]
    #X_test = X_test[initial_columns]
    # get the data from Binance  
    # klines = client.get_historical_klines(symbol, client.KLINE_INTERVAL_30MINUTE, "5 day ago")

    from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score 

    parameters = {'n_estimators': 100, 'max_features': 'log2', 'max_depth': 30, 'criterion': 'gini', 'bootstrap': False}

    rf = RandomForestClassifier( random_state=42 , bootstrap=parameters["bootstrap"], criterion=parameters["criterion"], 
                                        max_depth=parameters['max_depth'], max_features=parameters['max_features'], n_estimators=parameters["n_estimators"]) 

    rf.fit(X_train,Y_train)
    Y_pred = rf.predict( X_test)
        
    metrics =  { "Accuracy": accuracy_score(Y_pred, Y_test) , "Precision" : precision_score(Y_pred, Y_test , average='macro') , "Recall" : recall_score(Y_pred, Y_test , average="macro"), 
                    "F1" : f1_score(Y_pred, Y_test, average="macro")  }
    return metrics

Теперь давайте найдем оптимальную цель прорыва

metrics_data = [ ]

for  percentage in [0.02 , 0.03, 0.04,0.05 ]:
    for test in [4,6,8,10,12]:

        model_params = {"periods":[ 5, 20 ,30 ],
        "time_periods" : [5,20,30],
        "periods" :[5,10,20],

        "percentage" : percentage,
        "space" : 1,
         "test" : test }

        df = get_df("ETHUSDT",model_params)

        metrics = estimate_metrics(model_params , df)
        metrics["test"]= test
        metrics["percentage"] = percentage
        metrics_data = metrics_data + [ metrics]
        print (metrics_data)
pd.DataFrame(metrics_data)

Полученные результаты

Находим следующие результаты:

Таким образом, оптимальная цель прорыва для этого эксперимента — 3% за период времени 4 часа. Теперь если сравнить подгонку модели с теми же параметрами, но обученной только со значениями API Binance

Мы находим большое улучшение для нашей модели! :)

Вы можете получить мой код здесь https://github.com/alice1989123/Ta_trading/blob/master/Algo_Trading.ipynb

Удачного кодирования!