策略
介绍
Pine Script™ 策略模拟历史和实时数据上的交易执行,以促进交易系统的回溯测试和前向测试。它们包含许多与 Pine Script™ 指标相同的功能,同时提供下达、修改和取消假设订单并分析结果的能力。
当脚本使用
strategy()
函数进行声明时,它将获得对strategy.*
命名空间的访问权限,在该命名空间中它可以调用函数和变量来模拟订单并访问基本策略信息。此外,脚本将在“策略测试器”选项卡中外部显示信息和模拟结果。
一个简单的策略示例
以下脚本是一个简单的策略,模拟在两个移动平均线交叉时进入多头或空头仓位:
//@version=5
strategy("test", overlay = true)
// Calculate two moving averages with different lengths.
float fastMA = ta.sma(close, 14)
float slowMA = ta.sma(close, 28)
// Enter a long position when `fastMA` crosses over `slowMA`.
if ta.crossover(fastMA, slowMA)
strategy.entry("buy", strategy.long)
// Enter a short position when `fastMA` crosses under `slowMA`.
if ta.crossunder(fastMA, slowMA)
strategy.entry("sell", strategy.short)
// Plot the moving averages.
plot(fastMA, "Fast MA", color.aqua)
plot(slowMA, "Slow MA", color.orange)
注意:
- 该
strategy("test" overlay = true)
行声明该脚本是一个名为“测试”的策略,其视觉输出覆盖在主图表窗格上。 - strategies.entry()
是脚本用来模拟“买入”和“卖出”订单的命令。当脚本下订单时,它还会
id
在图表上绘制订单,并用箭头指示方向。 - 两个 plot() 函数用两种不同的颜色绘制移动平均线以供视觉参考。
将策略应用于图表
要测试策略,请将其应用于图表。您可以使用“指标和策略”对话框中的内置策略,也可以在 Pine 编辑器中编写自己的策略。单击“Pine 编辑器”选项卡中的“添加到图表”以将脚本应用于图表:
策略脚本编译并应用到图表后,将在主图表窗格上绘制订单标记,并在下面的“策略测试器”选项卡中显示模拟的性能结果:
策略测试员
所有使用strategy()函数声明的脚本都可以使用策略测试器模块 。用户可以从图表下方的“策略测试器”选项卡访问此模块,在这里他们可以方便地可视化他们的策略并分析假设的绩效结果。
概述
策略测试器的“概览”选项卡 显示了模拟交易序列中的基本绩效指标以及权益和回撤曲线,可让您快速查看策略绩效,而无需深入了解细节。本节中的图表以初始值为中心的基线图形式显示策略的权益曲线,以线图形式显示买入并持有的权益曲线,以直方图形式显示回撤曲线。用户可以使用图表下方的选项切换这些图并将其缩放为绝对值或百分比。
注意:
- 概览图使用两个刻度;左边是权益曲线,右边是回撤曲线。
- 当用户点击这些图上的某个点时,主图表视图将指向交易结束的点。
绩效摘要
模块的“绩效摘要”选项卡全面概述了策略的绩效指标。它显示三列:一列用于所有交易,一列用于所有多头,一列用于所有空头,为交易者提供有关策略的多头、空头和整体模拟交易绩效的更详细见解。
交易清单
交易清单选项卡提供了对策略模拟交易的详细了解,其中包含基本信息,包括执行日期和时间、使用的订单类型(进入或退出)、交易的合约/股票/手/单位数量和价格,以及一些关键的交易绩效指标。
注意:
- 用户可以通过点击此列表中的特定交易来查看图表上特定交易的时间。
- 通过点击列表上方的“交易编号”字段,用户可以从第一笔交易开始按升序排列交易,或者从最后一笔交易开始按降序排列交易。
特性
属性选项卡提供有关策略配置及其所应用数据集的详细信息。其中包括策略的日期范围、符号信息、脚本设置和策略属性。
- 日期范围- 包括模拟交易的日期范围和总可用的回溯测试范围。
- 符号信息- 包含符号名称和经纪人/交易所、图表的时间范围和类型、刻度大小、图表的点值和基础货币。
- 策略输入- 概述脚本设置中“输入”选项卡中提供的策略脚本中使用的各种参数和变量。
- 策略属性- 提供交易策略配置的概述。其中包括初始资本、基础货币、订单规模、保证金、金字塔式交易、佣金和滑点等基本详细信息。此外,本节还重点介绍了对策略计算行为所做的任何修改。
经纪商模拟器
TradingView 使用经纪商模拟器来模拟交易策略的表现。与实际交易不同,模拟器严格使用可用的图表价格进行订单模拟。因此,模拟只能在收盘后进行历史交易,并且只能在新的价格波动时进行实时交易。有关此行为的更多信息,请参阅 Pine Script™ 执行模型。
由于模拟器只能使用图表数据,因此它会对条形图内的价格变动做出假设。它使用条形图的开盘价、最高价和最低价来推断条形图内的活动,同时使用以下逻辑计算订单成交:
- 如果最高价比最低价更接近开盘价,则假定价格在柱状图上按以下顺序移动:开盘价 → 最高价 → 最低价 → 收盘价。
- 如果最低价比最高价更接近开盘价,则假定价格在柱状图上按以下顺序移动:开盘价 → 最低价 → 最高价 → 收盘价。
- 经纪商模拟器假设柱状图内的价格之间不存在差距,这意味着整个柱状图内的价格范围都可用于订单执行。但是,在执行固定价格订单(除市价订单外的所有订单)时,会考虑前一个柱状图收盘价与当前柱状图开盘价之间的差距。如果订单价格在两个柱状图之间的差距内交叉,则订单将在下一个柱状图开盘时执行,而不是在指定价格执行。
条形放大镜
高级帐户持有者可以通过策略 ()use_bar_magnifier
函数的参数
或脚本设置“属性”选项卡中的“使用条形放大器”输入来覆盖经纪商模拟器的条形图内假设。条形放大器
检查比图表时间范围小的数据,以获取有关条形图内价格行为的更详细信息,从而允许在模拟期间更精确地执行订单。
为了演示,以下脚本在
时间
值与 交叉时
在 处下达“买入”限价订单entryPrice
,在 处下达“退出”限价订单,并绘制两条水平线以可视化订单价格。该脚本还使用 突出显示背景以指示策略下达订单的时间:exitPrice
orderTime
orderColor
//@version=5
strategy("Bar Magnifier Demo", overlay = true, use_bar_magnifier = false)
//@variable The UNIX timestamp to place the order at.
int orderTime = timestamp("UTC", 2023, 3, 22, 18)
//@variable Returns `color.orange` when `time` crosses the `orderTime`, false otherwise.
color orderColor = na
// Entry and exit prices.
float entryPrice = hl2 - (high - low)
float exitPrice = entryPrice + (high - low) * 0.25
// Entry and exit lines.
var line entryLine = na
var line exitLine = na
if ta.cross(time, orderTime)
// Draw new entry and exit lines.
entryLine := line.new(bar_index, entryPrice, bar_index + 1, entryPrice, color = color.green, width = 2)
exitLine := line.new(bar_index, exitPrice, bar_index + 1, exitPrice, color = color.red, width = 2)
// Update order highlight color.
orderColor := color.new(color.orange, 80)
// Place limit orders at the `entryPrice` and `exitPrice`.
strategy.entry("Buy", strategy.long, limit = entryPrice)
strategy.exit("Exit", "Buy", limit = exitPrice)
// Update lines while the position is open.
else if strategy.position_size > 0.0
entryLine.set_x2(bar_index + 1)
exitLine.set_x2(bar_index + 1)
bgcolor(orderColor)
正如我们在上图中看到的,经纪商模拟器假设,在“买入”订单成交的柱状图上,柱状图内价格从开盘价变为高点,然后从高点变为低点,然后从低点变为收盘价,这意味着模拟器假设“退出”订单无法在同一柱状图上成交。然而,use_bar_magnifier = true
在声明语句中包含这些内容后,我们看到了不同的情况:
订单和条目
就像在现实生活中的交易一样,Pine 策略使用订单来管理仓位。在这种情况下,订单是模拟市场行为的命令,交易是订单成交后的结果。因此,要使用 Pine 进入或退出仓位,用户必须创建带有参数的订单,以指定其行为方式。
为了更深入地了解订单如何运作以及它们如何成为交易,让我们编写一个简单的策略脚本:
//@version=5
strategy("My strategy", overlay = true, margin_long = 100, margin_short = 100)
//@function Displays text passed to `txt` when called.
debugLabel(txt) =>
label.new(
bar_index, high, text = txt, color=color.lime, style = label.style_label_lower_right,
textcolor = color.black, size = size.large
)
longCondition = bar_index % 20 == 0 // true on every 20th bar
if (longCondition)
debugLabel("Long entry order created")
strategy.entry("My Long Entry Id", strategy.long)
strategy.close_all()
在此脚本中,我们定义了一个,longCondition
只要 可bar_index
被 20 整除,即每 20 个条形图,该条件即为真。该策略在
if结构中使用此条件,使用strategies.entry()
模拟入场订单,
并使用用户定义函数在入场价格处绘制标签
debugLabel()
。该脚本从全局范围调用
strategies.close_all()
来模拟平仓任何未平仓头寸的市场订单。让我们看看将脚本添加到图表后会发生什么:
图表上的蓝色箭头表示入场位置,紫色箭头标记策略平仓的位置。请注意,标签位于实际入场点之前,而不是出现在同一条柱上 - 这是实际的订单。默认情况下,Pine 策略会等待下一个可用的价格刻度再执行订单,因为在同一刻度上执行订单是不现实的。此外,它们会在每个历史柱的收盘时重新计算,这意味着在这种情况下,下一个可以执行订单的刻度是下一个柱的开盘价。因此,默认情况下,所有订单都会延迟一个图表柱。
需要注意的是,尽管脚本从全局范围调用
strategy.close_all()
,强制在每个柱状图上执行,但如果策略未模拟未平仓头寸,则函数调用不会执行任何操作。如果有未平仓头寸,该命令会发出市价单来平仓,并在下一个可用的报价时执行。例如,当
longCondition
柱状图 20 上的值为真时,策略会下达入场订单,在下一个报价时(即柱状图 21 的开盘价)执行。一旦脚本在该柱状图收盘时重新计算其值,该函数会下达平仓订单,并在柱状图 22 的开盘价执行。
订单类型
Pine Script™ 策略允许用户根据自己的特定需求模拟不同的订单类型。主要类型包括市价单、 限价单、止损单和止损限价单。
市价订单
市价单是最基本的订单类型。它们指示一种策略,即无论价格如何,尽快买入或卖出证券。因此,它们总是在下一个可用的价格点执行。默认情况下,所有strategy.*()
生成订单的功能都会专门生成市价单。
以下脚本在bar_index
能被 整除时模拟多头市场订单。否则,在能被 整除2 * cycleLength
时模拟空头市场订单,从而形成每条柱状图交替进行多头和空头交易的策略:bar_index
cycleLength
cycleLength
//@version=5
strategy("Market order demo", overlay = true, margin_long = 100, margin_short = 100)
//@variable Number of bars between long and short entries.
cycleLength = input.int(10, "Cycle length")
//@function Displays text passed to `txt` when called.
debugLabel(txt, lblColor) => label.new(
bar_index, high, text = txt, color = lblColor, textcolor = color.white,
style = label.style_label_lower_right, size = size.large
)
//@variable Returns `true` every `2 * cycleLength` bars.
longCondition = bar_index % (2 * cycleLength) == 0
//@variable Returns `true` every `cycleLength` bars.
shortCondition = bar_index % cycleLength == 0
// Generate a long market order with a `color.green` label on `longCondition`.
if longCondition
debugLabel("Long market order created", color.green)
strategy.entry("My Long Entry Id", strategy.long)
// Otherwise, generate a short market order with a `color.red` label on `shortCondition`.
else if shortCondition
debugLabel("Short market order created", color.red)
strategy.entry("My Short Entry Id", strategy.short)
限价订单
限价订单命令策略以特定价格或更优价格(多头订单低于指定价格,空头订单高于指定价格)进入头寸。当当前市场价格优于订单命令的
limit
参数时,订单将立即执行,而无需等待市场价格达到限价水平。
要在脚本中模拟限价订单,请使用参数将价格值传递给订单下达命令limit
。以下示例在收盘价低于 100 个条形图之前 800 个刻度的位置下达限价订单
last_bar_index
:
//@version=5
strategy("Limit order demo", overlay = true, margin_long = 100, margin_short = 100)
//@function Displays text passed to `txt` and a horizontal line at `price` when called.
debugLabel(price, txt) =>
label.new(
bar_index, price, text = txt, color = color.teal, textcolor = color.white,
style = label.style_label_lower_right, size = size.large
)
line.new(
bar_index, price, bar_index + 1, price, color = color.teal, extend = extend.right,
style = line.style_dashed
)
// Generate a long limit order with a label and line 100 bars before the `last_bar_index`.
if last_bar_index - bar_index == 100
limitPrice = close - syminfo.mintick * 800
debugLabel(limitPrice, "Long Limit order created")
strategy.entry("Long", strategy.long, limit = limitPrice)
请注意,脚本在交易前几条柱状图上放置标签并开始绘制线条。只要价格高于该limitPrice
值,订单就无法成交。一旦市场价格达到限制,策略就会在柱状图中间执行交易。如果我们将设置为
高于柱状图收盘limitPrice
价 800 个刻度,而不是低于,订单将立即成交,因为价格已经处于更佳值:
//@version=5
strategy("Limit order demo", overlay = true, margin_long = 100, margin_short = 100)
//@function Displays text passed to `txt` and a horizontal line at `price` when called.
debugLabel(price, txt) =>
label.new(
bar_index, price, text = txt, color = color.teal, textcolor = color.white,
style = label.style_label_lower_right, size = size.large
)
line.new(
bar_index, price, bar_index + 1, price, color = color.teal, extend = extend.right,
style = line.style_dashed
)
// Generate a long limit order with a label and line 100 bars before the `last_bar_index`.
if last_bar_index - bar_index == 100
limitPrice = close + syminfo.mintick * 800
debugLabel(limitPrice, "Long Limit order created")
strategy.entry("Long", strategy.long, limit = limitPrice)
止损和止损限价订单
止损单命令策略在价格达到指定stop
价格或更差值(多头订单高于指定值,空头订单低于指定值)后模拟另一笔订单。它们本质上与限价单相反。当当前市场价格低于参数时stop
,策略将触发后续订单,而无需等待当前价格达到止损水平。如果订单下单命令包含参数limit
,则后续订单将是指定值的限价单。否则,它将是市价单。
下面的脚本在close
100 条柱线上方 800 个刻度处下达止损订单。在此示例中,当市场价格stop
在下单后几条柱线与价格交叉时,该策略进入多头仓位。请注意,下单时的初始价格优于传递给 的价格stop
。等效限价订单将在以下图表柱线中成交:
//@version=5
strategy("Stop order demo", overlay = true, margin_long = 100, margin_short = 100)
//@function Displays text passed to `txt` when called and shows the `price` level on the chart.
debugLabel(price, txt) =>
label.new(
bar_index, high, text = txt, color = color.teal, textcolor = color.white,
style = label.style_label_lower_right, size = size.large
)
line.new(bar_index, high, bar_index, price, style = line.style_dotted, color = color.teal)
line.new(
bar_index, price, bar_index + 1, price, color = color.teal, extend = extend.right,
style = line.style_dashed
)
// Generate a long stop order with a label and lines 100 bars before the last bar.
if last_bar_index - bar_index == 100
stopPrice = close + syminfo.mintick * 800
debugLabel(stopPrice, "Long Stop order created")
strategy.entry("Long", strategy.long, stop = stopPrice)
使用limit
和stop
参数的订单下达命令会生成止损限价订单。此订单类型等待价格越过止损水平,然后以指定limit
价格下达限价订单。
让我们修改之前的脚本来模拟和可视化止损限价订单。在此示例中,我们使用low
100 条柱之前的值作为
limit
入场命令中的价格。此脚本还显示标签和价格水平,以指示策略何时越过stopPrice
,即策略何时激活限价订单。请注意市场价格最初如何达到限价水平,但策略不会模拟交易,因为价格必须越过 才能在stopPrice
下达待执行限价订单limitPrice
:
//@version=5
strategy("Stop-Limit order demo", overlay = true, margin_long = 100, margin_short = 100)
//@function Displays text passed to `txt` when called and shows the `price` level on the chart.
debugLabel(price, txt, lblColor, lineWidth = 1) =>
label.new(
bar_index, high, text = txt, color = lblColor, textcolor = color.white,
style = label.style_label_lower_right, size = size.large
)
line.new(bar_index, close, bar_index, price, style = line.style_dotted, color = lblColor, width = lineWidth)
line.new(
bar_index, price, bar_index + 1, price, color = lblColor, extend = extend.right,
style = line.style_dashed, width = lineWidth
)
var float stopPrice = na
var float limitPrice = na
// Generate a long stop-limit order with a label and lines 100 bars before the last bar.
if last_bar_index - bar_index == 100
stopPrice := close + syminfo.mintick * 800
limitPrice := low
debugLabel(limitPrice, "", color.gray)
debugLabel(stopPrice, "Long Stop-Limit order created", color.teal)
strategy.entry("Long", strategy.long, stop = stopPrice, limit = limitPrice)
// Draw a line and label once the strategy activates the limit order.
if high >= stopPrice
debugLabel(limitPrice, "Limit order activated", color.green, 2)
stopPrice := na
下单指令
Pine Script™ 策略具有多种功能来模拟下单,称为订单下单命令。每个命令都有独特的用途,并且行为与其他命令不同。
`策略.entry()`
stop
此命令模拟入场订单。默认情况下,策略在调用此函数时会下达市价单,但使用和参数时,它们也可以创建止损单、限价单和止损限价单limit
。
为了简化开仓操作,
strategy.entry()
具有几种独特的行为。其中一种行为是,此命令可以反转未平仓市场仓位,而无需额外的函数调用。当使用
strategy.entry()
下达的订单成交时,该函数将自动计算策略平仓所需的金额,并qty
默认以相反方向交易合约/股份/手数/单位。例如,如果策略在strategy.long
方向有 15 股未平仓仓位
,并调用
strategy.entry()在strategy.short方向
下达市价订单
,则策略下达订单将交易的金额为 15 股加上qty
新的空头订单。
下面的示例演示了一种仅使用
strategy.entry()
调用来下达入场订单的策略。它qty
每100条线创建一个价值为15股的多头市场订单,每25条线创建一个价值为5股的空头市场订单
qty
。脚本分别用蓝色和红色突出显示背景中的buyCondition
和
sellCondition
:
//@version=5
strategy("Entry demo", "test", overlay = true)
//@variable Is `true` on every 100th bar.
buyCondition = bar_index % 100 == 0
//@variable Is `true` on every 25th bar except for those that are divisible by 100.
sellCondition = bar_index % 25 == 0 and not buyCondition
if buyCondition
strategy.entry("buy", strategy.long, qty = 15)
if sellCondition
strategy.entry("sell", strategy.short, qty = 5)
bgcolor(buyCondition ? color.new(color.blue, 90) : na)
bgcolor(sellCondition ? color.new(color.red, 90) : na)
如上图所示,订单标记显示该策略在每次订单执行时交易 20 股,而不是 15 股和 5 股。由于 strategy.entry()会自动反转仓位,除非通过strategy.risk.allow_entry_in()函数 另有规定,否则 它会在改变方向时将未平仓头寸规模(多头入场为 15 股)添加到新订单规模(空头入场为 5 股),从而导致交易数量为 20 股。
请注意,在上面的例子中,尽管sellCondition
在另一个 之前出现了三次buyCondition
,但策略只在第一次出现时下达“卖出”订单。strategy.entry
()
命令的另一个独特行为是它受脚本的金字塔设置的影响。金字塔指定策略可以在同一方向填写的连续订单数。其默认值为 1,这意味着策略只允许在任一方向上填写一个连续订单。用户可以通过strategy()pyramiding
函数调用
的参数
或脚本设置“属性”选项卡中的“金字塔”输入来设置策略金字塔值。
如果我们添加pyramiding = 3
到我们之前的脚本的声明语句中,该策略将允许在同一方向进行最多三笔连续的交易,这意味着它可以在每次出现以下情况时模拟新的市场订单sellCondition
:
`策略.order()`
此命令模拟基本订单。与大多数包含内部逻辑以简化与策略接口的订单下达命令不同, strategy.order() 使用指定的参数而不考虑大多数其他策略设置。通过 strategy.order()下达的订单 可以开立新仓位并修改或关闭现有仓位。
以下脚本仅使用 strategy.order() 调用来创建和修改条目。该策略模拟每100条15个单位的多头市场订单,然后每25条模拟三个5个单位的空头订单。脚本突出显示背景蓝色和红色,以指示策略何时模拟“买入”和“卖出”订单:
//@version=5
strategy("Order demo", "test", overlay = true)
//@variable Is `true` on every 100th bar.
buyCond = bar_index % 100 == 0
//@variable Is `true` on every 25th bar except for those that are divisible by 100.
sellCond = bar_index % 25 == 0 and not buyCond
if buyCond
strategy.order("buy", strategy.long, qty = 15) // Enter a long position of 15 units.
if sellCond
strategy.order("sell", strategy.short, qty = 5) // Exit 5 units from the long position.
bgcolor(buyCond ? color.new(color.blue, 90) : na)
bgcolor(sellCond ? color.new(color.red, 90) : na)
此特定策略永远不会模拟空头仓位,因为与 strategy.entry()不同, strategy.order() 不会自动反转仓位。使用此命令时,产生的市场仓位是当前市场仓位和已成交订单数量的净和。在策略完成15个单位的“买入”订单后,它会执行三个“卖出”订单,每个订单将未平仓头寸减少5个单位,即15-5*3=0。 如上一节中所示, 使用strategy.entry()时,同一脚本的行为会有所不同。
`策略.exit()`
loss
此命令模拟退出订单。它的独特之处在于它允许策略退出市场头寸或通过、stop
、
profit
、limit
和参数以止损、止盈和追踪止损订单的形式形成多个退出订单trail_*
。
Strategy.exit()命令的最基本用途 是创建策略因亏损过多(止损)、赚取足够的钱(获利)或两者兼而有之(括号)而退出头寸的级别。
此命令的止损和止盈功能与两个参数相关。函数的loss
和profit
参数将止损和止盈值指定为距入场订单价格的定义刻度数,而其stop
和
limit
参数提供具体的止损和止盈价格值。函数调用中的绝对参数优先于相对参数。如果
strategy.exit()
调用包含profit
和limit
参数,则命令将优先考虑该limit
值并忽略该profit
值。同样,它只会stop
在函数调用包含
stop
和loss
参数时考虑该值。
来自strategy.exit()的所有
带有参数的退出订单都
from_entry
与相应的入场订单绑定id
;当没有与
from_entry
ID关联的未平仓市场头寸或活动入场订单时,策略无法模拟退出订单。
以下策略每 100 条通过 strategy.entry( )下达“买入”进场订单,并通过strategy.exit()命令 下达止损和止盈订单 。请注意,脚本调用 strategy.exit() 两次。“exit1”命令引用“买入1”进场订单,“exit2”引用“买入”订单。该策略将仅模拟来自“exit2”的退出订单,因为“exit1”引用了不存在的订单ID:
//@version=5
strategy("Exit demo", "test", overlay = true)
//@variable Is `true` on every 100th bar.
buyCondition = bar_index % 100 == 0
//@variable Stop-loss price for exit commands.
var float stopLoss = na
//@variable Take-profit price for exit commands.
var float takeProfit = na
// Place orders upon `buyCondition`.
if buyCondition
if strategy.position_size == 0.0
stopLoss := close * 0.99
takeProfit := close * 1.01
strategy.entry("buy", strategy.long)
strategy.exit("exit1", "buy1", stop = stopLoss, limit = takeProfit) // Does nothing. "buy1" order doesn't exist.
strategy.exit("exit2", "buy", stop = stopLoss, limit = takeProfit)
// Set `stopLoss` and `takeProfit` to `na` when price touches either, i.e., when the strategy simulates an exit.
if low <= stopLoss or high >= takeProfit
stopLoss := na
takeProfit := na
plot(stopLoss, "SL", color.red, style = plot.style_circles)
plot(takeProfit, "TP", color.green, style = plot.style_circles)
注意:
- 每个退出命令的限价单和止损单不一定以指定价格成交。策略可以以更优惠的价格成交限价单,以更差的价格成交止损单,具体取决于经纪商模拟器可用的值范围。
如果用户在strategy.exit()from_entry
调用中没有提供参数
,该函数将为每个打开的条目创建退出订单。
在此示例中,该策略创建“buy1”和“buy2”入场订单,并
每 100 条柱状图调用
不带参数的strategy.exit()from_entry
。从图表上的订单标记可以看出,一旦市场价格达到stopLoss
或takeProfit
值,该策略就会为“buy1”和“buy2”入场订单填写退出订单:
//@version=5
strategy("Exit all demo", "test", overlay = true, pyramiding = 2)
//@variable Is `true` on every 100th bar.
buyCondition = bar_index % 100 == 0
//@variable Stop-loss price for exit commands.
var float stopLoss = na
//@variable Take-profit price for exit commands.
var float takeProfit = na
// Place orders upon `buyCondition`.
if buyCondition
if strategy.position_size == 0.0
stopLoss := close * 0.99
takeProfit := close * 1.01
strategy.entry("buy1", strategy.long)
strategy.entry("buy2", strategy.long)
strategy.exit("exit", stop = stopLoss, limit = takeProfit) // Places orders to exit all open entries.
// Set `stopLoss` and `takeProfit` to `na` when price touches either, i.e., when the strategy simulates an exit.
if low <= stopLoss or high >= takeProfit
stopLoss := na
takeProfit := na
plot(stopLoss, "SL", color.red, style = plot.style_circles)
plot(takeProfit, "TP", color.green, style = plot.style_circles)
策略可以多次从同一个入场ID退出,这样便于形成多层级退出策略。执行多个退出命令时,每个订单的数量必须是交易数量的一部分,总和不超过未平仓头寸。如果函数的小于qty
当前市场头寸的大小,策略将模拟部分退出。如果该值超过qty
未平仓头寸数量,它将减少订单,因为它无法填充比未平仓头寸更多的合约/股/手/单位。
在下面的示例中,我们修改了之前的“退出演示”脚本,以模拟每次进入时的两个止损和止盈订单。该策略下达一个qty
两股的“买入”订单、一个一股的“退出 1”止损和止盈订单以及一个三股的qty
“退出 2”止损和止盈订单:qty
//@version=5
strategy("Multiple exit demo", "test", overlay = true)
//@variable Is `true` on every 100th bar.
buyCondition = bar_index % 100 == 0
//@variable Stop-loss price for "exit1" commands.
var float stopLoss1 = na
//@variable Stop-loss price for "exit2" commands.
var float stopLoss2 = na
//@variable Take-profit price for "exit1" commands.
var float takeProfit1 = na
//@variable Take-profit price for "exit2" commands.
var float takeProfit2 = na
// Place orders upon `buyCondition`.
if buyCondition
if strategy.position_size == 0.0
stopLoss1 := close * 0.99
stopLoss2 := close * 0.98
takeProfit1 := close * 1.01
takeProfit2 := close * 1.02
strategy.entry("buy", strategy.long, qty = 2)
strategy.exit("exit1", "buy", stop = stopLoss1, limit = takeProfit1, qty = 1)
strategy.exit("exit2", "buy", stop = stopLoss2, limit = takeProfit2, qty = 3)
// Set `stopLoss1` and `takeProfit1` to `na` when price touches either.
if low <= stopLoss1 or high >= takeProfit1
stopLoss1 := na
takeProfit1 := na
// Set `stopLoss2` and `takeProfit2` to `na` when price touches either.
if low <= stopLoss2 or high >= takeProfit2
stopLoss2 := na
takeProfit2 := na
plot(stopLoss1, "SL1", color.red, style = plot.style_circles)
plot(stopLoss2, "SL2", color.red, style = plot.style_circles)
plot(takeProfit1, "TP1", color.green, style = plot.style_circles)
plot(takeProfit2, "TP2", color.green, style = plot.style_circles)
从图表上的订单标记可以看出,尽管指定的qty
值超过了交易量,但该策略仍执行了“exit2”订单。脚本没有使用此数量,而是减少了订单大小以匹配剩余的仓位。
注意:
- 从strategy.exit()调用生成的所有订单都 属于同一个 strategy.oca.reduce组,这意味着当任一订单完成时,该策略会减少所有其他订单以匹配未平仓头寸。
值得注意的是,此命令生成的订单会保留一部分未平仓市场仓位以供退出。strategy.exit () 无法下订单退出已由另一个退出命令保留的一部分仓位。
以下脚本模拟了 100 条柱状图之前 20 股的“买入”市价单,其中“限价”和“止损”订单分别为 19 股和 20 股。如我们在图表上看到的,该策略首先执行了“止损”订单。但是,交易量只有一股。由于脚本首先下达了“限价”订单,因此该策略保留其qty
(19 股)以平仓,只留下一股由“止损”订单平仓:
//@version=5
strategy("Reserved exit demo", "test", overlay = true)
//@variable "stop" exit order price.
var float stop = na
//@variable "limit" exit order price
var float limit = na
//@variable Is `true` 100 bars before the `last_bar_index`.
longCondition = last_bar_index - bar_index == 100
if longCondition
stop := close * 0.99
limit := close * 1.01
strategy.entry("buy", strategy.long, 20)
strategy.exit("limit", limit = limit, qty = 19)
strategy.exit("stop", stop = stop, qty = 20)
bool showPlot = strategy.position_size != 0
plot(showPlot ? stop : na, "Stop", color.red, 2, plot.style_linebr)
plot(showPlot ? limit : na, "Limit 1", color.green, 2, plot.style_linebr)
Strategy.exit()函数的另一个主要特性
是它可以创建追踪止损,即每当价格朝着有利方向移动到更佳值时,追踪市价指定数量的止损订单。这些订单包含两个部分:激活水平和追踪偏移量。激活水平是市价必须跨越的值才能激活追踪止损计算,通过参数以刻度表示,trail_points
或通过参数以价格值表示trail_price
。如果退出调用包含两个参数,则参数trail_price
优先。追踪偏移量是止损将跟随市价的距离,通过参数以刻度表示trail_offset
。为了使
Strategy.exit()
创建和激活追踪止损,函数调用必须包含
trail_offset
和trail_price
或trail_points
参数。
下面的示例展示了移动止损的实际操作,并直观地展示了其行为。该策略模拟在图表上最后一根柱线前 100 个柱线的柱线上设置多头进场订单,然后在下一个柱线上设置移动止损。该脚本有两个输入:一个控制激活水平偏移(即激活止损所需的超过进场价格的金额),另一个控制移动偏移(即当市场价格朝着期望方向移动到更佳值时,要跟随的距离)。
图表上的绿色虚线显示市场价格必须跨越的水平才能触发追踪止损订单。价格越过该水平后,脚本会绘制一条蓝线来表示追踪止损。当价格上涨至新高值时(这对策略有利,因为这意味着头寸的价值正在增加),止损也会上升以保持trailingStopOffset
与当前价格相差一个刻度的距离。当价格下跌或未达到新高点时,止损值保持不变。最终,价格跌破止损,触发退出:
//@version=5
strategy("Trailing stop order demo", overlay = true, margin_long = 100, margin_short = 100)
//@variable Offset used to determine how far above the entry price (in ticks) the activation level will be located.
activationLevelOffset = input(1000, "Activation Level Offset (in ticks)")
//@variable Offset used to determine how far below the high price (in ticks) the trailing stop will trail the chart.
trailingStopOffset = input(2000, "Trailing Stop Offset (in ticks)")
//@function Displays text passed to `txt` when called and shows the `price` level on the chart.
debugLabel(price, txt, lblColor, hasLine = false) =>
label.new(
bar_index, price, text = txt, color = lblColor, textcolor = color.white,
style = label.style_label_lower_right, size = size.large
)
if hasLine
line.new(
bar_index, price, bar_index + 1, price, color = lblColor, extend = extend.right,
style = line.style_dashed
)
//@variable The price at which the trailing stop activation level is located.
var float trailPriceActivationLevel = na
//@variable The price at which the trailing stop itself is located.
var float trailingStop = na
//@variable Caclulates the value that Trailing Stop would have if it were active at the moment.
theoreticalStopPrice = high - trailingStopOffset * syminfo.mintick
// Generate a long market order to enter 100 bars before the last bar.
if last_bar_index - bar_index == 100
strategy.entry("Long", strategy.long)
// Generate a trailing stop 99 bars before the last bar.
if last_bar_index - bar_index == 99
trailPriceActivationLevel := open + syminfo.mintick * activationLevelOffset
strategy.exit(
"Trailing Stop", from_entry = "Long", trail_price = trailPriceActivationLevel,
trail_offset = trailingStopOffset
)
debugLabel(trailPriceActivationLevel, "Trailing Stop Activation Level", color.green, true)
// Visualize the trailing stop mechanic in action.
// If there is an open trade, check whether the Activation Level has been achieved.
// If it has been achieved, track the trailing stop by assigning its value to a variable.
if strategy.opentrades == 1
if na(trailingStop) and high > trailPriceActivationLevel
debugLabel(trailPriceActivationLevel, "Activation level crossed", color.green)
trailingStop := theoreticalStopPrice
debugLabel(trailingStop, "Trailing Stop Activated", color.blue)
else if theoreticalStopPrice > trailingStop
trailingStop := theoreticalStopPrice
// Visualize the movement of the trailing stop.
plot(trailingStop, "Trailing Stop")
`strategy.close()` 和`strategy.close_all()`
这些命令使用市场订单模拟退出头寸。这些函数在被调用时关闭交易,而不是在特定价格关闭交易。
下面的例子演示了一个简单的策略,即每 50 个条形图通过 strategy.entry()下达一次“买入”订单,然后在 25 个条形图之后使用strategy.close() 以市价订单关闭 :
//@version=5
strategy("Close demo", "test", overlay = true)
//@variable Is `true` on every 50th bar.
buyCond = bar_index % 50 == 0
//@variable Is `true` on every 25th bar except for those that are divisible by 50.
sellCond = bar_index % 25 == 0 and not buyCond
if buyCond
strategy.entry("buy", strategy.long)
if sellCond
strategy.close("buy")
bgcolor(buyCond ? color.new(color.blue, 90) : na)
bgcolor(sellCond ? color.new(color.red, 90) : na)
与大多数其他下单命令不同,strategy.close()id
的参数
引用要关闭的现有条目 ID。如果指定的条目 ID不存在,则该命令不会执行订单。如果一个仓位由多个具有相同 ID 的条目组成,则该命令将同时退出所有条目。id
为了演示,以下脚本每 25 条柱线下达一次“买入”订单。该脚本每 100 条柱线关闭一次所有“买入”条目。我们pyramiding = 3
在
strategy()
声明语句中包含了允许策略在同一方向模拟最多三个订单的内容:
//@version=5
strategy("Multiple close demo", "test", overlay = true, pyramiding = 3)
//@variable Is `true` on every 100th bar.
sellCond = bar_index % 100 == 0
//@variable Is `true` on every 25th bar except for those that are divisible by 100.
buyCond = bar_index % 25 == 0 and not sellCond
if buyCond
strategy.entry("buy", strategy.long)
if sellCond
strategy.close("buy")
bgcolor(buyCond ? color.new(color.blue, 90) : na)
bgcolor(sellCond ? color.new(color.red, 90) : na)
对于脚本具有多个具有不同 ID 的条目的情况, strategy.close_all() 命令会派上用场,因为它会关闭所有条目,而不管它们的 ID 是什么。
下面的脚本根据未平仓交易的数量依次下达“A”、“B”和“C”个入场订单,然后通过单个市场订单关闭所有订单:
//@version=5
strategy("Close multiple ID demo", "test", overlay = true, pyramiding = 3)
switch strategy.opentrades
0 => strategy.entry("A", strategy.long)
1 => strategy.entry("B", strategy.long)
2 => strategy.entry("C", strategy.long)
3 => strategy.close_all()
`strategy.cancel()` 和`strategy.cancel_all()`
这些命令允许策略取消挂单,即
当使用或参数时由strategy.exit()
或
strategy.order()
或
strategy.entry()生成的挂单。limit
stop
以下策略模拟了在 100 条柱线之前低于收盘价 500 个点的“买入”限价订单,然后在下一柱线取消订单。脚本在 处绘制一条水平线,并将limitPrice
背景颜色设为绿色和橙色,分别表示限价订单的下达和取消时间。我们可以看到,一旦市场价格超过 ,什么也没有发生,limitPrice
因为策略已经取消了订单:
//@version=5
strategy("Cancel demo", "test", overlay = true)
//@variable Draws a horizontal line at the `limit` price of the "buy" order.
var line limitLine = na
//@variable Returns `color.green` when the strategy places the "buy" order, `color.orange` when it cancels the order.
color bgColor = na
if last_bar_index - bar_index == 100
float limitPrice = close - syminfo.mintick * 500
strategy.entry("buy", strategy.long, limit = limitPrice)
limitLine := line.new(bar_index, limitPrice, bar_index + 1, limitPrice, extend = extend.right)
bgColor := color.new(color.green, 50)
if last_bar_index - bar_index == 99
strategy.cancel("buy")
bgColor := color.new(color.orange, 50)
bgcolor(bgColor)
与
strategy.close()一样,strategy.cancel()id
的参数
引用现有条目的ID。如果参数引用不存在的ID,此命令将不执行任何操作。当有多个具有相同ID的挂单时,此命令将一次性取消所有挂单。id
在此示例中,我们修改了之前的脚本,从 100 条线开始,在三个连续的线下“买入”限价订单。该策略在bar_index
距离最近的线 97 条线后取消所有订单,导致当价格跨越任何一条线时,它什么也不做:
//@version=5
strategy("Multiple cancel demo", "test", overlay = true, pyramiding = 3)
//@variable Draws a horizontal line at the `limit` price of the "buy" order.
var line limitLine = na
//@variable Returns `color.green` when the strategy places the "buy" order, `color.orange` when it cancels the order.
color bgColor = na
if last_bar_index - bar_index <= 100 and last_bar_index - bar_index >= 98
float limitPrice = close - syminfo.mintick * 500
strategy.entry("buy", strategy.long, limit = limitPrice)
limitLine := line.new(bar_index, limitPrice, bar_index + 1, limitPrice, extend = extend.right)
bgColor := color.new(color.green, 50)
if last_bar_index - bar_index == 97
strategy.cancel("buy")
bgColor := color.new(color.orange, 50)
bgcolor(bgColor)
注意:
- 我们
pyramiding = 3
在脚本的声明语句中添加了允许三个 strategy.entry() 订单执行的语句。或者,脚本可以使用 strategy.order()实现相同的输出 ,因为它对pyramiding
设置不敏感。
需要注意的是, strategy.cancel() 和 strategy.cancel_all()都 无法取消市场订单,因为策略会在下一个报价时立即执行这些订单。策略无法在订单成交后取消订单。要平仓,请使用 strategy.close() 或 strategy.close_all()。
此示例模拟了 100 条柱之前的“买入”市场订单,然后尝试在下一条柱上取消所有挂单。由于该策略已经填写了“买入”订单,因此 在此情况下, strategy.cancel_all() 命令不会执行任何操作,因为没有要取消的挂单:
//@version=5
strategy("Cancel market demo", "test", overlay = true)
//@variable Returns `color.green` when the strategy places the "buy" order, `color.orange` when it tries to cancel.
color bgColor = na
if last_bar_index - bar_index == 100
strategy.entry("buy", strategy.long)
bgColor := color.new(color.green, 50)
if last_bar_index - bar_index == 99
strategy.cancel_all()
bgColor := color.new(color.orange, 50)
bgcolor(bgColor)
头寸规模
Pine Script™ 策略有两种方式来控制模拟交易的规模:
- 使用strategy()函数中的
default_qty_type
和参数为所有订单设置默认的固定数量类型和值 ,该函数还会在脚本设置的“属性”选项卡中设置默认值。default_qty_value
- 调用strategy.entry()
qty
时 指定参数。当用户向函数提供此参数时,脚本将忽略策略的默认数量值和类型。
以下示例模拟longAmount
当low
价格等于价值时“买入”订单,以及当价格等于价值时lowest
“卖出”订单:shortAmount
high
highest
//@version=5
strategy("Buy low, sell high", overlay = true, default_qty_type = strategy.cash, default_qty_value = 5000)
int length = input.int(20, "Length")
float longAmount = input.float(4.0, "Long Amount")
float shortAmount = input.float(2.0, "Short Amount")
float highest = ta.highest(length)
float lowest = ta.lowest(length)
switch
low == lowest => strategy.entry("Buy", strategy.long, longAmount)
high == highest => strategy.entry("Sell", strategy.short, shortAmount)
请注意,在上面的例子中,尽管我们在声明语句中指定了
default_qty_type
和default_qty_value
参数,但脚本不会将这些默认值用于模拟订单。相反,它将它们的大小设置为longAmount
和shortAmount
单位。如果我们希望脚本使用默认类型和值,我们必须qty
从
strategy.entry()
调用中删除规范:
//@version=5
strategy("Buy low, sell high", overlay = true, default_qty_type = strategy.cash, default_qty_value = 5000)
int length = input.int(20, "Length")
float highest = ta.highest(length)
float lowest = ta.lowest(length)
switch
low == lowest => strategy.entry("Buy", strategy.long)
high == highest => strategy.entry("Sell", strategy.short)
平仓
尽管可以模拟从
策略测试器模块的
交易列表选项卡中显示的特定进场订单退出,但所有订单都按照 FIFO(先进先出)规则链接。如果用户未指定策略.exit()调用
的参数
,则策略将从打开它的第一个进场订单开始退出未平仓市场头寸。from_entry
以下示例按顺序模拟两个订单:按最后 100 个柱的市场价格执行“Buy1”,一旦头寸规模与“Buy1”的规模匹配,则执行“Buy2”。该策略仅在 为positionSize
15 个单位时下达退出订单。该脚本不
from_entry
向
strategies.exit()
命令提供参数,因此该策略每次调用该函数时都会为所有未平仓头寸下达退出订单,从第一个开始。它
positionSize
在单独的窗格中绘制 以供视觉参考:
//@version=5
strategy("Exit Demo", pyramiding = 2)
float positionSize = strategy.position_size
if positionSize == 0 and last_bar_index - bar_index <= 100
strategy.entry("Buy1", strategy.long, 5)
else if positionSize == 5
strategy.entry("Buy2", strategy.long, 10)
else if positionSize == 15
strategy.exit("bracket", loss = 10, profit = 10)
plot(positionSize == 0 ? na : positionSize, "Position Size", color.lime, 4, plot.style_histogram)
注意:
- 我们
pyramiding = 2
在脚本的声明语句中加入了允许它模拟同一方向的两个连续订单。
假设我们想在“Buy1”之前退出“Buy2”。让我们看看,如果我们指示策略在执行两个订单时先关闭“Buy2”,然后再关闭“Buy1”,会发生什么情况:
//@version=5
strategy("Exit Demo", pyramiding = 2)
float positionSize = strategy.position_size
if positionSize == 0 and last_bar_index - bar_index <= 100
strategy.entry("Buy1", strategy.long, 5)
else if positionSize == 5
strategy.entry("Buy2", strategy.long, 10)
else if positionSize == 15
strategy.close("Buy2")
strategy.exit("bracket", "Buy1", loss = 10, profit = 10)
plot(positionSize == 0 ? na : positionSize, "Position Size", color.lime, 4, plot.style_histogram)
正如我们在策略测试器的“交易列表”选项卡中看到的那样,它不是使用
strategy.close()关闭“Buy2”头寸,而是先关闭“Buy1”的数量,即关闭订单数量的一半,然后关闭“Buy2”头寸的一半,因为经纪商模拟器默认遵循FIFO规则。用户可以通过close_entries_rule = "ANY"
在
strategy()
函数中指定来更改此行为。
OCA团体
一取消全 (OCA) 组允许策略在执行订单放置命令时完全或部分取消其他订单,包括
strategy.entry()
和
strategy.order(),具体oca_name
取决于oca_type
用户在函数调用中提供的。
`策略.oca.取消`
strategies.oca.cancel OCA类型
oca_name
在组中的订单填写或部分填写后
取消所有相同的订单。
例如,以下策略在ma1
交叉
时执行订单ma2
。当
strategy.position_size
为0时,它会在柱线的
high
和上放置多头和空头止损订单。否则,它会调用strategy.close_all()
以市价单关闭所有未平仓头寸。根据价格行为,策略可能会在发出平仓订单之前完成两个订单。此外,如果经纪商模拟器的intrabar假设支持它,则两个订单可能会在同一根柱线上完成。
在这种情况下, strategy.close_all()
命令不执行任何操作,因为脚本在执行完两个订单之前无法调用该操作:low
//@version=5
strategy("OCA Cancel Demo", overlay=true)
float ma1 = ta.sma(close, 5)
float ma2 = ta.sma(close, 9)
if ta.cross(ma1, ma2)
if strategy.position_size == 0
strategy.order("Long", strategy.long, stop = high)
strategy.order("Short", strategy.short, stop = low)
else
strategy.close_all()
plot(ma1, "Fast MA", color.aqua)
plot(ma2, "Slow MA", color.orange)
为了消除策略在平仓订单之前执行多头和空头订单的情况,我们可以指示它在执行一个订单后取消另一个订单。在此示例中,我们将oca_name
两个
strategy.order()
命令的设置为“Entry”,并将其设置oca_type
为strategy.oca.cancel
:
//@version=5
strategy("OCA Cancel Demo", overlay=true)
float ma1 = ta.sma(close, 5)
float ma2 = ta.sma(close, 9)
if ta.cross(ma1, ma2)
if strategy.position_size == 0
strategy.order("Long", strategy.long, stop = high, oca_name = "Entry", oca_type = strategy.oca.cancel)
strategy.order("Short", strategy.short, stop = low, oca_name = "Entry", oca_type = strategy.oca.cancel)
else
strategy.close_all()
plot(ma1, "Fast MA", color.aqua)
plot(ma2, "Slow MA", color.orange)
`策略.oca.reduce`
Strategy.oca.reduce
OCA 类型不会取消订单。相反,它会在每次新订单成交时按已平仓合约/股份/手数/单位数
减少订单规模oca_name
,这对于退出策略特别有用。
以下示例演示了尝试只做多头的退出策略,该策略为每个新入场点生成止损订单和两个止盈订单。在两个移动平均线交叉时,它使用
strategy.entry()模拟一个“多头”入场订单,
其中a为6个单位,然后使用strategy.order()分别
在、和和价格qty
模拟6、3和3个单位的止损/限价订单
。stop
limit1
limit2
将策略添加到图表后,我们发现它无法按预期工作。此脚本的问题是, 与strategy.exit()不同, strategy.order() 默认情况下不属于OCA组 。由于我们没有明确将订单分配给OCA组,因此该策略在填写订单时不会取消或减少订单,这意味着可以交易比未平仓头寸更多的数量并反转方向:
//@version=5
strategy("Multiple TP Demo", overlay = true)
var float stop = na
var float limit1 = na
var float limit2 = na
bool longCondition = ta.crossover(ta.sma(close, 5), ta.sma(close, 9))
if longCondition and strategy.position_size == 0
stop := close * 0.99
limit1 := close * 1.01
limit2 := close * 1.02
strategy.entry("Long", strategy.long, 6)
strategy.order("Stop", strategy.short, stop = stop, qty = 6)
strategy.order("Limit 1", strategy.short, limit = limit1, qty = 3)
strategy.order("Limit 2", strategy.short, limit = limit2, qty = 3)
bool showPlot = strategy.position_size != 0
plot(showPlot ? stop : na, "Stop", color.red, style = plot.style_linebr)
plot(showPlot ? limit1 : na, "Limit 1", color.green, style = plot.style_linebr)
plot(showPlot ? limit2 : na, "Limit 2", color.green, style = plot.style_linebr)
为了使我们的策略按预期发挥作用,我们必须指示它减少其他止损/止盈订单的单位数量,以使它们不超过剩余未平仓头寸的规模。
在下面的例子中,我们将oca_name
退出策略中每笔订单的设置为“Bracket”,并将设置oca_type
为
strategy.oca.reduce。这些设置告诉策略在执行其中一个订单时,qty
将“Bracket”组中订单的价值减少qty
已成交的订单,以防止交易过多的单位并导致逆转:
//@version=5
strategy("Multiple TP Demo", overlay = true)
var float stop = na
var float limit1 = na
var float limit2 = na
bool longCondition = ta.crossover(ta.sma(close, 5), ta.sma(close, 9))
if longCondition and strategy.position_size == 0
stop := close * 0.99
limit1 := close * 1.01
limit2 := close * 1.02
strategy.entry("Long", strategy.long, 6)
strategy.order("Stop", strategy.short, stop = stop, qty = 6, oca_name = "Bracket", oca_type = strategy.oca.reduce)
strategy.order("Limit 1", strategy.short, limit = limit1, qty = 3, oca_name = "Bracket", oca_type = strategy.oca.reduce)
strategy.order("Limit 2", strategy.short, limit = limit2, qty = 6, oca_name = "Bracket", oca_type = strategy.oca.reduce)
bool showPlot = strategy.position_size != 0
plot(showPlot ? stop : na, "Stop", color.red, style = plot.style_linebr)
plot(showPlot ? limit1 : na, "Limit 1", color.green, style = plot.style_linebr)
plot(showPlot ? limit2 : na, "Limit 2", color.green, style = plot.style_linebr)
注意:
- 我们将
qty
“限价 2”订单的 改为 6,而不是 3,因为该策略在执行“限价 1”订单时会将其值降低 3。保留qty
3 的值会导致其在执行第一个限价订单后降至 0 并且永远不会执行。
`策略.oca.none`
Strategy.oca.none OCA类型
指定订单独立于任何 OCA 组执行。此值是Strategy.order()
和
Strategy.entry()oca_type
订单下单命令的
默认值
。
货币
Pine Script™ 策略可以使用与其计算工具不同的基础货币。用户可以通过在
strategy()currency.*
函数中包含一个变量作为
参数来指定模拟账户的基础货币
,这将改变脚本的
strategy.account_currency
值。strategy的默认值为,这意味着脚本使用图表上工具的基础货币。currency
currency
currency.NONE
当策略脚本使用指定的基础货币时,它会将模拟利润乘以前一交易日的 FX_IDC 汇率。例如,以下策略在最后 500 个图表条上设置一个标准手(100,000 单位)的入场订单,并以 1 点作为盈利目标和止损,然后在单独的窗格中绘制净利润以及符号的倒置每日收盘价。我们已将基础货币设置为currency.EUR
。当我们将此脚本添加到 FX_IDC:EURUSD 时,两个图表对齐,确认该策略使用此符号的前一天汇率进行计算:
//@version=5
strategy("Currency Test", currency = currency.EUR)
if last_bar_index - bar_index < 500
strategy.entry("LE", strategy.long, 100000)
strategy.exit("LX", "LE", profit = 1, loss = 1)
plot(math.abs(ta.change(strategy.netprofit)), "1 Point profit", color = color.fuchsia, linewidth = 4)
plot(request.security(syminfo.tickerid, "D", 1 / close)[1], "Previous day's inverted price", color = color.lime)
注意:
- 当交易时间范围高于每日时,该策略将使用收盘前一个交易日的收盘价来计算历史柱的交叉汇率。例如,在每周时间范围内,它将根据前一个星期四的收盘价计算交叉汇率,但该策略仍将使用实时柱的每日收盘价。
改变计算行为
策略将针对图表上的所有历史条形图执行,然后在有新数据可用时自动继续实时计算。默认情况下,策略脚本仅对每个已确认的条形图计算一次。我们可以通过更改strategy () 函数的参数或单击脚本“属性”选项卡中“重新计算”部分的复选框来更改此行为。
`每次计时计算`
calc_on_every_tick
是控制实时数据计算行为的可选设置。启用此参数后,脚本将在每个新的价格变动时重新计算其值。默认情况下,其值为 false,这意味着脚本仅在确认条形图后执行计算。
启用此计算行为在前向测试时可能特别有用,因为它有助于实现精细的实时策略模拟。但是,需要注意的是,此行为会导致实时模拟和历史模拟之间的数据差异,因为历史条形图不包含报价信息。用户应谨慎使用此设置,因为数据差异可能会导致策略重新绘制其历史记录。
以下脚本将在每次close
达到输入的highest
或值时模拟新订单。由于
在策略声明中启用了 ,因此脚本将在编译后在每个新的实时价格变动时模拟新订单:lowest
length
calc_on_every_tick
//@version=5
strategy("Donchian Channel Break", overlay = true, calc_on_every_tick = true, pyramiding = 20)
int length = input.int(15, "Length")
float highest = ta.highest(close, length)
float lowest = ta.lowest(close, length)
if close == highest
strategy.entry("Buy", strategy.long)
if close == lowest
strategy.entry("Sell", strategy.short)
//@variable The starting time for real-time bars.
var realTimeStart = timenow
// Color the background of real-time bars.
bgcolor(time_close >= realTimeStart ? color.new(color.orange, 80) : na)
plot(highest, "Highest", color = color.lime)
plot(lowest, "Lowest", color = color.red)
注意:
- 该脚本
pyramiding
在声明中使用了值 20,这允许该策略最多模拟同一方向的 20 笔交易。 - 为了直观地区分哪些条形图被策略处理为实时条形图,脚本会为自上次编译以来的所有条形图的背景添加 颜色 。
将脚本应用到图表并让其根据一些实时条形图进行计算后,我们可能会看到如下输出:
脚本在条件有效的每个新的实时报价上下达“买入”订单,导致每条柱状图有多个订单。但是,不熟悉此行为的用户可能会惊讶地发现,在重新编译脚本后,策略的输出发生了变化,因为之前执行实时计算的柱状图现在是历史柱状图,不包含报价信息:
`calc_on_order_fills`
可选calc_on_order_fills
设置在模拟订单填写后立即重新计算策略,这允许脚本使用更精细的价格并下达额外订单,而无需等待确认条形图。
启用此设置可以为脚本提供额外的数据,否则这些数据直到条形图关闭后才可用,例如未确认条形图上模拟位置的当前平均价格。
下面的示例显示了一个启用了的简单策略,
calc_on_order_fills
当
strategy.position_size
为0时,该策略模拟“买入”订单。脚本使用
strategy.position_avg_price
来计算stopLoss
和,takeProfit
并在价格与它们交叉时模拟“退出”订单,无论条形图是否得到确认。因此,一旦触发退出,策略就会重新计算并下达新的入场订单,因为strategy.position_size
再次
等于0。策略在退出发生后下达订单,并在退出后的下一个报价时执行订单,这将是条形图的OHLC值之一,取决于模拟的条形图内运动:
//@version=5
strategy("Intrabar exit", overlay = true, calc_on_order_fills = true)
float stopSize = input.float(5.0, "SL %", minval = 0.0) / 100.0
float profitSize = input.float(5.0, "TP %", minval = 0.0) / 100.0
if strategy.position_size == 0.0
strategy.entry("Buy", strategy.long)
float stopLoss = strategy.position_avg_price * (1.0 - stopSize)
float takeProfit = strategy.position_avg_price * (1.0 + profitSize)
strategy.exit("Exit", stop = stopLoss, limit = takeProfit)
注意:
- 关闭后
calc_on_order_fills
,相同的策略在触发退出订单后只会进入一个条形图。首先,中间条形图退出,但没有进入订单。然后,策略将在条形图关闭后模拟进入订单,并在下一个价格变动(即下一个条形图开盘时)执行。
需要注意的是,启用该选项calc_on_order_fills
可能会产生不切实际的策略结果,因为经纪商模拟器可能会假设在实时交易时不可能的订单价格。用户必须谨慎使用此设置,并仔细考虑其脚本中的逻辑。
以下示例模拟了脚本加载到图表上时,在 last_bar_index的 25 条柱状窗口内,每次新订单填写和柱状图确认后的“买入”订单 。启用此设置后,策略会模拟每条柱状图四次入场,因为模拟器认为每条柱状图有四个刻度(开盘价、最高价、最低价、收盘价),这是不切实际的行为,因为订单通常不可能在柱状图的确切最高价或最低价填写:
//@version=5
strategy("buy on every fill", overlay = true, calc_on_order_fills = true, pyramiding = 100)
if last_bar_index - bar_index <= 25
strategy.entry("Buy", strategy.long)
`关闭时处理订单`
默认策略行为在每根柱线收盘时模拟订单,这意味着最早执行订单并执行策略计算和警报的机会是在下一根柱线开盘时。交易者可以通过启用设置来更改此行为,以使用每根柱线的收盘价来处理策略process_orders_on_close
。
这种行为在回测手动策略时最为有用,在这种策略中,交易者在收盘前退出头寸,或者在非 24x7 市场中的算法交易者设置盘后交易能力的情况下,收盘后发出的警报仍然有希望在第二天之前完成。
注意:
- 至关重要的是要意识到,
process_orders_on_close
在实时交易环境中使用策略可能会导致重新绘制策略,因为当市场收盘时仍然会发出收盘警报,并且订单可能要到下一个市场开盘才能完成。
模拟交易成本
为了使策略绩效报告包含相关且有意义的数据,交易者应努力在策略结果中考虑潜在的实际成本。忽略这一点可能会让交易者对策略绩效产生不切实际的看法,并破坏测试结果的可信度。如果不对与交易相关的潜在成本进行建模,交易者可能会高估策略的历史盈利能力,从而可能导致实时交易中做出次优决策。Pine Script™ 策略包括用于在绩效结果中模拟交易成本的输入和参数。
委员会
佣金是指经纪商/交易所在执行交易时收取的费用。根据经纪商/交易所的不同,有些经纪商/交易所可能按每笔交易或合约/股票/手/单位收取固定费用,而有些经纪商/交易所可能按交易总额的一定百分比收取费用。用户可以通过在strategy()函数
中
包含commission_type
和参数
或在策略设置的“属性”选项卡中设置“佣金”输入来设置其策略的佣金属性。commission_value
close
以下脚本是一个简单的策略,当等于该highest
值除以该值时,模拟 2% 股权的“多头”仓位length
,并在等于该值时关闭交易lowest
:
//@version=5
strategy("Commission Demo", overlay=true, default_qty_value = 2, default_qty_type = strategy.percent_of_equity)
length = input.int(10, "Length")
float highest = ta.highest(close, length)
float lowest = ta.lowest(close, length)
switch close
highest => strategy.entry("Long", strategy.long)
lowest => strategy.close("Long")
plot(highest, color = color.new(color.lime, 50))
plot(lowest, color = color.new(color.red, 50))
检查策略测试器中的结果后,我们发现该策略在测试范围内实现了 17.61% 的正资产增长率。但是,回测结果并未考虑经纪商/交易所可能收取的费用。让我们看看当我们在策略模拟中对每笔交易都收取少量佣金时,这些结果会发生什么变化。在此示例中,我们
在strategy()声明中
包含了commission_type = strategy.commission.percent
和
,这意味着它将模拟对所有已执行订单收取 1% 的佣金:commission_value = 1
//@version=5
strategy(
"Commission Demo", overlay=true, default_qty_value = 2, default_qty_type = strategy.percent_of_equity,
commission_type = strategy.commission.percent, commission_value = 1
)
length = input.int(10, "Length")
float highest = ta.highest(close, length)
float lowest = ta.lowest(close, length)
switch close
highest => strategy.entry("Long", strategy.long)
lowest => strategy.close("Long")
plot(highest, color = color.new(color.lime, 50))
plot(lowest, color = color.new(color.red, 50))
从上面的例子中我们可以看出,在回测中应用 1% 的佣金后,该策略模拟的净利润显著减少(仅为 1.42%),且权益曲线波动性更大,最大回撤也更大,突显了佣金模拟对策略测试结果的影响。
滑点和未完成限额
在实际交易中,由于波动性、流动性、订单规模和其他市场因素,经纪商/交易所执行订单的价格可能与交易者预期的价格略有不同,这会对策略的表现产生重大影响。经纪商/交易所执行交易的预期价格与实际价格之间的差异就是我们所说的滑点。滑点是动态的、不可预测的,因此无法精确模拟。但是,在回测或正向测试期间将少量滑点计入每笔交易中可能有助于使结果更符合实际情况。用户可以在策略结果中对滑点进行建模,大小为固定的刻度数,方法是
slippage
在
strategy()
声明中包含一个参数,或在策略设置的“属性”选项卡中设置“滑点”输入。
以下示例演示了滑点模拟如何影响策略测试中市价单的成交价格。以下脚本在市场价格高于 EMA(EMA 上涨时)时下达 2% 权益的“买入”市价单,并在价格跌破 EMA(EMA 下跌时)时平仓。我们已将策略 ()slippage = 20
函数包含在内
,该函数声明每个模拟订单的价格将按交易方向滑落 20 个刻度。该脚本使用
策略 (strategy.opentrades.entry_bar_index()
和
策略 (strategy.closedtrades.exit_bar_index())
来获取和,并利用它们来获取
订单的。当条形索引位于时,
是
第一个
策略 (strategy.opentrades.entry_price())
值。在是来自上次平仓交易的策略 (strategy.closedtrades.exit_price())
值。该脚本绘制了预期成交价格以及滑点后的模拟成交价格,以直观地比较差异:entryIndex
exitIndex
fillPrice
entryIndex
fillPrice
exitIndex
fillPrice
//@version=5
strategy(
"Slippage Demo", overlay = true, slippage = 20,
default_qty_value = 2, default_qty_type = strategy.percent_of_equity
)
int length = input.int(5, "Length")
//@variable Exponential moving average with an input `length`.
float ma = ta.ema(close, length)
//@variable Returns `true` when `ma` has increased and `close` is greater than it, `false` otherwise.
bool longCondition = close > ma and ma > ma[1]
//@variable Returns `true` when `ma` has decreased and `close` is less than it, `false` otherwise.
bool shortCondition = close < ma and ma < ma[1]
// Enter a long market position on `longCondition`, close the position on `shortCondition`.
if longCondition
strategy.entry("Buy", strategy.long)
if shortCondition
strategy.close("Buy")
//@variable The `bar_index` of the position's entry order fill.
int entryIndex = strategy.opentrades.entry_bar_index(0)
//@variable The `bar_index` of the position's close order fill.
int exitIndex = strategy.closedtrades.exit_bar_index(strategy.closedtrades - 1)
//@variable The fill price simulated by the strategy.
float fillPrice = switch bar_index
entryIndex => strategy.opentrades.entry_price(0)
exitIndex => strategy.closedtrades.exit_price(strategy.closedtrades - 1)
//@variable The expected fill price of the open market position.
float expectedPrice = fillPrice ? open : na
color expectedColor = na
color filledColor = na
if bar_index == entryIndex
expectedColor := color.green
filledColor := color.blue
else if bar_index == exitIndex
expectedColor := color.red
filledColor := color.fuchsia
plot(ma, color = color.new(color.orange, 50))
plotchar(fillPrice ? open : na, "Expected fill price", "—", location.absolute, expectedColor)
plotchar(fillPrice, "Fill price after slippage", "—", location.absolute, filledColor)
注意:
- 由于该策略对所有订单执行应用恒定滑点,因此有些订单可能会在模拟中超出蜡烛范围执行。因此用户应谨慎使用此设置,因为过多的模拟滑点可能会产生不切实际的更差测试结果。
一些交易者可能认为,他们可以通过使用限价单来避免滑点的不利影响,因为与市价单不同,限价单不能以低于指定值的价格执行。然而,根据现实市场的情况,即使市场价格达到订单价格,限价单也可能无法成交,因为只有当证券具有足够的流动性和围绕该值的价格行为时,限价单才能成交。为了在回测中考虑未成交订单的可能性,用户可以
backtest_fill_limits_assumption
在声明语句中指定值,或使用“属性”选项卡中的“验证限价单价格”输入来指示策略仅在价格超过订单价格一定数量的刻度后才成交限价单。
hlcc4
以下示例在为过去柱线的值high
且
没有待处理条目时,在柱线 下达 2% 权益的限价订单
。当为值时,该策略将平仓并取消所有订单。每次触发订单时,策略都会在 处绘制一条水平线,并在每个柱线上更新该水平线,直到平仓或取消订单:highest
length
low
lowest
limitPrice
//@version=5
strategy(
"Verify price for limits example", overlay = true,
default_qty_type = strategy.percent_of_equity, default_qty_value = 2
)
int length = input.int(25, title = "Length")
//@variable Draws a line at the limit price of the most recent entry order.
var line limitLine = na
// Highest high and lowest low
highest = ta.highest(length)
lowest = ta.lowest(length)
// Place an entry order and draw a new line when the the `high` equals the `highest` value and `limitLine` is `na`.
if high == highest and na(limitLine)
float limitPrice = hlcc4
strategy.entry("Long", strategy.long, limit = limitPrice)
limitLine := line.new(bar_index, limitPrice, bar_index + 1, limitPrice)
// Close the open market position, cancel orders, and set `limitLine` to `na` when the `low` equals the `lowest` value.
if low == lowest
strategy.cancel_all()
limitLine := na
strategy.close_all()
// Update the `x2` value of `limitLine` if it isn't `na`.
if not na(limitLine)
limitLine.set_x2(bar_index + 1)
plot(highest, "Highest High", color = color.new(color.green, 50))
plot(lowest, "Lowest Low", color = color.new(color.red, 50))
默认情况下,脚本假定所有限价订单都保证成交。然而,在实际交易中情况往往并非如此。让我们在限价订单中添加价格验证,以解决可能未成交的订单。在此示例中,我们已将其包含
backtest_fill_limits_assumption = 3
在
strategy()
函数调用中。我们可以看到,使用限价验证会省略一些模拟订单成交并更改其他订单的时间,因为现在只有在价格突破限价三个刻度后,入场订单才能成交:
风险管理
设计一个表现良好的策略是一项艰巨的任务,更不用说一个在广泛的市场中表现良好的策略了。大多数策略都是针对特定的市场模式/条件而设计的,当应用于其他数据时可能会产生无法控制的损失。因此,策略的风险管理质量对其表现至关重要。用户可以使用带有前缀的特殊命令在其策略脚本中设置风险管理标准strategy.risk
。
策略可以以任意组合的方式纳入任意数量的风险管理标准。所有风险管理命令都会在每个报价和订单执行事件中执行,无论策略的计算行为如何变化。在脚本运行时无法禁用任何这些命令。无论风险规则位于何处,它都会始终适用于策略,除非用户从代码中删除调用。
此命令覆盖了strategy.entry()命令允许的市场方向 。当用户使用此函数指定交易方向(例如 strategy.direction.long)时,策略将仅按该方向进行交易。但是,需要注意的是,如果脚本在有未平仓市场仓位的情况下调用相反方向的进入命令,则策略将模拟市场订单以退出该仓位。
该命令会在策略模拟了规定数量的连续亏损的交易日后,取消所有挂单、平仓并停止所有额外的交易行为。
当策略的亏损达到函数调用中指定的金额后,此命令将取消所有挂单、平仓市场仓位并停止所有其他交易行为。
此命令指定每个交易日(或如果时间范围高于每日,则每个图表条)的最大成交订单数。一旦策略执行了当天的最大订单数,它将取消所有待处理订单,关闭未平仓头寸,并暂停交易活动,直到当前交易时段结束。
此命令控制策略每个交易日(或如果时间范围高于每日,则每个图表条)可承受的最大损失。当策略的损失达到此阈值时,它将取消所有挂单,关闭未平仓头寸,并停止所有交易活动,直到当前交易时段结束。
此命令指定使用strategy.entry()命令时可能的最大持仓规模 。如果进入命令的数量导致市场持仓超过此阈值,则策略将减少订单数量,以使最终持仓不超过限制。
利润
保证金是交易者必须在其账户中持有的市场头寸的最低百分比,作为从经纪商处获得和维持贷款的抵押品,以实现其所需的杠杆。strategy ()margin_long
声明的和
margin_short
参数
以及脚本设置“属性”选项卡中的“多头/空头头寸保证金”输入允许策略指定多头和空头头寸的保证金百分比。例如,如果交易者将多头头寸的保证金设置为 25%,则他们必须有足够的资金来支付 25% 的未平多头头寸。这个保证金百分比还意味着交易者可能会在交易中花费高达 400% 的股本。
如果策略模拟的资金无法弥补保证金交易的损失,经纪商模拟器就会触发追加保证金通知,强制清算全部或部分头寸。模拟器清算的合约/股票/手数/单位的确切数量是弥补损失所需的四倍,以防止在后续交易中不断追加保证金。模拟器使用以下算法计算金额:
- 计算该仓位花费的资本金额:
Money Spent = Quantity * Entry Price
- 计算证券市场价值 (MVS):
MVS = Position Size * Current Price
MVS
计算开仓利润为和 之间的差值Money Spent
。如果仓位为空头,我们将其乘以 -1。- 计算该策略的权益价值:
Equity = Initial Capital + Net Profit + Open Profit
- 计算保证金比率:
Margin Ratio = Margin Percent / 100
- 计算保证金价值,即支付交易者部分头寸所需的现金:
Margin = MVS * Margin Ratio
- 计算可用资金:
Available Funds = Equity - Margin
- 计算交易者损失的总金额:
Loss = Available Funds / Margin Ratio
- 计算交易者需要清算多少合约/股票/手数/单位才能弥补损失。我们将此值截断为与当前符号的最小头寸大小相同的小数精度:
Cover Amount = TRUNCATE(Loss / Current Price).
- 计算经纪商将清算多少单位来弥补损失:
Margin Call = Cover Amount * 4
为了详细检查此计算,让我们将内置的超级趋势策略添加到 1D 时间范围内的 NASDAQ:TSLA 图表中,并在策略设置的“属性”选项卡中将“订单规模”设置为股权的 300%,将“多头仓位保证金”设置为 25%:
第一次入场发生在 2010 年 9 月 16 日,当时股价为开盘价。该策略以 4.43 美元(入场价)买入 682,438 股(仓位规模)。然后,在 2010 年 9 月 23 日,当价格跌至 3.9(当前价格)时,模拟器通过追加保证金强制清算了 111,052 股。
策略警报
常规 Pine Script™ 指标有两种不同的机制来设置自定义警报条件: alertcondition() 函数,它每个函数调用跟踪一个特定条件; alert() 函数,它同时跟踪所有调用,但在调用次数、警报消息等方面提供了更大的灵活性。
Pine Script™ 策略不适用于 alertcondition() 调用,但它们支持通过 alert() 函数生成自定义警报。除此之外,每个创建订单的函数还带有自己的内置警报功能,无需任何额外的代码即可实现。因此,任何使用订单下单命令的策略都可以在订单执行时发出警报。此类内置策略警报的精确机制在 我们的用户手册中的警报页面的订单执行事件部分中进行了描述。
当策略使用创建订单的功能和该alert()
功能时,警报创建对话框会提供触发条件的选择:它可以在alert()
事件、订单填写事件或两者上触发。
对于许多交易策略而言,触发条件与实时交易之间的延迟可能是一个关键的性能因素。默认情况下,策略脚本只能
在实时条形图收盘时
执行alert()函数调用,考虑使用alert.freq_once_per_bar_close,而不管freq
调用中的参数是什么。用户还可以通过calc_on_every_tick = true
在
strategies()
调用中包含或在创建警报之前在策略设置的“属性”选项卡中选择“每次报价时重新计算”选项来更改警报频率。但是,根据脚本的不同,这也可能对策略的行为产生不利影响,因此使用此方法时请谨慎并注意限制。
当向第三方发送警报以实现策略自动化时,我们建议使用订单执行警报而不是 alert
()
函数,因为它们不受相同限制;订单执行事件发出的警报会立即执行,不受脚本
calc_on_every_tick
设置的影响。用户可以通过编译器注释设置订单执行警报的默认消息@strategy_alert_message
。此注释提供的文本将填充警报创建对话框中订单执行的“消息”字段。
以下脚本显示了默认订单填充警报消息的简单示例。在
strategy()
声明语句上方,它使用@strategy_alert_message
占位
符来表示消息文本中的交易操作、头寸规模、股票代码和填充价格值:
//@version=5
//@strategy_alert_message {{strategy.order.action}} {{strategy.position_size}} {{ticker}} @ {{strategy.order.price}}
strategy("Alert Message Demo", overlay = true)
float fastMa = ta.sma(close, 5)
float slowMa = ta.sma(close, 10)
if ta.crossover(fastMa, slowMa)
strategy.entry("buy", strategy.long)
if ta.crossunder(fastMa, slowMa)
strategy.entry("sell", strategy.short)
plot(fastMa, "Fast MA", color.aqua)
plot(slowMa, "Slow MA", color.orange)
当用户从“条件”下拉选项卡中选择其名称时,此脚本将使用其默认消息填充警报创建对话框:
触发警报后,策略将使用相应的值填充警报消息中的占位符。例如:
测试策略说明
交易者通常会根据历史和实时市场情况测试和调整其策略,因为许多人认为分析结果可能会提供有价值的见解,了解策略的特征、潜在弱点以及未来的潜力。但是,交易者应始终注意模拟策略结果的偏差和局限性,尤其是在使用结果支持实时交易决策时。本节概述了与策略验证和调整相关的一些注意事项以及减轻其影响的可能解决方案。
回溯测试和前向测试
回测是交易者用来评估交易策略或模型历史表现的一种技术,通过模拟和分析历史市场数据上交易策略或模型的过去结果;该技术假设,分析策略过去数据的结果可以洞悉其优势和劣势。在回测时,许多交易者会调整策略的参数,以期优化其结果。分析和优化历史结果可能有助于交易者更深入地了解策略。然而,交易者在根据优化的回测结果做出决策时,应始终了解风险和局限性。
除了回溯测试之外,审慎的交易系统开发通常还涉及将实时分析作为前瞻性评估交易系统的工具。前瞻性测试旨在衡量策略在实时、真实市场条件下的表现,其中交易成本、滑点和流动性等因素会对其表现产生重大影响。前瞻性测试的明显优势在于不受某些类型偏差(例如前瞻偏差或“未来数据泄露”)的影响,但缺点是测试数据量有限。因此,它通常不是用于策略验证的独立解决方案,但它可以提供对策略在当前市场条件下表现的有用见解。
回溯测试和前向测试是同一枚硬币的两面,因为这两种方法都旨在验证策略的有效性并确定其优缺点。通过结合回溯测试和前向测试,交易者可能能够弥补一些限制,并更清楚地了解其策略的表现。然而,交易者需要净化他们的策略和评估过程,以确保洞察尽可能与现实相符。
前瞻性偏见
在回测某些策略时,一个典型的问题是,在评估过程中未来数据会泄漏到过去,即那些请求替代时间范围数据、使用重绘变量(例如
timenow )或改变条形内订单填充计算行为的策略。这被称为前瞻偏差。由于未来实际上永远无法预先知道,这种偏差不仅是导致策略结果不切实际的常见原因,而且也是策略重绘的典型原因之一。交易者通常可以通过前向测试他们的系统来确认这种偏差,因为前瞻偏差不适用于当前条形图之外没有已知数据的实时数据。用户可以通过确保不使用将未来泄漏到过去的重绘变量、request.*()
函数不包含
barmerge.lookahead_on
而不偏移数据系列(如
我们关于重绘的
页面的此部分所述)以及它们使用现实的计算行为来消除策略中的这种偏差。
选择偏差
选择偏差是许多交易者在测试其策略时遇到的一个常见问题。当交易者仅分析特定工具或时间范围的结果而忽略其他工具或时间范围的结果时,就会发生这种情况。这种偏差可能导致对策略稳健性的扭曲看法,从而影响交易决策和绩效优化。交易者可以通过在多个(最好是多样化的)符号和时间范围内评估其策略来减少选择偏差的影响,务必不要忽略其分析中的不良绩效结果或挑选测试范围。
过度拟合
优化回测时的一个常见陷阱是过度拟合(“曲线拟合”)的可能性,这种情况发生在策略针对特定数据量身定制但无法很好地推广到新的、未见过的数据时。一种广泛使用的有助于降低过度拟合可能性并促进更好推广的方法是将工具的数据分成两个或多个部分,以在用于优化的样本之外测试策略,也称为“样本内”(IS)和“样本外”(OOS)回测。在这种方法中,交易者使用 IS 数据进行策略优化,而 OOS 部分用于测试和评估 IS 优化后在新数据上的表现,而无需进一步优化。虽然这种方法和其他更强大的方法可以让人们一窥策略在优化后的表现,但交易者应该谨慎行事,因为未来本质上是不可知的。无论用于测试和优化的数据如何,任何交易策略都无法保证未来的表现。