아키텍처 돌파구 백테스팅 전략

저자:차오장, 날짜: 2023-10-17 17:26:03
태그:

img

전반적인 설명

이 전략은 특정 돌파구 조건에서 길거나 짧게 이동하기 위해 레벨 돌파구 접근 방식을 채택하고 최적의 매개 변수 조합을 찾기 위해 자동 백테스팅 기능을 갖추고 있습니다.

원칙

  1. 입력 매개 변수에는 룩백 데이, 영업률, 스톱 로스 비율, 룩백 범위, 영업/스톱 로스 범위 등 자동 백테스팅 매개 변수 등이 포함됩니다.

  2. 백테스팅 중에, 룩백의 다양한 조합을 통과하고, 수익을 취하고, 손실을 멈추고, 각 조합에 대한 PnL을 기록합니다.

  3. 돌파 신호 논리: 입구 바가 아닌 상단역 위에 근접 파업이 있을 때 길고, 입구 바가 아닌 하단역 아래에 근접 파업이 있을 때 짧다.

  4. 스톱 로스 조건: 수익을 취하지 않고 스톱 로스가 트리거되면 거래를 종료합니다.

  5. 이윤을 취하는 조건: 중단되지 않고 이윤을 취하는 것이 시작되면 거래를 종료합니다.

  6. 상세한 백테스트 결과 표를 표시합니다. 사용자 설정에 따라 승률, 순이익 또는 거래 수에 따라 정렬할 수 있습니다.

장점

  1. 자동 백테스트는 수동 테스트 없이 최적의 매개 변수 세트를 빠르게 찾을 수 있습니다.

  2. 필요에 따라 승률, 순이익, 거래 수 등에 따라 역 테스트 결과를 유연하게 분류하십시오.

  3. 각 거래에 대한 PnL을 시각화하십시오.

  4. 커스터마이징 가능한 백테스트 매개 변수 더 넓은 매개 변수 공간을 테스트하여 글로벌 최적을 찾습니다.

  5. 단순하고 명확한 거래 규칙, 이해하기 쉽고 실행하기 쉽습니다.

위험 과 해결책

  1. 짧은 백테스트 기간은 불안정한 결과를 초래할 수 있습니다. 해결책: 더 긴 백테스트 기간을 사용하십시오.

  2. 수익성에 영향을 미치는 미끄러지기 쉬운 빈번한 거래. 해결책: 적당하게 수익/손실을 중지하는 수준을 완화하십시오.

  3. 단일 기기 백테스트는 대표적이지 않을 수 있습니다. 해결책: 안정적인 매개 변수 집합을 찾기 위해 다른 제품에 대한 테스트.

  4. 과도하게 최적화된 매개 변수는 과도한 부착을 유발합니다. 해결책: 제품과 시간 프레임에 걸쳐 매개 변수의 안정성을 테스트하십시오.

  5. 거래 비용을 무시하면 결과의 편향이 발생합니다. 해결책: 합리적인 수수료 설정을 사용하십시오.

개선 방향

  1. 트래일링 스톱이나 트레이드 리미트를 추가하는 것 같은 최적화 차원을 높여주세요.

  2. 트렌드 필터로 진입 조건을 최적화합니다.

  3. 이윤/손실을 멈추는 것을 향상시키면, 동적 이윤을 얻거나 손해를 멈추는 것을 막습니다.

  4. 매개 변수 최적화를 위한 기계 학습을 도입합니다.

  5. 더 빠른 백테스팅을 위해 코드 구조를 최적화합니다.

  6. 제품 및 시간 프레임에 걸쳐 매개 변수 견고성을 테스트합니다.

  7. 자동 거래 기능을 통합하는 것을 고려하십시오.

결론

이 전략은 명확하고 간단한 논리를 가지고 있으며, 자동 백테스팅은 빠른 매개 변수 조정, PnL 디스플레이는 추가 개선을 촉진합니다. 위험은 존재하지만 강력한 실용적 가치와 함께 다차원 최적화를 통해 감소 할 수 있습니다. 요약하자면, 자동 백테스팅 도구로 장착 된이 전략은 거래자가 간단한 브레이크아웃 개념을 기반으로 안정적인 거래 시스템을 신속하게 개발하는 데 도움이 될 수 있습니다.


