Създаване на комбинирана стратегия с помощта на Parabolic SAR и Hull Moving Average.

Комбинирането на стратегии и индикатори винаги е правилният път към стабилна техническа или количествена система за търговия. В тази статия търсенето продължава към комбиниране на различни елементи с надеждата да се намери надеждна система. Ще кодираме и ще обсъдим Parabolic SAR и Hull Moving Average, така че да предоставят смислени и интуитивни сигнали.

Към момента резултатите от търговията вече няма да бъдат публикувани, тъй като са функция на разходите, пропускането, естеството на алгоритмите, управлението на риска и множество други променливи. Това може да подведе читателя и затова ще предоставя само функциите за индикаторите, техните сигнали и как да изчисляваме възвръщаемостта, както и да ги анализираме. Единственото хранене с лъжица тук ще бъде представянето на кода на индикатора и как да се изчисли ефективността.

Току-що публикувах нова книга след успеха на Нови технически индикатори в Python. Той включва по-пълно описание и добавяне на сложни стратегии за търговия със страница в Github, посветена на непрекъснато актуализирания код. Ако смятате, че това ви интересува, не се колебайте да посетите връзката по-долу или ако предпочитате PDF версията, можете да се свържете с мен в Linkedin.



Параболичният индикатор за спиране и обръщане

Параболичният стоп-и-реверс е интересен индикатор, създаден от Уайлдър Уилс, който е и създателят на известния RSI. Този индикатор се използва най-вече като плаващ стоп, който проследява тенденцията, докато се развива, но няма вреда да го тествате като стратегия за търговия. Струва си да се отбележи, че той се представя сравнително добре при стабилни тенденции, но както всеки друг индикатор, той има своите слабости, в този случай вариращите пазари.

Ще се позова на библиотека на Python, наречена talibоткъдето потребителят може да импортира функцията sarкоято използва рамка с данни и изчислява стойностите. След като промените тази функция, можете да се обърнете към тази по-долу (не си приписвам заслугата за това, тъй като просто промених някои редове за разлика от другите функции, които са кодирани от мен):

def sar(s, af = 0.02, amax = 0.2):    high, low = s.high, s.low    
    # Starting values
    sig0, xpt0, af0 = True, high[0], af
    sar = [low[0] - (high - low).std()]    
    for i in range(1, len(s)):
        sig1, xpt1, af1 = sig0, xpt0, af0
        lmin = min(low[i - 1], low[i])
        lmax = max(high[i - 1], high[i])        
        if sig1:
            sig0 = low[i] > sar[-1]
            xpt0 = max(lmax, xpt1)
        else:
            sig0 = high[i] >= sar[-1]
            xpt0 = min(lmin, xpt1)
       if sig0 == sig1:
            sari = sar[-1] + (xpt1 - sar[-1])*af1
            af0 = min(amax, af1 + af)            
            if sig0:
                af0 = af0 if xpt0 > xpt1 else af1
                sari = min(sari, lmin)
            else:
                af0 = af0 if xpt0 < xpt1 else af1
                sari = max(sari, lmax)
        else:
            af0 = af
            sari = xpt0sar.append(sari)     
        return sar

Основното разбиране е, че когато Parabolic SAR (точките около пазарната цена) е под текущата цена, тогава перспективата е възходяща, а когато е над текущата цена, тогава перспективата е меча.

За да добавите Parabolic SAR към вашия OHLC масив (за предпочитане numpy), използвайте следните стъпки:

importing pandas as pd
# Converting to a pandas Data frame
my_data = pd.DataFrame(my_data)
# Renaming columns to fit the function
my_data.columns = ['open','high','low','close']
# Calculating the Parabolic SAR
Parabolic = sar(my_data, 0.02, 0.2)
# Converting the Parabolic values back to an array
Parabolic = np.array(Parabolic)
# Reshaping
Parabolic = np.reshape(Parabolic, (-1, 1))
# Concatenating with the OHLC Data
my_data = np.concatenate((my_data, Parabolic), axis = 1)

