freqtrade / technical

Various indicators developed or collected for the Freqtrade
GNU General Public License v3.0
758 stars 218 forks source link

PMAX indicator #148

Closed tarantula3535 closed 3 years ago

tarantula3535 commented 3 years ago

I mentioned here #97. an indicator that I like and use..

def PMAX(dataframe, period = 10, multiplier = 3, length=12, MAtype=1, src=1):
    """
    Function to compute PMAX

    Args :
        df : Pandas DataFrame which contains ['date', 'open', 'high', 'low', 'close', 'volume'] columns
        period : Integer indicates the period of computation in terms of number of candles
        multiplier : Integer indicates value to multiply the ATR
        length: moving averages length
        MAtype: type of the moving average

    Returns :
        df : Pandas DataFrame with new columns added for 
            True Range (TR), ATR (ATR_$period)
            PMAX (pm_$period_$multiplier_$length_$Matypeint)
            PMAX Direction (pmX_$period_$multiplier_$length_$Matypeint)
    """
    import talib.abstract as ta
    df = dataframe.copy()
    mavalue = 'MA_' + str(MAtype) + '_' + str(length)
    atr = 'ATR_' + str(period)
    df[atr]=ta.ATR(df , timeperiod = period)
    pm = 'pm_' + str(period) + '_' + str(multiplier) + '_' + str(length) + '_' + str(MAtype)
    pmx = 'pmX_' + str(period) + '_' + str(multiplier) + '_' + str(length) + '_' + str(MAtype)
    # MAtype==1 --> EMA
    # MAtype==2 --> DEMA
    # MAtype==3 --> T3
    # MAtype==4 --> SMA
    # MAtype==5 --> VIDYA
    # MAtype==6 --> TEMA
    # MAtype==7 --> WMA
    # MAtype==8 --> VWMA
    # MAtype==9 --> zema
    if src == 1:
        masrc=df["close"]
    elif src == 2:
        masrc = (df["high"] + df["low"]) / 2
    elif src == 3:
        masrc = (df["high"] + df["low"]+ df["close"] + df["open"]) / 4
    if MAtype==1:
        df[mavalue]= ta.EMA(masrc , timeperiod = length)
    elif MAtype==2:
        df[mavalue]= ta.DEMA(masrc , timeperiod = length)
    elif MAtype==3:
        df[mavalue]= ta.T3(masrc , timeperiod = length)
    elif MAtype==4:
        df[mavalue]= ta.SMA(masrc , timeperiod = length)
    elif MAtype==5:
        df[mavalue]= VIDYA(df , length= length)
    elif MAtype==6:
        df[mavalue]= ta.TEMA(masrc , timeperiod = length)
    elif MAtype==7:
        df[mavalue]= ta.WMA(df , timeperiod = length)
    elif MAtype==8:
        df[mavalue]= vwma(df , length)
    elif MAtype==9:
        df[mavalue]= zema(df , period=length)
    # Compute basic upper and lower bands
    df['basic_ub'] = df[mavalue] + (multiplier * df[atr])
    df['basic_lb'] = df[mavalue] - (multiplier * df[atr])
    # Compute final upper and lower bands
    df['final_ub'] = 0.00
    df['final_lb'] = 0.00
    for i in range(period, len(df)):
        df['final_ub'].iat[i] = df['basic_ub'].iat[i] if df['basic_ub'].iat[i] < df['final_ub'].iat[i - 1] or df[mavalue].iat[i - 1] > df['final_ub'].iat[i - 1] else df['final_ub'].iat[i - 1]
        df['final_lb'].iat[i] = df['basic_lb'].iat[i] if df['basic_lb'].iat[i] > df['final_lb'].iat[i - 1] or df[mavalue].iat[i - 1] < df['final_lb'].iat[i - 1] else df['final_lb'].iat[i - 1]

    # Set the Pmax value
    df[pm] = 0.00
    for i in range(period, len(df)):
        df[pm].iat[i] = df['final_ub'].iat[i] if df[pm].iat[i - 1] == df['final_ub'].iat[i - 1] and df[mavalue].iat[i] <= df['final_ub'].iat[i] else \
                        df['final_lb'].iat[i] if df[pm].iat[i - 1] == df['final_ub'].iat[i - 1] and df[mavalue].iat[i] >  df['final_ub'].iat[i] else \
                        df['final_lb'].iat[i] if df[pm].iat[i - 1] == df['final_lb'].iat[i - 1] and df[mavalue].iat[i] >= df['final_lb'].iat[i] else \
                        df['final_ub'].iat[i] if df[pm].iat[i - 1] == df['final_lb'].iat[i - 1] and df[mavalue].iat[i] <  df['final_lb'].iat[i] else 0.00 
    # Mark the trend direction up/down
    df[pmx] = np.where((df[pm] > 0.00), np.where((df[mavalue] < df[pm]), 'down',  'up'), np.NaN)
    # Remove basic and final bands from the columns
    df.drop(['basic_ub', 'basic_lb', 'final_ub', 'final_lb'], inplace=True, axis=1)

    df.fillna(0, inplace=True)

    return df