/*backtest
start: 2023-09-16 00:00:00
end: 2023-10-16 00:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/

// © -_-
//@version=5
// strategy("[-_-] LBAB", process_orders_on_close=true, overlay=true, max_labels_count=500, max_lines_count=500, max_boxes_count=500, default_qty_type=strategy.cash, default_qty_value=100, initial_capital=10000, commission_type=strategy.commission.percent, commission_value=0.075)

// Inputs
lookback = input.int(2, title="Lookback", minval=2, maxval=15)      
tp = input.float(5, title="TP (%)", minval=1, maxval=10000)            
sl = input.float(5, title="SL (% from Low)", minval=1, maxval=100)  
com = input.float(0.075, title="Commission (%)", minval=0, maxval=50)

min_lookback_tr = input.float(2, title="Min Lookback", minval=1, maxval=500, inline="tr_lookback", group="Optimisation") 
max_lookback_tr = input.float(5, title="Max Lookback", minval=1, maxval=500, inline="tr_lookback", group="Optimisation") 
min_tp_tr = input.float(5, title="Min TP (%)", minval=1, maxval=10000, inline="tr_tp", group="Optimisation") 
max_tp_tr = input.float(10, title="Max TP (%)", minval=1, maxval=10000, inline="tr_tp", group="Optimisation") 
min_sl_tr = input.float(1, title="Min SL (%)", minval=1, maxval=100, inline="tr_sl", group="Optimisation") 
max_sl_tr = input.float(5, title="Max SL (%)", minval=1, maxval=100, inline="tr_sl", group="Optimisation") 
imp_perc_profit = input.bool(true, title="Percentage profitable", group="Optimisation")
imp_netprofit = input.bool(false, title="Net profit", group="Optimisation")
imp_numtrades = input.bool(false, title="Number of trades", group="Optimisation")
table_pos = input.string("Bottom Right", title="Position", options=["Top Left", "Top Center", "Top Right", "Middle Left", "Middle Center", "Middle Right", "Bottom Left", "Bottom Center", "Bottom Right"], group="Table")
table_font_size = input.string("Normal", title="Font size", options=["Auto", "Tiny", "Small", "Normal", "Large"], group="Table")

// Table parameters
table_pos_ = switch table_pos 
    "Top Left" => position.top_left
    "Top Center" => position.top_center
    "Top Right" => position.top_right
    "Middle Left" => position.middle_left
    "Middle Center" => position.middle_center
    "Middle Right" => position.middle_right
    "Bottom Left" => position.bottom_left
    "Bottom Center" => position.bottom_center
    "Bottom Right" => position.bottom_right

table_font_size_ = switch table_font_size
    "Auto" => size.auto
    "Tiny" => size.tiny
    "Small" => size.small
    "Normal" => size.normal
    "Large" => size.large

// Sorting function (first element will be largest)
sortArr(arr, arr_index) =>
    n = array.size(arr) - 1 
    for i = 0 to n - 1
        for j = 0 to n - i - 1
            if array.get(arr, j) < array.get(arr, j + 1)
                temp = array.get(arr, j)
                temp_index = array.get(arr_index, j)
                array.set(arr, j, array.get(arr, j + 1))
                array.set(arr, j + 1, temp)
                array.set(arr_index, j, array.get(arr_index, j + 1))
                array.set(arr_index, j + 1, temp_index)

// Safe checks
if min_lookback_tr > max_lookback_tr 
    runtime.error("Min Lookback must be less than Max Lookback")
if min_tp_tr > max_tp_tr 
    runtime.error("Min Take Profit must be less than Max Take Profit")
if min_sl_tr > max_sl_tr
    runtime.error("Min Stop Loss must be less than Max Stop Loss")

// 
tp_min_ = int(min_tp_tr / 1)
tp_max_ = int(max_tp_tr / 1)

sl_min_ = int(min_sl_tr / 1)
sl_max_ = int(max_sl_tr / 1)

// Size for arrays
arr_size = int((max_lookback_tr - min_lookback_tr + 1) * (tp_max_ - tp_min_ + 1) * (sl_max_ - sl_min_ + 1))

// Arrays
var arr_bi = array.new_int(arr_size, na)           // bar_index of Smash Day
var arr_in_pos = array.new_bool(arr_size, false)   // are we in a position?

var arr_params = array.new_string(arr_size, "")
var arr_wonlost = array.new_string(arr_size, "")
var arr_profit = array.new_float(arr_size, 0)

// Testing what parameters are best
index = 0

// Lookback
for lookback_i = min_lookback_tr to max_lookback_tr
    // Take profit
    for tp_i = tp_min_ to tp_max_
        // Stop loss
        for sl_i = sl_min_ to sl_max_
            // Parameters of current iteration
            lookback_ = lookback_i
            tp_ = tp_i
            sl_ = sl_i

            //
            if array.get(arr_params, index) == ""
                array.set(arr_params, index, str.tostring(lookback_) + " " + str.tostring(tp_) + " " + str.tostring(sl_))

            // Was there an entry?
            was_edone = false

            // If entry price reached
            if not array.get(arr_in_pos, index) and not na(array.get(arr_bi, index))
                if high >= high[bar_index - array.get(arr_bi, index)] and bar_index != array.get(arr_bi, index)
                    array.set(arr_in_pos, index, true)
                    was_edone := true

            // If we're in a position
            if array.get(arr_in_pos, index) and bar_index != array.get(arr_bi, index) and not was_edone
                low_sl = low[bar_index - array.get(arr_bi, index)] * (1 - sl_ / 100)
                high_ep = high[bar_index - array.get(arr_bi, index)]
                high_tp = high_ep * (1 + tp_ / 100)

                amount = 100

                // Stop loss
                if low <= low_sl
                    array.set(arr_in_pos, index, false)
                    array.set(arr_wonlost, index, array.get(arr_wonlost, index) + "0")
                    array.set(arr_profit, index, array.get(arr_profit, index) - math.abs(amount / high_ep * low_sl - amount) - com / 100 * amount * 2)
                    array.set(arr_bi, index, na)
                // Take profit
                if high >= high_tp
                    array.set(arr_in_pos, index, false)
                    array.set(arr_wonlost, index, array.get(arr_wonlost, index) + "1")
                    array.set(arr_profit, index, array.get(arr_profit, index) + math.abs(amount / high_ep * high_tp - amount) - com / 100 * amount * 2)
                    array.set(arr_bi, index, na)

            // Entry condition
            cond = barstate.isconfirmed and close < low[1] and high[1] < high[lookback_ + 1] //and not array.get(arr_in_pos, index) 

            // New entry price
            if cond and not array.get(arr_in_pos, index)
                array.set(arr_bi, index, bar_index)
            
            // Update index
            index := index + 1

// Checking the results
var table t = na
var result_index = array.new_int(0, na)
var result_arr_winrate = array.new_float(0, na)
var result_arr_tradenum = array.new_int(0, na)
var sort_array = array.new_float(0, na)

if (barstate.islast or barstate.islastconfirmedhistory) and na(t)
    for i = 0 to array.size(arr_params) - 1
        wins = 0
        losses = 0
        arr = array.get(arr_wonlost, i)
        for j = 0 to str.length(arr) - 1
            str_ = str.substring(arr, j, j + 1)
            if str_ == "0"
                losses := losses + 1
            if str_ == "1"
                wins := wins + 1
        // Push percentage profitable trades
        perc_profit = math.round(wins / (wins + losses) * 100, 2)
        array.push(result_arr_winrate, perc_profit)
        // Push number of trades
        trade_num = str.length(array.get(arr_wonlost, i))
        array.push(result_arr_tradenum, trade_num)
        // Push index
        array.push(result_index, i)
        // For combined sorting                          
        array.push(sort_array, (imp_netprofit ? array.get(arr_profit, i) : 1) * (imp_perc_profit ? perc_profit : 1) * (imp_numtrades ? trade_num : 1))

    // Sort
    sortArr(array.copy(sort_array), result_index)

    t := table.new(columns=6, rows=13, bgcolor=color.white, border_color=color.new(color.blue, 0), border_width=1, frame_color=color.new(color.blue, 0), frame_width=1, position=table_pos_)

    table.cell(t, 0, 0, "% Profitable" + (imp_perc_profit ? " ↓" : ""), bgcolor=imp_perc_profit ? color.rgb(23, 18, 25) : color.white, text_color=imp_perc_profit ? color.white : color.black, text_size=table_font_size_)
    table.cell(t, 1, 0, "Net Profit" + (imp_netprofit ? " ↓" : ""), bgcolor=imp_netprofit ? color.rgb(23, 18, 25) : color.white, text_color=imp_netprofit ? color.white : color.black, text_size=table_font_size_)
    table.cell(t, 2, 0, "# of trades" + (imp_numtrades ? " ↓" : ""), bgcolor=imp_numtrades ? color.rgb(23, 18, 25) : color.white, text_color=imp_numtrades ? color.white : color.black, text_size=table_font_size_)
    table.cell(t, 3, 0, "Lookback", text_size=table_font_size_)
    table.cell(t, 4, 0, "Take Profit %", text_size=table_font_size_)
    table.cell(t, 5, 0, "Stop Loss %", text_size=table_font_size_)

    counter = 0
    forloop_counter = math.min(array.size(result_index) - 1, 10)
    for i = 0 to forloop_counter
        i_ = array.get(result_index, i)
        params_ = str.split(array.get(arr_params, i_), " ")
        col_ = color.new(color.blue, 75)
        table.cell(t, 0, i + 1, str.tostring(array.get(result_arr_winrate, i_)) + "%", bgcolor=col_, text_size=table_font_size_)
        table.cell(t, 1, i + 1, str.tostring(math.round(array.get(arr_profit, i_), 2)) + "$", bgcolor=col_, text_size=table_font_size_)
        table.cell(t, 2, i + 1, str.tostring(array.get(result_arr_tradenum, i_)), bgcolor=col_, text_size=table_font_size_)
        table.cell(t, 3, i + 1, array.get(params_, 0), bgcolor=col_, text_size=table_font_size_)
        table.cell(t, 4, i + 1, array.get(params_, 1), bgcolor=col_, text_size=table_font_size_)
        table.cell(t, 5, i + 1, array.get(params_, 2), bgcolor=col_, text_size=table_font_size_)
        counter := counter + 1

    // Warn if timeframe is <= 10 minutes
    if timeframe.in_seconds(timeframe.period) <= 600
        table.cell(t, 0, forloop_counter + 2, "Timeframe might be too low", bgcolor=color.orange, text_size=table_font_size_, tooltip="Selected timeframe might be too low and cause an error")
        table.merge_cells(t, 0, forloop_counter + 2, 5, forloop_counter + 2)

// Strategy
var int bi = na
var int pos_bi = na

// Buy condition
cond = barstate.isconfirmed and close < low[1] and high[1] < high[lookback + 1] and strategy.position_size == 0 

// Stop loss, Take profit
if strategy.position_size[1] == 0 and strategy.position_size > 0 and bar_index != bi
    strategy.exit("TP/SL", "Long", stop=low[bar_index - bi] * (1 - sl / 100), limit=high[bar_index - bi] * (1 + tp / 100))
    pos_bi := bar_index

// Buy
if cond 
    strategy.order("Long", strategy.long, stop=high)
    bi := bar_index

// Box
if strategy.position_size[1] != 0 and strategy.position_size == 0
    tn = strategy.closedtrades - 1
    penp = strategy.closedtrades.entry_price(tn)
    pexp = strategy.closedtrades.exit_price(tn)





더 많은