Ако също се интересувате от повече технически индикатори и използването на Python за създаване на стратегии, тогава моята най-продавана книга за техническите индикатори може да ви заинтересува:



Плъзгащата средна на Хъл

Пълзящите средни ни помагат да потвърдим и да управляваме тенденцията. Те са най-известният технически индикатор и това се дължи на тяхната простота и доказан опит в добавянето на стойност към анализите. Можем да ги използваме, за да намерим нива на подкрепа и съпротива, стопове и цели и да разберем основната тенденция. Тази гъвкавост ги прави незаменим инструмент в нашия търговски арсенал.

Както подсказва името, това е вашата обикновена проста средна стойност, която се използва навсякъде в статистиката и в общи линии във всяка друга част от живота ни. Това са просто общите стойности на наблюденията, разделени на броя наблюдения. Математически казано, може да се запише като:

Това, което ни интересува, за да изградим Hull Moving Average, е линейно-претеглената Moving Average, това е проста подвижна средна, която придава повече тежест на последните данни. Най-скорошното наблюдение е с най-голяма тежест и всяко предишно има прогресивно намаляващо тегло. Интуитивно, той има по-малко забавяне от другите пълзящи средни, но също така е най-малко използван и следователно, това, което печели при намаляване на забавянето, губи популярност.

Математически казано, може да се запише като:

По принцип, ако имаме набор от данни, съставен от две числа [1, 2] и искаме да изчислим линейно претеглена средна стойност, тогава ще направим следното:

  • (2 x 2) + (1 x 1) = 5
  • 5 / 3 = 1.66

Това предполага времева серия с номер 2 като най-скорошното наблюдение.

import numpy as npdef lwma(Data, lookback):
    
    weighted = []
    for i in range(len(Data)):
            try:
                total = np.arange(1, lookback + 1, 1)
                
                matrix = Data[i - lookback + 1: i + 1, 3:4]
                matrix = np.ndarray.flatten(matrix)
                matrix = total * matrix
                wma = (matrix.sum()) / (total.sum())
                weighted = np.append(weighted, wma)          
            except ValueError:
                pass
    
    Data = Data[lookback - 1:, ]
    weighted = np.reshape(weighted, (-1, 1)) 
    Data = np.concatenate((Data, weighted), axis = 1)   
    
    return Data
# For this function to work, you need to have an OHLC array composed of the four usual columns, then you can use the below syntax to get a data array with the weighted moving average using the lookback you need
my_ohlc_data = lwma(my_ohlc_data, 20)

Сега, след като разбрахме какво е претеглена подвижна средна, можем да продължим с представянето на Hull Moving Average, мощна ранна система, следваща тренда.

Hull Moving Average използва претеглената пълзяща средна като градивни елементи и се изчислява, като се следват стъпките по-долу:

  • Изберете период за преглед като 20 или 100 и изчислете претеглената подвижна средна на цената на затваряне.
  • Разделете периода за ретроспективен преглед, намерен в първата стъпка, и изчислете претеглената подвижна средна на цената на затваряне, като използвате този нов период за ретроспективен преглед. Ако числото не може да бъде разделено на две, тогава вземете най-близкото число преди запетаята (напр. ретроспекция от 15 може да бъде 7 или 8 като втора ретроспекция).
  • Умножете втората претеглена пълзяща средна по две и извадете от нея първата претеглена пълзяща средна.
  • Като последна стъпка, вземете корен квадратен от първия ретроспективен преглед (напр. ако сте избрали ретроспективен преглед от 100, тогава третият ретроспективен период е 10) и изчислете претеглената подвижна средна върху последния резултат, който сме имали в трета стъпка. Внимавайте да не го калкулирате по пазарни цени

Следователно, ако изберем период за ретроспективен преглед от 100, ще изчислим на 100 ретроспективен период, след това на 50 и накрая на 10, приложен към последния резултат.

Създаване на сигнали

Както при всеки правилен изследователски метод, целта е да тестваме стратегията и да можем да видим сами дали си струва да я имаме като добавка към нашата вече съществуваща рамка за търговия или не.