tarantula3535 commented 3 years ago

https://tr.tradingview.com/v/sU9molfV/ it is the tradingview' link.. https://www.youtube.com/watch?v=yR6tkDTTjCQ this link is the explanations with subtitles.. I hope it will be useful...

xmatthias commented 3 years ago

please do not post it here as issue (moving the work to others) - but implement this as a Pull request - so we can review and merge it eventually. I don't see why i should do the work of "copy-pasting" indicators from issues into code just because you don't like doing Pull requests.

tarantula3535 commented 3 years ago

sorry i close the issue..

xmatthias commented 3 years ago

@tarantula3535 now don't get me wrong - i'm not saying this indicator is not useful - but you can basically use the "edit file" button on github to add this in there.

Now doing this on a cloned version has advantages and is probably easier to get right (as you can run flake8 to check if the layout is correct) - but the overall flow to create a pull request is a lot less work than actually writing this indicator.

AetherWaves commented 3 years ago

I played with the indicator some time and figured out the recursive calculation of pmax data is very heavy making parameter optimization quite difficult. The range for i in range(period, len(df)): i the for loop will be growing with the size of the dataframe ? Some ideas to speed up the recursive indicator calculation (numpy, cython, vectorization etc.) ?

xmatthias commented 3 years ago

cython shuld work. Numpy is already used - and vectorization is not possible if one column's result depends on the same column from the prior row (df['aaa'].iat[i] = df['aaa'].iat[i -1] +/- whatever).

I've been playing with the idea of using cython for a while in my head - but never got around to actually do something with it.

If you're familiar with that - please submit a PR adding this (results should be identical, obviously - but the speed should be superior). We're not depending on cython at the moment, so some dependencies might change (i think i can help with that eventually) - and if this works well, it can for sure serve as a sample for other inicators which would also greatly benefit from this.

AetherWaves commented 3 years ago

Hello Matthias, here some value to represent the porformance advantages of cython in case of the PMAX indicator:

Array size: 5000, Python: 1.048s, Cython: 8.976ms, Speed up factor: 116.7 Array size: 10000, Python: 2.132s, Cython: 12.939ms, Speed up factor: 164.7 Array size: 15000, Python: 3.237s, Cython: 16.991ms, Speed up factor: 190.5 Array size: 20000, Python: 4.374s, Cython: 21.910ms, Speed up factor: 199.6 Array size: 25000, Python: 5.531s, Cython: 25.956ms, Speed up factor: 213.1 Array size: 30000, Python: 6.866s, Cython: 32.886ms, Speed up factor: 208.8 Array size: 35000, Python: 8.114s, Cython: 35.908ms, Speed up factor: 226.0 Array size: 40000, Python: 9.474s, Cython: 40.879ms, Speed up factor: 231.7 Array size: 45000, Python: 10.650s, Cython: 42.916ms, Speed up factor: 248.2

Cython is quite easy to implement. the compiled library under windows 10 gets tha name .cp39-win_amd64.pyd I guess the other indicators could benefit from it too.

