Количественная стратегия торговли, основанная на переходе на переломный момент

Автор:Чао Чжан, Дата: 2023-12-12 11:07:46
Тэги:

img

Обзор

Это количественная торговая стратегия, которая использует поворотные точки в качестве сигналов входа. Она рассчитывает поднимающиеся и падающие поворотные точки. Как только цена пройдет через эти поворотные точки, она инициирует длинные или короткие позиции.

Принцип стратегии

Эта стратегия основана в основном на теории переворота поворота. Сначала она рассчитывает повороты по левым N-полоскам и правым M-полоскам. Затем она отслеживает в режиме реального времени, прорывается ли цена через эти повороты.

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

В частности, эта стратегия рассчитывает повышающиеся и понижающиеся точки опоры с помощью функций ta.pivothigh и ta.pivotlow. Затем сравнивается, проходит ли текущая самая высокая цена через повышающуюся точку опоры и проходит ли самая низкая цена через понижающуюся точку опоры. Если произойдет прорыв, будет инициирована соответствующая длинная или короткая стратегия.

Кроме того, эта стратегия также использует стоп-лосс для контроля рисков. В частности, когда цена проходит через пивоту, она немедленно размещает ордер, устанавливая стоп-лосс на другой стороне пивоту. Это может минимизировать потерю, вызванную неудачным сигналом.

Анализ преимуществ

Эта стратегия, основанная на перевороте поворота, имеет следующие преимущества:

  1. Сигнал обратного поворота является довольно надежным с высоким показателем выигрыша
  2. Риск хорошо контролируется с разумным установлением стоп-лосса
  3. Это легко реализовать с помощью лаконичного кода
  4. Он применим к различным продуктам с хорошей гибкостью.

Анализ рисков

Эта стратегия также сопряжена с некоторыми рисками:

  1. Опорные точки могут не работать, что приводит к неправильным сигналам
  2. Может быть отклонение после прорыва точки поворота, вызывая стоп-лосс
  3. Частота торговли может быть высокой, что влечет за собой неявные затраты на торговлю
  4. Производительность зависит от выбора продукта и настройки параметров

Для снижения рисков можно рассмотреть следующие аспекты:

  1. Оптимизировать количество левых и правых строк для обеспечения надежного расчета точки поворота
  2. Определенное ослабление стоп-лосса для предотвращения чрезмерного затягивания
  3. Определить минимальную цель прибыли для сокращения частой торговли туда и обратно
  4. Испытание на различных продуктах и параметрах для поиска оптимальной конфигурации

Руководство по оптимизации

Есть возможности для дальнейшей оптимизации этой стратегии:

  1. Включить другие показатели для оценки надежности прорывов по поводу ключевых показателей
  2. Добавить модели машинного обучения для определения тенденций цен
  3. Использование высокочастотных данных для повышения чувствительности сигнала
  4. Введение модуля размещения позиций для динамической настройки позиций
  5. Подключить детальный модуль счета для расчета реальной стоимости торговли

Эти оптимизации могут улучшить показатель выигрыша, прибыльность и стабильность стратегии.

Заключение

В общем, это количественная торговая стратегия, основанная на теории перехода поворота. Она использует цены прорывных поворотных точек в качестве торговых сигналов при принятии стоп-лосс для контроля рисков. Эта стратегия проста в реализации и широко применима, что делает ее практической количественной торговой стратегии. Но она также несет некоторые риски и нуждается в дальнейшем тестировании и оптимизации, чтобы найти оптимальную конфигурацию в реальной торговле.