Първата стъпка е създаването на правилата за търговия. Кога системата ще купува и кога ще къса? С други думи, кога се дава сигналът, който казва на системата, че текущият пазар ще върви нагоре или надолу?

Условията за търговия, които можем да изберем са:

  • Отидете дълго (Купете) пазарът е над Parabolic SAR, като същевременно е над текущата Hull Moving Average. Като последно условие, последната цена на затваряне трябва да бъде по-ниска от последния Parabolic SAR.
  • Продавайте на къса позиция, пазарът е под Parabolic SAR, като същевременно е под текущата Hull Moving Average. Като последно условие, последната цена на затваряне трябва да е над последния Parabolic SAR.

Горната диаграма показва сигналите, генерирани от системата. Трябва да имаме предвид честотата на сигналите, когато разработваме алгоритъм за търговия. Функцията за сигнал, използвана за генериране на задействания въз основа на условията, споменати по-горе, може да бъде намерена в този фрагмент:

def signal(Data, close, psar, hull_ma, buy, sell):
    
    Data = adder(Data, 10)
    
    for i in range(len(Data)):
        if Data[i, close] > Data[i, psar] and Data[i, close] > Data[i, hull_ma] and Data[i - 1, close] < Data[i - 1, hull_ma]:
                Data[i, buy] = 1
         
        elif Data[i, close] < Data[i, psar] and Data[i, close] < Data[i, hull_ma] and Data[i - 1, close] > Data[i - 1, hull_ma]:
                Data[i, sell] = -1
                
    return Data

Горното показва същата техника, приложена върху почасовите стойности на USDCHF. Без съмнение изоставащите индикатори трудно ще открият тенденцията в нейното начало, поради което понякога виждаме някои възходящи сигнали около върховете и мечи сигнали около дъната.

Сега е време да видим интуицията на анализа на стратегията. Не забравяйте, че вече няма да се предоставят резултати от обратно тестване, но по-долу ще бъде много по-полезно.

Ако се интересувате да видите повече технически индикатори, не се колебайте да разгледате статията по-долу:



Рамката за оценка на стратегията

След като разполагахме със сигналите, сега знаем кога алгоритъмът би поставил своите поръчки за покупка и продажба, което означава, че имаме приблизително копие на миналото, където можем да контролираме решенията си без пристрастия назад. Трябва да симулираме как би се справила стратегията при нашите условия. Това означава, че трябва да изчислим възвръщаемостта и да анализираме показателите за ефективност. Този раздел ще се опита да покрие най-важното и ще предостави рамка. Първо можем да започнем с най-простата мярка от всички, отчета за печалбата и загубата. Когато извършваме бек-тест на нашата система, ние искаме да видим дали тя е направила пари или е загубила пари. В крайна сметка това е игра на богатство. Това може да се направи чрез изчисляване на печалбите и загубите, брутната и нетната възвръщаемост, както и начертаване на диаграмата на собствения капитал, която е просто времевият ред на нашия баланс, като се дава перфектен алгоритъм, който инициира поръчки за покупка и продажба въз основа на стратегията. Преди да видим това, трябва да се уверим в следното, тъй като искаме рамка, която пасва навсякъде:

Горната таблица казва, че трябва да имаме индикатора или генератора на сигнали в колона 4 или 5 (не забравяйте, че индексирането в Python започва от нула). Сигналът за покупка (константа = 1) в колоната, индексирана на 6, и сигналът за къса продажба (константа = -1) в колоната, индексирана на 7. Това гарантира, че останалата част от кода по-долу работи както трябва. Причината за това е, че при OHLC данни вече имаме заети първите 4 колони, оставяйки ни 1 или 2 колони, за да поставим нашите индикатори, преди да имаме две сигнални колони. Използването на функцията за изтриване, видяна по-горе, може да ви помогне да постигнете този ред, в случай че индикаторите заемат повече от 2 колони.

Първата стъпка в изграждането на кривата на собствения капитал е да изчислим печалбите и загубите от отделните сделки, които предприемаме. От съображения за простота можем да обмислим покупка и продажба на цени на затваряне. Това означава, че когато получим сигнала от индикатора или модела при затваряне, ние започваме търговията при затваряне, докато не получим друг сигнал, при който излизаме и инициираме новата сделка. В реалния живот правим това главно при следващото отваряне, но като цяло във FX няма голяма разлика. Кодът, който трябва да се определи за колоните печалба/загуба, е следният:

