Chiến lược này áp dụng cách tiếp cận đột phá cấp để đi dài hoặc ngắn trong một số điều kiện đột phá nhất định, và có khả năng tự động kiểm tra để tìm sự kết hợp thông số tối ưu.
Các thông số đầu vào bao gồm ngày xem lại, tỷ lệ lợi nhuận, tỷ lệ dừng lỗ, và các thông số tự động kiểm tra lại như phạm vi xem lại, phạm vi lợi nhuận / dừng lỗ v.v.
Trong quá trình backtesting, đi qua các kết hợp khác nhau của lookback, lấy lợi nhuận và dừng lỗ, và ghi lại PnL cho mỗi sự kết hợp.
Logic tín hiệu đột phá: dài khi phá vỡ gần bên trên dải trên và không phải thanh đầu vào, ngắn khi phá vỡ gần bên dưới dải dưới và không phải thanh đầu vào.
Điều kiện dừng lỗ: nếu không có lợi nhuận và dừng lỗ được kích hoạt, thoát khỏi giao dịch.
Điều kiện lấy lợi nhuận: nếu không dừng lại và lấy lợi nhuận được kích hoạt, thoát khỏi giao dịch.
Hiển thị bảng kết quả backtest chi tiết, có thể sắp xếp theo tỷ lệ thắng, lợi nhuận ròng hoặc số lượng giao dịch dựa trên cài đặt của người dùng.
Kiểm tra tự động có thể nhanh chóng tìm ra các bộ tham số tối ưu mà không cần kiểm tra thủ công.
Loại kết quả backtest linh hoạt theo tỷ lệ thắng, lợi nhuận ròng, số lượng giao dịch vv theo nhu cầu.
Hiển thị PnL cho mỗi giao dịch.
Các tham số backtest có thể tùy chỉnh để kiểm tra không gian tham số rộng hơn để tìm tối ưu toàn cầu.
Quy tắc giao dịch đơn giản và rõ ràng dễ hiểu và thực hiện.
Thời gian backtest ngắn có thể dẫn đến kết quả không ổn định.
Các giao dịch thường xuyên có xu hướng trượt ảnh hưởng đến lợi nhuận.
Kiểm tra ngược bằng một dụng cụ có thể không đại diện.
Giải pháp: kiểm tra sự ổn định của các thông số trên các sản phẩm và khung thời gian.
Bỏ qua chi phí giao dịch dẫn đến sự thiên vị trong kết quả.
Tăng kích thước tối ưu hóa như thêm dừng lại hoặc giới hạn giao dịch.
Tối ưu hóa điều kiện nhập cảnh với bộ lọc xu hướng.
Nâng cao lợi nhuận / dừng lỗ như lợi nhuận năng động hoặc dừng lỗ.
Giới thiệu máy học để tối ưu hóa tham số.
Tối ưu hóa cấu trúc mã để kiểm tra lại nhanh hơn.
Kiểm tra độ bền của tham số trên các sản phẩm và khung thời gian.
Xem xét tích hợp khả năng giao dịch tự động.
Chiến lược có logic rõ ràng và đơn giản, tự động kiểm tra lại cho phép điều chỉnh các tham số nhanh chóng, màn hình PnL tạo điều kiện cải tiến hơn nữa. Rủi ro tồn tại nhưng có thể được giảm thông qua tối ưu hóa đa chiều, với giá trị thực tế mạnh mẽ. Tóm lại, chiến lược này được trang bị các công cụ kiểm tra lại tự động có thể giúp các nhà giao dịch nhanh chóng phát triển các hệ thống giao dịch ổn định dựa trên các khái niệm đột phá đơn giản.
/*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)