/*backtest
start: 2022-12-05 00:00:00
end: 2023-12-11 00:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/

//@version=5
strategy('Weekly Returns with Benchmark', overlay=true, 
     default_qty_type=strategy.percent_of_equity, default_qty_value=25, 
     commission_type=strategy.commission.percent, commission_value=0.1)

////////////
// Inputs //

// Pivot points inputs
leftBars   = input(2, group = "Pivot Points")
rightBars  = input(1, group = "Pivot Points")

// Styling inputs
prec       = input(1, title='Return Precision',                            group = "Weekly Table")
from_date  = input(timestamp("01 Jan 3000 00:00 +0000"), "From Date", group = "Weekhly Table")
prof_color = input.color(color.green, title = "Gradient Colors", group = "Weeky Table", inline = "colors")
loss_color = input.color(color.red,   title = "",                group = "Weeky Table", inline = "colors")

// Benchmark inputs
use_cur    = input.bool(true,        title = "Use current Symbol for Benchmark", group = "Benchmark")
symb_bench = input('BTC_USDT:swap', title = "Benchmark",                        group = "Benchmark")
disp_bench = input.bool(false,       title = "Display Benchmark?",               group = "Benchmark")
disp_alpha = input.bool(false,       title = "Display Alpha?",                   group = "Benchmark")

// Pivot Points Strategy
swh = ta.pivothigh(leftBars, rightBars)
swl = ta.pivotlow (leftBars, rightBars)

hprice = 0.0
hprice := not na(swh) ? swh : hprice[1]

lprice = 0.0
lprice := not na(swl) ? swl : lprice[1]

le = false
le := not na(swh) ? true : le[1] and high > hprice ? false : le[1]

se = false
se := not na(swl) ? true : se[1] and low < lprice ? false : se[1]

if le
    strategy.entry('PivRevLE', strategy.long, comment='PivRevLE', stop=hprice + syminfo.mintick)

if se
    strategy.entry('PivRevSE', strategy.short, comment='PivRevSE', stop=lprice - syminfo.mintick)

plot(hprice, color=color.new(color.green, 0), linewidth=2)
plot(lprice, color=color.new(color.red, 0), linewidth=2)


///////////////////
// WEEKLY TABLE //

new_week = weekofyear(time[1]) != weekofyear(time)
new_year = year(time) != year(time[1])

eq       = strategy.equity
bench_eq = close

// benchmark eq
bench_eq_htf = request.security(symb_bench, timeframe.period, close)

if (not use_cur)
    bench_eq := bench_eq_htf

bar_pnl   = eq / eq[1] - 1
bench_pnl = bench_eq / bench_eq[1] - 1



// Current Weekly P&L
cur_week_pnl  = 0.0
cur_week_pnl := bar_index == 0 ? 0 : 
                 time >= from_date and (time[1] < from_date or new_week) ? bar_pnl : 
                 (1 + cur_week_pnl[1]) * (1 + bar_pnl) - 1

// Current Yearly P&L
cur_year_pnl  = 0.0
cur_year_pnl := bar_index == 0 ? 0 : 
                 time >= from_date and (time[1] < from_date or new_year) ? bar_pnl : 
                 (1 + cur_year_pnl[1]) * (1 + bar_pnl) - 1


// Current Weekly P&L - Bench
bench_cur_week_pnl  = 0.0
bench_cur_week_pnl := bar_index == 0 or (time[1] < from_date and time >= from_date) ? 0 : 
                       time >= from_date and new_week ? bench_pnl : 
                       (1 + bench_cur_week_pnl[1]) * (1 + bench_pnl) - 1 

// Current Yearly P&L - Bench
bench_cur_year_pnl  = 0.0
bench_cur_year_pnl := bar_index == 0 ? 0 : 
                       time >= from_date and (time[1] < from_date  or new_year) ? bench_pnl : 
                       (1 + bench_cur_year_pnl[1]) * (1 + bench_pnl) - 1




var week_time = array.new_int(0)
var year_time = array.new_int(0)

var week_pnl = array.new_float(0)
var year_pnl = array.new_float(0)

var bench_week_pnl = array.new_float(0)
var bench_year_pnl = array.new_float(0)


// Filling weekly / yearly pnl arrays
if array.size(week_time) > 0
    if weekofyear(time) == weekofyear(array.get(week_time, array.size(week_time) - 1))
        array.pop(week_pnl)
        array.pop(bench_week_pnl)
        array.pop(week_time)


if array.size(year_time) > 0
    if year(time) == year(array.get(year_time, array.size(year_time) - 1))
        array.pop(year_pnl)
        array.pop(bench_year_pnl)
        array.pop(year_time)


if (time >= from_date)
    array.push(week_time, time)
    array.push(year_time, time)
    
    array.push(week_pnl, cur_week_pnl)
    array.push(year_pnl, cur_year_pnl)
    
    array.push(bench_year_pnl, bench_cur_year_pnl)
    array.push(bench_week_pnl, bench_cur_week_pnl)


// Weekly P&L Table  

table_size = size.tiny
var weekly_table = table(na)

if array.size(year_pnl) > 0 and barstate.islastconfirmedhistory

    weekly_table := table.new(position.bottom_right, 
                 columns=56, rows=array.size(year_pnl) * 3 + 5, border_width=1)

// Fill weekly performance
    table.cell(weekly_table, 0, 0,  'Perf', 
                 bgcolor = #999999, text_size= table_size)


    for numW = 1 to 53 by 1
        table.cell(weekly_table, numW, 0,  str.tostring(numW), 
                 bgcolor= #999999, text_size= table_size)


    table.cell(weekly_table, 54, 0, ' ',    
                 bgcolor = #999999, text_size= table_size)
    table.cell(weekly_table, 55, 0, 'Year', 
                 bgcolor = #999999, text_size= table_size)

    max_abs_y = math.max(math.abs(array.max(year_pnl)), math.abs(array.min(year_pnl)))
    max_abs_m = math.max(math.abs(array.max(week_pnl)), math.abs(array.min(week_pnl)))

    
    for yi = 0 to array.size(year_pnl) - 1 by 1
        table.cell(weekly_table, 0,  yi + 1,
                 str.tostring(year(array.get(year_time, yi))), 
                 bgcolor=#cccccc, text_size=table_size)
                 
        table.cell(weekly_table, 53, yi + 1, ' ',   
                 bgcolor=#999999, text_size=table_size)
                 
        table.cell(weekly_table, 54, yi + 1, ' ',   
                 bgcolor=#999999, text_size=table_size)

        y_color = color.from_gradient(array.get(year_pnl, yi), -max_abs_y, max_abs_y, loss_color, prof_color) 

        table.cell(weekly_table, 55, yi + 1, 
                 str.tostring(math.round(array.get(year_pnl, yi) * 100, prec)), 
                 bgcolor=y_color, text_size=table_size)
    
    int iw_row= na
    int iw_col= na

    for wi = 0 to array.size(week_time) - 2 by 1
        w_row   = year(array.get(week_time, wi)) - year(array.get(year_time, 0)) + 1
        w_col   = weekofyear(array.get(week_time, wi))

        w_color = color.from_gradient(array.get(week_pnl, wi), -max_abs_m, max_abs_m, loss_color, prof_color)
        
        if iw_row + 1 == w_row and iw_col + 1 == w_col
            table.cell(weekly_table, w_col, w_row-1,
                 str.tostring(math.round(array.get(week_pnl, wi) * 100, prec)), 
                 bgcolor=w_color, text_size=table_size)
        else
            table.cell(weekly_table, w_col, w_row,
                 str.tostring(math.round(array.get(week_pnl, wi) * 100, prec)), 
                 bgcolor=w_color, text_size=table_size)
        
        
        iw_row:= w_row
        iw_col:= w_col


    // Fill benchmark performance
    next_row =  array.size(year_pnl) + 1  

    if (disp_bench)
    
        table.cell(weekly_table, 0,  next_row, 'Bench', 
                 bgcolor=#999999, text_size=table_size)
        
        for numW = 1 to 53 by 1
            table.cell(weekly_table, numW, next_row,  str.tostring(numW), 
                 bgcolor= #999999, text_size= table_size)

        table.cell(weekly_table, 54, next_row, ' '   ,   
                 bgcolor = #999999, text_size=table_size)
        table.cell(weekly_table, 55, next_row, 'Year',   
                 bgcolor = #999999, text_size=table_size)
    
        max_bench_abs_y = math.max(math.abs(array.max(bench_year_pnl)), math.abs(array.min(bench_year_pnl)))
        max_bench_abs_w = math.max(math.abs(array.max(bench_week_pnl)), math.abs(array.min(bench_week_pnl)))
    
        for yi = 0 to array.size(year_time) - 1 by 1
            table.cell(weekly_table, 0,  yi + 1 + next_row + 1, 
                 str.tostring(year(array.get(year_time, yi))), 
                 bgcolor=#cccccc, text_size=table_size)

            table.cell(weekly_table, 53, yi + 1 + next_row + 1, ' ',   
                 bgcolor=#999999, text_size=table_size)
            
            table.cell(weekly_table, 54, yi + 1 + next_row + 1, ' ', 
                 bgcolor=#999999, text_size=table_size)
                 
            y_color = color.from_gradient(array.get(bench_year_pnl, yi), -max_bench_abs_y, max_bench_abs_y, loss_color, prof_color)
            table.cell(weekly_table, 55, yi + 1 + next_row + 1, 
                 str.tostring(math.round(array.get(bench_year_pnl, yi) * 100, prec)), 
                 bgcolor=y_color, text_size=table_size)
     
    
        int iw_row1= na
        int iw_col1= na

        for wi = 0 to array.size(week_time) - 1 by 1
            w_row   = year(array.get(week_time, wi)) - year(array.get(year_time, 0)) + 1
            w_col   = weekofyear(array.get(week_time, wi))
        
            w_color = color.from_gradient(array.get(bench_week_pnl, wi), -max_bench_abs_w, max_bench_abs_w, loss_color, prof_color)
    
            if iw_row1 + 1 == w_row and iw_col1 + 1 == w_col
                table.cell(weekly_table, w_col, w_row  + next_row    , 
                 str.tostring(math.round(array.get(bench_week_pnl, wi) * 100, prec)),
                 bgcolor=w_color, text_size=table_size)
            else
                table.cell(weekly_table, w_col, w_row  + next_row + 1, 
                 str.tostring(math.round(array.get(bench_week_pnl, wi) * 100, prec)), 
                 bgcolor=w_color, text_size=table_size)
                
            iw_row1:= w_row
            iw_col1:= w_col
    
    
// Fill Alpha
    if (disp_alpha)
    
        // columns
        next_row :=  array.size(year_pnl) * 2 + 3   
        table.cell(weekly_table, 0,  next_row, 'Alpha', 
                 bgcolor=#999999, text_size= table_size)


        for numW = 1 to 53 by 1
            table.cell(weekly_table, numW, next_row,  str.tostring(numW), 
                 bgcolor= #999999, text_size= table_size)


        table.cell(weekly_table, 54, next_row, ' '   ,  
                 bgcolor=#999999, text_size= table_size)
        table.cell(weekly_table, 55, next_row, 'Year',  
                 bgcolor=#999999, text_size= table_size)
        
        
        
        max_alpha_abs_y = 0.0
        for yi = 0 to array.size(year_time) - 1 by 1
            if (math.abs(array.get(year_pnl, yi)  - array.get(bench_year_pnl, yi)) > max_alpha_abs_y)
                max_alpha_abs_y := math.abs(array.get(year_pnl, yi)  - array.get(bench_year_pnl, yi))
    
        max_alpha_abs_w = 0.0
        for wi = 0 to array.size(week_pnl) - 1 by 1
            if (math.abs(array.get(week_pnl, wi) - array.get(bench_week_pnl, wi)) > max_alpha_abs_w)
                max_alpha_abs_w := math.abs(array.get(week_pnl, wi) - array.get(bench_week_pnl, wi))
    
    
        for yi = 0 to array.size(year_time) - 1 by 1
            table.cell(weekly_table, 0,  yi + 1 + next_row + 1, 
                 str.tostring(year(array.get(year_time, yi))), 
                 bgcolor=#cccccc, text_size= table_size)
                 
            table.cell(weekly_table, 53, yi + 1 + next_row + 1, ' ',   
                 bgcolor=#999999, text_size= table_size)
                 
            table.cell(weekly_table, 54, yi + 1 + next_row + 1, ' ',   
                 bgcolor=#999999, text_size= table_size)

            y_color = color.from_gradient(array.get(year_pnl, yi)  - array.get(bench_year_pnl, yi), -max_alpha_abs_y, max_alpha_abs_y, loss_color, prof_color)
            table.cell(weekly_table, 55, yi + 1 + next_row + 1,
                 str.tostring(math.round((array.get(year_pnl, yi)  - array.get(bench_year_pnl, yi)) * 100, prec)), 
                 bgcolor=y_color, text_size= table_size)
     
     
        int iw_row2= na
        int iw_col2= na
        
        for wi = 0 to array.size(week_time) - 1 by 1
            w_row   = year(array.get(week_time, wi)) - year(array.get(year_time, 0)) + 1
            w_col   = weekofyear(array.get(week_time, wi))
            w_color = color.from_gradient(array.get(week_pnl, wi) - array.get(bench_week_pnl, wi), -max_alpha_abs_w, max_alpha_abs_w, loss_color, prof_color)
    
            if iw_row2 + 1 == w_row and iw_col2 + 1 == w_col
                table.cell(weekly_table, w_col, w_row  + next_row , 
                     str.tostring(math.round((array.get(week_pnl, wi) - array.get(bench_week_pnl, wi)) * 100, prec)), 
                     bgcolor=w_color, text_size= table_size)
            else
                table.cell(weekly_table, w_col, w_row  + next_row + 1 , 
                     str.tostring(math.round((array.get(week_pnl, wi) - array.get(bench_week_pnl, wi)) * 100, prec)), 
                     bgcolor=w_color, text_size= table_size)
        
            iw_row2:= w_row
            iw_col2:= w_col


Больше