def holding(Data, buy, sell, buy_return, sell_return):for i in range(len(Data)):
        try:
            if Data[i, buy] == 1: 
               for a in range(i + 1, i + 1000):                        
                  if Data[a, buy] != 0 or Data[a, sell] != 0:
                     Data[a, buy_return] = (Data[a, 3] - Data[i, 3])
                        break                        
                    else:
                        continue
                
            elif Data[i, sell] == -1:        
               for a in range(i + 1, i + 1000):                        
                  if Data[a, buy] != 0 or Data[a, sell] != 0:
                    Data[a, sell_return] = (Data[i, 3] - Data[a, 3])
                        break                                        
                    else:
                        continue                                         
        except IndexError:
            pass
# Using the function
holding(my_data, 6, 7, 8, 9)

Това ще ни даде колони 8 и 9, попълнени с резултатите от брутната печалба и загуба от направените сделки. Сега трябва да ги трансформираме в кумулативни числа, така че да изчислим кривата на собствения капитал. За да направим това, използваме функцията за индексиране по-долу:

def indexer(Data, expected_cost, lot, investment):
    
    # Charting portfolio evolution  
    indexer = Data[:, 8:10]    
    
    # Creating a combined array for long and short returns
    z = np.zeros((len(Data), 1), dtype = float)
    indexer = np.append(indexer, z, axis = 1)
    
    # Combining Returns
    for i in range(len(indexer)):
        try:    
          if indexer[i, 0] != 0:
             indexer[i, 2] = indexer[i, 0] - (expected_cost / lot)
                
          if indexer[i, 1] != 0:
             indexer[i, 2] = indexer[i, 1] - (expected_cost / lot)
        except IndexError:
            pass
        
    # Switching to monetary values
    indexer[:, 2] = indexer[:, 2] * lot
    
    # Creating a portfolio balance array
    indexer = np.append(indexer, z, axis = 1)
    indexer[:, 3] = investment 
    
    # Adding returns to the balance    
    for i in range(len(indexer)):
    
        indexer[i, 3] = indexer[i - 1, 3] + (indexer[i, 2])
    
    indexer = np.array(indexer)
    
    return np.array(indexer)
# Using the function for a 0.1 lot strategy on $10,000 investment
expected_cost = 0.5 * (lot / 10000) # 0.5 pip spread
investment    = 10000                  
lot           = 10000
equity_curve = indexer(my_data, expected_cost, lot, investment)

Кодът по-долу се използва за генериране на диаграмата. Обърнете внимание, че функцията на индексатора нетира възвръщаемостта, като използва прогнозната цена на сделката, следователно кривата на собствения капитал, която ще бъде начертана, теоретично е без такси.