def PMAX(dataframe, period=10, multiplier=3, length=12, MAtype=1, src=1):  # noqa: C901
    """
    Function to compute PMAX
    Source: https://www.tradingview.com/script/sU9molfV/
    Pinescript Author: KivancOzbilgic

    Args :
        df : Pandas DataFrame with the columns ['date', 'open', 'high', 'low', 'close', 'volume']
        period : Integer indicates the period of computation in terms of number of candles
        multiplier : Integer indicates value to multiply the ATR
        length: moving averages length
        MAtype: type of the moving average

    Returns :
        df : Pandas DataFrame with new columns added for
            True Range (TR), ATR (ATR_$period)
            PMAX (pm_$period_$multiplier_$length_$Matypeint)
            PMAX Direction (pmX_$period_$multiplier_$length_$Matypeint)
    """

    df = dataframe.copy()

    mavalue = "MA_" + str(MAtype) + "_" + str(length)
    atr = "ATR_" + str(period)
    df[atr] = ta.ATR(df, timeperiod=period)
    pm = "pm_" + str(period) + "_" + str(multiplier) + "_" + str(length) + "_" + str(MAtype)
    pmx = "pmX_" + str(period) + "_" + str(multiplier) + "_" + str(length) + "_" + str(MAtype)
    # MAtype==1 --> EMA
    # MAtype==2 --> DEMA
    # MAtype==3 --> T3
    # MAtype==4 --> SMA
    # MAtype==5 --> VIDYA
    # MAtype==6 --> TEMA
    # MAtype==7 --> WMA
    # MAtype==8 --> VWMA
    # MAtype==9 --> zema
    if src == 1:
        masrc = df["close"]
    elif src == 2:
        masrc = (df["high"] + df["low"]) / 2
    elif src == 3:
        masrc = (df["high"] + df["low"] + df["close"] + df["open"]) / 4
    if MAtype == 1:
        df[mavalue] = ta.EMA(masrc, timeperiod=length)
    elif MAtype == 2:
        df[mavalue] = ta.DEMA(masrc, timeperiod=length)
    elif MAtype == 3:
        df[mavalue] = ta.T3(masrc, timeperiod=length)
    elif MAtype == 4:
        df[mavalue] = ta.SMA(masrc, timeperiod=length)
    elif MAtype == 5:
        df[mavalue] = ta.VIDYA(df, length=length)
    elif MAtype == 6:
        df[mavalue] = ta.TEMA(masrc, timeperiod=length)
    elif MAtype == 7:
        df[mavalue] = ta.WMA(df, timeperiod=length)
    elif MAtype == 8:
        df[mavalue] = ta.vwma(df, length)
    elif MAtype == 9:
        df[mavalue] = ta.zema(df, period=length)

    # Compute basic upper and lower bands
    df["basic_ub"] = df[mavalue] + (multiplier * df[atr])
    df["basic_lb"] = df[mavalue] - (multiplier * df[atr])
    # Compute final upper and lower bands
    df["final_ub"] = 0.00
    df["final_lb"] = 0.00

    df[pm] = 0.00

    # calling the cython function
    pm_array = PMAXCython.pmax(df["basic_ub"].to_numpy(), df["basic_lb"].to_numpy(),
                                             df["final_ub"].to_numpy(), df["final_lb"].to_numpy(),
                                             df[mavalue].to_numpy(), period=period, rtol=1e-1, atol=1e-5)

    df[pm] = pm_array

    # Mark the trend direction up/down
    df[pmx] = np.where((df[pm] > 0.00), np.where((df[mavalue] < df[pm]), "down", "up"), np.NaN)

    # T1.append(time.time() - T0)
    # Remove basic and final bands from the columns
    df.drop(["basic_ub", "basic_lb", "final_ub", "final_lb"], inplace=True, axis=1)

    df.fillna(0, inplace=True)

    return df
xmatthias commented 3 years ago

here some value to represent the porformance advantages of cython in case of the PMAX indicator:

Array size: 5000, Python: 1.048s, Cython: 8.976ms, Speed up factor: 116.7 Array size: 10000, Python: 2.132s, Cython: 12.939ms, Speed up factor: 164.7 Array size: 15000, Python: 3.237s, Cython: 16.991ms, Speed up factor: 190.5 Array size: 20000, Python: 4.374s, Cython: 21.910ms, Speed up factor: 199.6 Array size: 25000, Python: 5.531s, Cython: 25.956ms, Speed up factor: 213.1 Array size: 30000, Python: 6.866s, Cython: 32.886ms, Speed up factor: 208.8 Array size: 35000, Python: 8.114s, Cython: 35.908ms, Speed up factor: 226.0 Array size: 40000, Python: 9.474s, Cython: 40.879ms, Speed up factor: 231.7 Array size: 45000, Python: 10.650s, Cython: 42.916ms, Speed up factor: 248.2

Cython is quite easy to implement. the compiled library under windows 10 gets tha name .cp39-win_amd64.pyd I guess the other indicators could benefit from it too.

I've never doubted the speed improvement :laughing: although it's quite nice to see it in numbers.

What's missing in my opinion however is the cython code itself - while you point out the resulting file - without the code, just the filename, there's little i can do with it - assuming PMAXCython.pmax will not be "magically" available in all python installations around the world ...

Best create a Pull request with this (including the cython code however) - that's the easiest way to test and comment on lines where there's some doubts...