该策略采用级别突破的方式,在一定突破条件下做多做空,并具有自动回测功能来找到最佳参数组合。
输入参数包括回看天数、止盈百分比、止损百分比,以及自动回测参数如回看天数、止盈止损范围等。
回测时遍历回看天数、止盈百分比、止损百分比的各种组合,记录每种组合下的盈亏情况。
突破信号判断:收盘价上穿upper band且不是入市时柱,做多;收盘价下穿lower band且不是入市时柱,做空。
止损条件判断:若未止盈且触发止损线,则止损出场。
止盈条件判断:若未止损且触发止盈线,则止盈出场。
显示回测结果明细表格,可根据用户设置按获利率或净利润或交易次数排序。
自动回测功能可以快速找到最佳参数组合,无需手工测试。
可根据盈利率、净利润、交易次数等排序回测结果,灵活选择符合自己需要的最优参数。
可视化显示每笔交易的盈亏情况。
回测参数可自定义,可以测试更广的参数空间,找到全局最优。
策略交易规则简单清晰,容易理解实现。
回测周期短可能导致结果不稳定。解决方法:设置更长的回测周期。
交易频繁容易造成滑点影响盈利。解决方法:适当放宽止盈止损幅度。
单一商品回测结果可能不具代表性。解决方法:回测不同品种,找到稳定的参数组合。
参数过优化可能导致过拟合。解决方法:验证参数在不同品种和时间周期的稳定性。
忽略交易成本可能导致回测结果偏差。解决方法:设置合理的手续费参数。
增加参数优化维度,如加入移动止损或交易次数限制等。
优化入市条件,结合趋势指标过滤。
优化止盈止损策略,如动态止盈或追踪止损等。
增加机器学习等算法辅助参数优化。
优化代码结构,提高回测速度。
在多品种多周期验证参数稳定性。
考虑整合自动交易功能。
该策略总体思路清晰易懂,自动回测功能可以快速优化参数,显示盈亏情况有利于策略改进。存在一定风险需要注意,但可通过多方面优化不断改进,具有很强的实用价值。整体来说,该策略利用简单突破思路,配备自动回测工具,可以辅助交易者快速建立稳定的交易系统。
/*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)