plt.plot(equity_curve[:, 3], linewidth = 1, label = 'EURUSD)
plt.grid()
plt.legend()
plt.axhline(y = investment, color = 'black’, linewidth = 1)
plt.title(’Strategy’, fontsize = 20)

Сега е време да започнете да оценявате ефективността, като използвате други мерки.

Ще представя набързо основните съотношения и показатели, преди да представя функция за пълна производителност, която ги извежда заедно. Следователно дискусиите по-долу са предимно информационни, ако се интересувате от кода, можете да го намерите в края.

Hit ratio       =  42.28 % # Simulated Ratio

Hit Ratio е изключително лесен за използване. Това е просто броят на печелившите сделки спрямо общия брой направени сделки. Например, ако имаме 1359 сделки в течение на 5 години и сме били печеливши в 711 от тях, тогава нашият коефициент на попадение (точност) е 711/1359 = 52,31%.

Нетната печалба е просто последната стойност в кривата на собствения капитал без такси минус първоначалното салдо. Това е просто добавената стойност към сумата, която сме инвестирали в началото.

Net profit      =  $ 1209.4 # Simulated Profit

Мярката за нетната възвръщаемост е възвръщаемостта на вашата инвестиция или капитал. Ако сте започнали с $1000 и в края на годината балансът ви показва $1300, тогава бихте направили здравословните 30%.

Net Return       =  30.01% # Simulated Return

Един бърз поглед върху Средната печалба в сделките и Средната загуба може да ни помогне да управляваме по-добре рисковете си. Например, ако средната ни печалба е $1,20, а средната ни загуба е $4,02, тогава знаем, че нещо не е наред, тъй като рискуваме твърде много пари за твърде малка печалба.

Average Gain    =  $ 56.95 per trade # Simulated Average Gain
Average Loss    =  $ -41.14 per trade # Simulated Average Loss

След това можем да изчислим две мерки:

  • Теоретично съотношение риск-възнаграждение: Това е желаното съотношение на средните печалби към средните загуби. Съотношение 2,0 означава, че целим два пъти повече, отколкото рискуваме.
  • Реализираното съотношение риск-възнаграждение:Това е действителното съотношение на средните печалби към средните загуби. Коефициент от 0,75 означава, че се насочваме към три четвърти от това, което рискуваме.
Theoretical Risk Reward = 2.00 # Simulated Ratio
Realized Risk Reward    = 0.75 # Simulated Ratio

Факторът на печалбата е относително бърз и лесен метод за изчисляване на рентабилността на стратегията. Изчислява се като общата брутна печалбавърху общата брутна загуба в абсолютни стойности, следователно тълкуването на фактора печалба (наричан още като индекс на рентабилност на жаргона на корпоративните финанси) е колко печалба се генерира на $1 загуба. Формулата за коефициента на печалба е:

Profit factor   =  1.34 # Simulated Profit Factor

Очакваната продължителносте гъвкава мярка, представена от добре познатия Лоран Берну, която се състои от средната печалба/загуба и коефициента на попадение. Той предоставя очакваната печалба или загуба на стойност в долари, претеглена с коефициента на попадение. Степента на печалба е това, което наричаме съотношение на попадения във формулата по-долу и чрез това съотношението на загубите е 1 — съотношение на попадение.

Expectancy      =  $ 1.33 per trade # Simulated Expectancy

Друг интересен измерител е броят на сделките. Това е просто за да разберем честотата на сделките, които имаме.

Trades          = 3697 # Simulated Number

Сега сме готови всички горепосочени показатели да се показват едновременно. След като изчислим функцията за индексиране, можем да използваме функцията за производителност по-долу, за да ни даде необходимите показатели:

def performance(indexer, Data, name):
    
    # Profitability index
    indexer = np.delete(indexer, 0, axis = 1)
    indexer = np.delete(indexer, 0, axis = 1)
    
    profits = []
    losses  = []
    np.count_nonzero(Data[:, 7])
    np.count_nonzero(Data[:, 8])
    
    for i in range(len(indexer)):
        
        if indexer[i, 0] > 0:
            value    = indexer[i, 0]
            profits  = np.append(profits, value)
            
        if indexer[i, 0] < 0:
            value    = indexer[i, 0]
            losses   = np.append(losses, value)
    
    # Hit ratio calculation
    hit_ratio = round((len(profits) / (len(profits) + len(losses))) * 100, 2)
    
    realized_risk_reward = round(abs(profits.mean() / losses.mean()), 2)
    
    # Expected and total profits / losses
    expected_profits = np.mean(profits)
    expected_losses  = np.abs(np.mean(losses))
    total_profits    = round(np.sum(profits), 3)
    total_losses     = round(np.abs(np.sum(losses)), 3)
    
    # Expectancy
    expectancy    = round((expected_profits * (hit_ratio / 100)) \
                   - (expected_losses * (1 - (hit_ratio / 100))), 2)
        
    # Largest Win and Largest Loss
    largest_win = round(max(profits), 2)
    largest_loss = round(min(losses), 2)
    # Total Return
    indexer = Data[:, 10:12]    
    
    # Creating a combined array for long and short returns
    z = np.zeros((len(Data), 1), dtype = float)
    indexer = np.append(indexer, z, axis = 1)
    
    # Combining Returns
    for i in range(len(indexer)):
        try:    
          if indexer[i, 0] != 0:
             indexer[i, 2] = indexer[i, 0] - (expected_cost / lot)
                
          if indexer[i, 1] != 0:
             indexer[i, 2] = indexer[i, 1] - (expected_cost / lot)
        except IndexError:
            pass
        
    # Switching to monetary values
    indexer[:, 2] = indexer[:, 2] * lot
    
    # Creating a portfolio balance array
    indexer = np.append(indexer, z, axis = 1)
    indexer[:, 3] = investment 
    
    # Adding returns to the balance    
    for i in range(len(indexer)):
    
        indexer[i, 3] = indexer[i - 1, 3] + (indexer[i, 2])
    
    indexer = np.array(indexer)
    
    total_return = (indexer[-1, 3] / indexer[0, 3]) - 1
    total_return = total_return * 100
    
    
    print('-----------Performance-----------', name)
    print('Hit ratio       = ', hit_ratio, '%')
    print('Net profit      = ', '$', round(indexer[-1, 3] - indexer[0, 3], 2))
    print('Expectancy      = ', '$', expectancy, 'per trade')
    print('Profit factor   = ' , round(total_profits / total_losses, 2)) 
    print('Total Return    = ', round(total_return, 2), '%')
    print('')    
    print('Average Gain    = ', '$', round((expected_profits), 2), 'per trade')
    print('Average Loss    = ', '$', round((expected_losses * -1), 2), 'per trade')
    print('Largest Gain    = ', '$', largest_win)
    print('Largest Loss    = ', '$', largest_loss)    
    print('')
    print('Realized RR     = ', realized_risk_reward)
    print('Minimum         =', '$', round(min(indexer[:, 3]), 2))
    print('Maximum         =', '$', round(max(indexer[:, 3]), 2))
    print('Trades          =', len(profits) + len(losses))
# Using the function
performance(equity_curve, my_data, 'EURUSD)

Това трябва да ни даде нещо като следното:

-----------Performance----------- EURUSD
Hit ratio       =  42.28 %
Net profit      =  $ 1209.4
Expectancy      =  $ 0.33 per trade
Profit factor   =  1.01
Total Return    =  120.94 %
Average Gain    =  $ 56.95 per trade
Average Loss    =  $ -41.14 per trade
Largest Gain    =  $ 347.5
Largest Loss    =  $ -311.6
Realized RR     =  1.38
Minimum         = $ -1957.6
Maximum         = $ 4004.2
Trades          = 3697
# All of the above are simulated results and do not reflect the presented strategy or indicator

Заключение и важен отказ от отговорност

Не забравяйте винаги да правите вашите бек-тестове. Винаги трябва да вярвате, че другите хора грешат. Моите индикатори и стил на търговия може да работят за мен, но може би не и за вас.

Твърдо вярвам, че не се храни с лъжица. Научих се чрез правене, а не чрез копиране. Трябва да получите идеята, функцията, интуицията, условията на стратегията и след това сами да разработите (още по-добра) такава, така че да я тествате и подобрите, преди да решите да я използвате на живо или да я елиминирате. Изборът ми да не предоставям резултати от бек-тестване трябва да накара читателя да проучи повече самата стратегия и да работи повече върху нея. По този начин можете да споделите с мен вашата по-добра стратегия и ние ще забогатеем заедно.

За да обобщим, реалистични ли са стратегиите, които предоставям? Да, но само чрез оптимизиране на средата (стабилен алгоритъм, ниски разходи, честен брокер, правилно управление на риска и управление на поръчките). Стратегиите предоставени ли са единствено за търгуване? Не, това е за стимулиране на мозъчна атака и получаване на повече идеи за търговия, тъй като на всички ни е писнало да слушаме за свръхпродаден RSI като причина за късо или надминаване на съпротива като причина да отида дълго. Опитвам се да въведа нова област, наречена Обективен технически анализ, където използваме твърди данни, за да преценим нашите техники, вместо да разчитаме на остарели класически методи.