限制策略的日内交易数量:strategy.risk.max_intraday_filled_orders()函数
理想情况下,我们的策略只应在存在正向期望值时才进行交易。但在市场处于震荡时,策略很可能会产生过多低质量的交易信号。本文将介绍如何利用TradingView的 strategy.risk.max_intraday_filled_orders() 函数来限制日内交易的频率。
避免策略在单个交易时段内过度交易
当我们的交易策略陷入市场噪音,交易频率远超平时,这往往对最终利润无益。在这种时候,更好的选择是停止当天的交易,待次日市场恢复常态后再继续。
为了限制我们策略的日内交易次数,我们可以使用 strategy.risk.max_intraday_filled_orders() 函数。通过这种方式,我们可以防止策略因过度交易而陷入亏损的泥潭。该函数的基础语法格式如下:
strategy.risk.max_intraday_filled_orders(count)
其中,count 参数是一个必需的数值,用以指定在单个交易时段内,所允许的已成交入场和出场订单的最大总数。一旦达到此值,TradingView便会停止该策略当日的交易。
因此,如果我们不希望策略每天成交的订单超过20笔,可以这样使用该函数:
// 在单个日内交易时段内,成交的订单数不得超过20笔
strategy.risk.max_intraday_filled_orders(count=20)
请注意,该函数会监控日内交易时段内的所有已成交交易。这包括了通过 strategy.entry() 发送的入场订单、通过 strategy.order() 生成的交易、通过 strategy.exit() 生成的平仓订单,以及通过 strategy.close() 和 strategy.close_all() 提交的市价平仓单。
该函数的核心特性
了解了如何调用该函数后,让我们来深入探究它的一些重要特性。
首先,日内(Intra-day)的定义是基于品种的交易时段。TradingView是根据交易品种的交易时段来衡量日内交易数量的。因此,该函数计算的是在单个交易日内发生的交易,而非日历日。换言之,日内交易的计数器不会在午夜(00:00)日历日结束时重置,它会在该品种的交易时段结束时才被清零。
对于许多品种(如多数欧洲股票,9:00开盘,17:30收盘),交易日与日历日是重合的。但情况并非总是如此。例如,外汇品种EUR/JPY的交易时间是从纽约时间17:00到次日17:00。这意味着,所有在周一17:00之后到周二17:00之前发生的交易,都会被计入同一个限额内。(如果你想查看特定品种的交易时间,可以在图表上右键点击其名称,然后从下拉菜单中选择商品信息。)
在日线及以上周期,日内的含义变为单根K线。该函数的设计初衷是用于日内策略。但当我们在更高的时间框架(如日线图)上使用它时,它依然有效,只是工作方式略有不同。在日线及更高的时间框架上,它限制的是单根K线内已成交的订单数量,1天及以上的时间周期被简单地视为一天。因此,对于日线图或更高周期,日内的含义从一个交易时段切换为一根价格K线。如果我们使用此函数限制日内交易为10笔,那么在日线图上,它实际限制的是每根日K线最多成交10笔订单。(这一特性使得该函数在启用每个Tick都重新计算设置时非常有用,因为该设置可能导致策略在单根K线内提交大量交易。)
此风险规则同时限制入场和出场订单。计入限额的,是所有已成交的订单,这既包括了入场订单,也包括了平仓(或部分平仓)的订单。目前,我们无法将该函数配置为只计算入场或只计算出场。
此风险函数会暂停策略交易。一旦策略在当日成交的订单数达到了设定的上限,所有待处理的挂单都将被取消;如果策略当前有持仓,TradingView会提交一个市价单来平掉该仓位;在当前交易时段结束前,TradingView将不再允许该策略进行任何新的交易。请注意,TradingView不会在次日自动为你重新建立之前被强制平掉的仓位。因此,如果我们持有多仓时此规则被触发,那么若想在第二天继续持有多仓,我们的策略必须重新提交一个新的买入订单。
此风险函数始终有效。它在每次脚本计算时都处于激活状态,因此,我们脚本的每一个决策和行为,都会受到此风控规则的制约。这也意味着,整个回测和所有实时信号都会被限制。要禁用它,没有捷径可走,只能通过编辑策略代码,移除该函数或使用Ctrl + /快捷键将其注释掉。
此外,函数的执行方式不可条件化。目前,我们无法有条件地执行该函数。这意味着我们不能通过 if 语句,根据特定事件来动态启用此风控规则:
// 错误!不能在if语句中调用
if close > ta.ema(close, 10)
strategy.risk.max_intraday_filled_orders(count=10)
同样,我们也不能根据条件来动态设置函数的参数值:
// 错误!参数不能是动态的
strategy.risk.max_intraday_filled_orders(count= close > ta.ema(close, 10) ? 10 : 5)
这两个示例都会引发TradingView的编译错误。因此,脚本在整个回测和实盘期间,都会使用同一个固定的日内交易次数上限,无法根据市场状况或策略表现进行自适应调整。
不过,虽然该函数没有手动的界面设置,但我们可以创建自己的输入选项。这样,我们至少可以在不修改代码的情况下,方便地配置最大交易次数。示例如下:
// 创建一个用于设置最大日内交易次数的输入项
maxTrades = input.int(20, title="最大日内交易次数")
// 使用该输入项的值来调用风险管理函数
strategy.risk.max_intraday_filled_orders(maxTrades)
这里,我们用 input.int() 创建了一个整数输入框,默认值为20,并用 maxTrades 变量来追踪其当前值。然后,我们将 maxTrades 变量传递给 strategy.risk.max_intraday_filled_orders() 函数,从而实现通过界面来调整风控参数。
最后,该函数的作用对象仅限当前策略。它只对其所在的策略脚本生效,不会感知其他策略的行为,也无法获知其他图表上的交易情况,更不会知道你在TradingView中执行的任何手动交易。因此,如果你同时也在手动交易,那么你当日的总交易笔数仍有可能超出预期。
示例:日内成交订单不超过5笔
详细了解了该函数的工作机制后,让我们通过一个完整的TradingView策略脚本来看看如何应用它。下方案例基于12周期的动量指标(momentum)及其22周期的EMA均线进行交易。当动量线上穿其均线或零线时,我们建立多头或空头仓位。
为了防止策略在震荡市中过度交易,我们使用 strategy.risk.max_intraday_filled_orders() 将日内成交的订单总数限制为5笔。策略代码如下:
//@version=5
strategy(title="示例:最大日内成交订单",
overlay=false)
// 计算指标值
momValue = ta.mom(close, 12)
momEma = ta.ema(momValue, 22)
// 在副图上绘制指标线
plot(momValue, color=color.teal)
plot(momEma, color=color.orange)
hline(0)
// 将每日交易限制在5笔以内
strategy.risk.max_intraday_filled_orders(count=5)
// 定义交易条件
enterLong = ta.crossover(momValue, momEma) or
ta.crossover(momValue, 0)
enterShort = ta.crossunder(momValue, momEma) or
ta.crossunder(momValue, 0)
// 提交订单
if enterLong
strategy.entry("EL", strategy.long)
if enterShort
strategy.entry("ES", strategy.short)
我们首先定义策略设置,然后用 ta.mom() 和 ta.ema() 计算动量值及其EMA。接着,通过 plot() 和 hline() 将指标线绘制出来。然后,我们加入核心的风控规则:
// 将每日交易限制在5笔以内
strategy.risk.max_intraday_filled_orders(count=5)
这里,我们将 count 参数设为5,确保我们的策略在单个交易日内,成交的入场和出场订单总数不能超过5笔。一旦达到这个数量,TradingView便会停止该策略当日的交易活动。接下来,我们定义交易条件:
// 定义交易条件
enterLong = ta.crossover(momValue, momEma) or
ta.crossover(momValue, 0)
enterShort = ta.crossunder(momValue, momEma) or
ta.crossunder(momValue, 0)
我们创建了两个布尔变量 enterLong 和 enterShort。当动量线上穿其EMA均线,或上穿零线时,enterLong 为 true。同理,当动量线下穿其EMA均线,或下穿零线时,enterShort 为 true。代码的最后一部分负责提交入场订单:
// 提交订单
if enterLong
strategy.entry("EL", strategy.long)
if enterShort
strategy.entry("ES", strategy.short)
这两个 if 语句分别在 enterLong 或 enterShort 为 true 时,通过 strategy.entry() 函数建立多头或空头仓位。以下是该策略在图表上的行为方式:
在本例中,策略在以太坊上执行了数笔交易。但在完成第5笔交易后(发生在早上6点刚过),TradingView便停止了该策略。现在我们必须等待下一个交易时段开始,脚本才能重新进行交易。对于ETH/USD这个交易对,下一个时段从午夜(00:00)开始。
示例:限制并追踪TradingView的日内交易
下方的策略脚本基于两条移动平均线的交叉进行交易。逻辑本身很简单,但我们还将额外绘制出日内的成交订单数量。这使得我们可以直观地观察策略何时达到我们通过 strategy.risk.max_intraday_filled_orders() 函数设定的每日10笔交易的上限。策略的完整代码如下:
//@version=5
strategy(title="示例:每日最大订单数",
overlay=false, precision=0)
// 计算移动平均线
fastMA = ta.sma(close, 6)
slowMA = ta.sma(close, 12)
// 设定每日交易不超过10次
strategy.risk.max_intraday_filled_orders(count=10)
// 提交交易订单
if ta.crossover(fastMA, slowMA)
strategy.entry("EL", strategy.long, qty=10)
if ta.crossunder(fastMA, slowMA)
strategy.entry("ES", strategy.short, qty=10)
// 计算当日已成交的订单数量(此代码
// 假设每根K线最多只成交一笔订单)
filledOrders = 0
filledOrders := dayofweek != dayofweek[1] ? 0 :
strategy.position_size != strategy.position_size[1] ?
filledOrders[1] + 1 :
filledOrders[1]
// 在图表上以柱状图形式绘制成交订单数,以便追踪
plot(filledOrders, style=plot.style_columns)
让我们来逐步解析这段代码。首先,我们用 strategy() 函数配置策略信息,然后用 ta.sma() 计算两条均线。接着,我们设定日内交易的次数上限:
// 设定每日交易不超过10次
strategy.risk.max_intraday_filled_orders(count=10)
我们调用 strategy.risk.max_intraday_filled_orders() 函数,并将其 count 参数设为10,从而允许策略在单个日内交易时段中,最多完成10次开仓或平仓交易。然后是基于均线交叉的交易逻辑:
// 提交交易订单
if ta.crossover(fastMA, slowMA)
strategy.entry("EL", strategy.long, qty=10)
if ta.crossunder(fastMA, slowMA)
strategy.entry("ES", strategy.short, qty=10)
当快线上穿慢线时做多,反之则做空。接下来,我们编写逻辑来计算并追踪日内的成交订单数:
// 计算当日已成交的订单数量(此代码
// 假设每根K线最多只成交一笔订单)
filledOrders = 0
filledOrders := dayofweek != dayofweek[1] ? 0 :
strategy.position_size != strategy.position_size[1] ?
filledOrders[1] + 1 :
filledOrders[1]
我们定义了一个 filledOrders 变量来充当计数器。我们通过嵌套的两个条件运算符(?:)来更新它的值。第一个运算符通过比较 dayofweek != dayofweek[1] 来判断新的一天是否开始,若是,则将计数器归零。否则,第二个运算符会检查仓位规模 strategy.position_size 是否与上一根K线不同,若不同,则说明发生了一笔交易,计数器加一。如果仓位不变,计数器也保持不变。
重要提示:我们这里用于计算日内交易的自定义代码,其准确性基于两个假设:一是策略在单根K线上不会产生多次交易;二是我们所交易品种的交易时段与日历日基本吻合(因为我们是基于 dayofweek 星期几来进行判断的)。
最后,我们将这个计数器的值绘制出来:
// 在图表上以柱状图形式绘制成交订单数,以便追踪
plot(filledOrders, style=plot.style_columns)
我们用 plot() 函数,将 filledOrders 的值以柱状图(plot.style_columns)的形式展示在副图上。下面是这个策略及其绘图在图表上的实际表现:
在本例中,我们的策略在一天内对以太坊进行了数次交易。该品种的日内交易时段是从0:00到24:00。我们可以看到,副图中的柱状图在每天开始时从0计数,然后随着每一笔策略订单的成交而递增。
然而,这个过程并不会无限持续。当日内成交的订单数达到10笔时,strategy.risk.max_intraday_filled_orders() 函数便会介入,并停止策略当天的所有后续交易。直到第二天开始,计数器归零,策略才能重新提交新的交易。
优点与局限
优点方面:一是简单高效,此函数提供了一种快速限制日内交易频率的简便方法,省去了我们自己编写和测试复杂逻辑的麻烦;二是防止过度交易,通过此函数,我们可以有效避免因策略逻辑缺陷或市场剧烈波动导致的过度交易,这种行为通常会对策略的净利润产生严重的负面影响。
局限方面:一是缺乏灵活性,我们无法根据条件动态地启用或禁用此函数,一旦设置,它将在整个回测及实盘期间持续有效,而不管策略表现或市场状况如何。二是无法动态调整阈值,我们不能根据策略逻辑来动态调整最大交易次数,这使得我们无法实现在市场波动剧烈时减少交易、在市场环境有利时增加交易这类更智能的风控。三是混淆开仓与平仓,该函数统计的是所有成交订单,包括开仓和平仓。然而,从风险控制的角度看,开仓是增加风险,而平仓是降低或锁定风险,将两者混为一谈可能不够精细。四是不考虑交易盈亏,此风控规则不关心日内交易是盈利还是亏损。虽然限制亏损交易是好事,但如果策略正处于一个连续盈利的状态,我们显然希望它能继续交易下去,而不是被强制停止。
除了本文讨论的函数,TradingView还提供了其他多种风险管理函数:strategy.risk.allow_entry_in() 限制策略的交易方向(只做多或只做空);strategy.risk.max_cons_loss_days() 限制最大连续亏损天数;strategy.risk.max_drawdown() 基于最大资金回撤来停止策略;strategy.risk.max_position_size() 限制策略的最大持仓规模;strategy.risk.max_intraday_loss() 限制策略的单日最大亏损额。我们可以单独使用这些规则,也可以将它们组合起来。
简单总结一下:strategy.risk.max_intraday_filled_orders() 函数会在策略的日内成交订单数达到设定的上限时,自动停止策略。具体来说,当限制被触发时,TradingView会先取消所有待处理的订单,然后提交一个市价单来平掉当前持有的仓位,并阻止策略生成任何新的交易。只有在当前交易时段结束后,策略才被允许重新开始交易。
该函数只有一个参数,即允许的日内开仓和平仓订单的总数。我们无法对这个值或函数本身进行条件化设置,因此它在整个回测和实盘期间都以固定的阈值持续生效。TradingView是根据交易品种的交易时段来统计日内交易的,而非日历日。例如,如果一个品种的交易时段是从17:00到第二天的17:00,那么在17:00之后发生的交易将被计入新一天的限额中。当我们在日线或更高的时间框架上运行策略时,此函数的功能会变为限制每根K线的交易数量。
限制策略的日内损失:strategy.risk.max_intraday_loss()函数
即使是优秀的日内交易策略,也难免会遇到做什么都错的日子。为了防止不利的市场状况导致严重的资金损失,我们可以在策略的日内亏损达到某个预设上限时,及时叫停。在TradingView中,我们可以通过 strategy.risk.max_intraday_loss() 函数来实现这一功能。
在达到特定日内亏损后暂停TradingView策略
一个持续亏损的交易策略是任何交易者都需正视的大问题。一旦资金回撤变得过大,想要恢复将极其困难。因此,防范于未然远胜于事后补救。除了在连亏期间减小交易规模外,日内策略还可以选择今日休战、明日再来的模式。
为了限制我们策略的日内亏损,我们可以使用 strategy.risk.max_intraday_loss() 函数。一旦脚本在当日的亏损达到了我们预设的上限,交易便会停止,直到下一个交易日策略才会重新启动。该函数的基础语法格式如下:
strategy.risk.max_intraday_loss(value, type)
这两个参数的含义是:value 是一个必需的浮点数值,用以设定最大日内亏损的阈值。这个值可以是基于图表交易品种的固定货币金额,也可以是基于账户权益的百分比。对于后者,此参数的值应在0.0到1.0的范围内(例如,3%应写为0.03)。type 是一个必需的参数,用以指定日内亏损的衡量方式,可选值为 strategy.cash(按亏损的货币金额计算)或 strategy.percent_of_equity(按亏损的权益百分比计算)。
例如,假设我们希望将日内亏损上限设为450个货币单位,可以这样使用该风控函数:
strategy.risk.max_intraday_loss(value=450, type=strategy.cash)
如果我们不希望单日亏损超过账户权益的3%,则应这样使用(注意 value 的格式):
strategy.risk.max_intraday_loss(value=0.03, type=strategy.percent_of_equity)
请注意,一旦达到最大日内亏损,该函数将会停止所有交易活动。这意味着我们不仅无法通过 strategy.entry() 函数建立新仓位,也无法使用 strategy.order() 函数来发起任何新交易。(这一点值得特别留意,因为并非所有风险管理函数都会对 strategy.order() 施加限制。)
该函数的核心特性
了解了如何调用该函数后,让我们来深入探究它的一些重要特性。
首先,日内(Intra-day)的定义是基于品种的交易时段。TradingView并不是根据日历日来衡量日内亏损,而是基于交易品种的交易时段。这两者并非总是重合。例如,荷兰股票的交易时间是9:00到17:30,其交易日与日历日一致。但也有许多品种的交易时段是跨越午夜的。例如,FXCM的原油(FX:UKOIL)的交易时间是从纽约时间17:00到次日17:00。这意味着,在这24小时内发生的所有交易,都属于同一个日内交易时段,strategy.risk.max_intraday_loss() 会在这个完整的时段内来衡量每日亏损。
这也影响到我们的策略何时能恢复交易。假设我们的策略在周三13:30触发了亏损上限。我们不必等到周四才能重新交易。因为一个新的交易时段在周三17:00就开始了,所以在同一个日历日的晚些时候,我们的脚本就可以再次发送订单。(如果你想查看特定品种的交易时间,可以在图表左上角右键点击其名称,然后从下拉菜单中选择商品信息。)
在日线及以上周期,日内的含义变为单根K线。当我们在日线或更高的时间框架上使用该函数时,它无法再衡量我们通常理解的日内亏损。此时,它限制的是在单根价格K线内所产生的亏损。换句话说,TradingView将日线或更高周期的单根K线视为一个独立的交易时段。实际上,在日线及以上周期使用该函数通常意义不大,因为TradingView在默认情况下,每根K线通常只成交一笔订单。但是,如果开启了订单成交后重新计算或每个Tick都重新计算的设置,那么在单根K线内成交多笔订单就成为可能。在这种非典型情况下,你也可以利用它来限制单根K线的最大亏损。
触发后的行为是停止一切交易。当策略在当日的亏损达到了设定的上限时,TradingView会取消所有待处理的挂单;接着,它会发送一个市价单来平掉当前持有的仓位;此后,策略将无法再下达任何新的订单;直到下一个新的交易时段开始,策略才能重新恢复交易。请注意,TradingView不会在新时段开始时为你自动恢复之前的头寸。因此,如果我们持有多仓时被强制平仓,那么若想在下一时段继续交易,策略必须重新发送一个新的买入订单。
当权益为负时,策略可能被永久停止。通常情况下,该函数只会暂停一个交易时段的交易。但有一个极为重要的例外:如果我们使用 strategy.percent_of_equity 来衡量日内亏损,并且策略的总权益变成了负数,那么在任何后续的交易日,策略都将无法再发送任何新订单。换言之,当使用百分比模式时,该函数要求策略权益必须为正,才允许其重新交易。由于策略在被禁止交易时无法盈利恢复,这实际上会导致策略被永久停止。而如果我们基于固定金额(即 strategy.cash 设置)来衡量日内亏损,那么即使权益为负也不是问题,策略在下一个交易时段可以照常启动。
实际亏损可能比设定的最大日内亏损更严重。如果策略触发了日内亏损上限,TradingView会通过市价单来强制平仓。当这个市价单遭遇滑点成交时,我们的实际亏损会比预设的上限更糟。这种风险在日内价格剧烈波动的时刻尤为突出,例如在重大经济报告发布前后。在这些情况下,亏损很容易就超过我们设定的最大值。因此,一个明智的做法是,始终将最大日内亏损的设定值,定得比你能承受或想要承受的实际亏损更小一些,以预留安全边际。
函数的作用范围是全局且持续有效的。只要我们将 strategy.risk.max_intraday_loss() 加入到脚本中,它就会在每次脚本计算时影响策略的行为。因此,我们脚本的每一个决策和行为,都会受到最大日内亏损规则的制约。这意味着整个回测和所有实时信号都会被影响。要禁用它,没有捷径可走,只能通过编辑代码,移除该函数或使用Ctrl + /快捷键将其注释掉。
另外,日内亏损的计算不一定需要平仓交易。当我们使用 strategy.percent_of_equity 设置时,日内亏损是基于策略权益来衡量的。权益是初始资金、已平仓盈亏和未平仓头寸浮动盈亏的总和。这个浮动盈亏的成分带来了一个有趣的副作用:由于当持仓的浮动利润减少时,策略的总权益也会下降,因此我们的策略可能在没有任何平仓交易的情况下就触发最大日内亏损。实际上,当一笔盈利持仓的利润在日内回吐时,这本身就被视为一种权益上的亏损,从而可能触发该函数。
函数的执行方式不可条件化。我们无法根据条件来动态执行该函数,也无法用代码逻辑来动态设置其 value 参数。因此,if 语句不能在特定市场条件下才启用此风控规则:
// 错误!不能在if语句中调用
if close > ta.sma(close, 200)
strategy.risk.max_intraday_loss(value=2500, type=strategy.cash)
同样,也不能用条件运算符来动态配置最大日内亏损值:
// 错误!参数不能是动态的
strategy.risk.max_intraday_loss(value= volume < ta.sma(volume, 50) ? 2000 : 4500, type=strategy.cash)
这两个示例都会引发TradingView的编译错误。因此,我们的策略必须在所有K线上都使用同一个固定的日内亏损上限。
不过,虽然该函数没有手动的界面设置,但我们可以创建自己的输入选项,从而在不修改代码的情况下方便地配置函数的值。要基于固定货币金额设置最大日内亏损,我们可以创建一个整数或浮点数输入选项,然后将其值传递给 strategy.risk.max_intraday_loss():
// 创建一个用于设置最大日内亏损(以货币金额计)的输入项
maxLoss = input.int(750, title="最大日内亏损")
// 使用该输入项的值来调用最大日内亏损函数
strategy.risk.max_intraday_loss(maxLoss, type=strategy.cash)
这里,我们用 input.int() 创建了一个整数输入框,默认值为750,并将其值存入 maxLoss 变量。然后,我们将 maxLoss 变量与 type=strategy.cash 一起使用。
要将最大日内亏损配置为权益的百分比,我们同样可以创建一个数值输入选项:
// 创建一个用于设置最大日内权益亏损(%)的输入项
maxPercLoss = input.int(10, title="最大日内权益亏损(%)", minval=1, maxval=100) / 100
// 使用该输入项的值来调用最大日内亏损函数
strategy.risk.max_intraday_loss(maxPercLoss, type=strategy.percent_of_equity)
这段代码通过 input.int() 创建了一个范围在1到100的整数输入框。由于该函数在百分比模式下需要一个0到1之间的小数,我们便将用户的输入值除以100。然后,我们将处理后的 maxPercLoss 变量与 type=strategy.percent_of_equity 一起使用。
最后,该函数仅影响当前策略脚本。它的作用域严格限定在调用了它的那个策略脚本之内,无法感知其他图表上的策略行为,也无法获知你手动执行的交易或在经纪商账户中的实际持仓。因此,当它停止了我们策略的交易活动时,我们依然可以通过手动下单的方式交易同一个品种。同时,其他交易该品种的策略也将保持活跃,不受影响。
示例:设定每个交易日的风险上限
了解了该函数的特性后,让我们通过一个完整的策略脚本,来看看如何实际应用这个风险控制函数。下方的策略基于9周期的动量指标及其均线进行交易。当动量线与它的均线发生交叉时,我们便开仓做多或做空。此外,当动量线穿越其0轴时,我们也会进行交易。
为了防止策略损失过多资金,我们将日内亏损上限设定为100个货币单位。一旦亏损额度触及此限,TradingView便会停止策略当天的所有交易活动。策略的完整代码如下:
//@version=5
strategy(title="最大日内亏损示例", overlay=false,
default_qty_type=strategy.fixed, default_qty_value=15)
// 计算所需指标值
momValue = ta.mom(close, 9)
emaMom = ta.ema(momValue, 15)
// 在图表上绘制指标线
plot(momValue, color=color.blue)
plot(emaMom, color=color.orange)
hline(0, color=color.gray)
// 将日内亏损上限设为100个货币单位
strategy.risk.max_intraday_loss(value=100, type=strategy.cash)
// 定义交易条件
enterLong = ta.crossover(momValue, 0) or
ta.crossover(momValue, emaMom)
enterShort = ta.crossunder(momValue, 0) or
ta.crossunder(momValue, emaMom)
// 提交交易订单
if enterLong
strategy.entry("EL", strategy.long)
if enterShort
strategy.entry("ES", strategy.short)
我们首先通过 strategy() 函数配置策略信息。然后用 ta.mom() 函数计算动量指标,并用 ta.ema() 计算其移动平均线。接着,我们用 plot() 函数将这两个值绘制在图表上。我们将动量线设为蓝色(color.blue),其均线设为橙色(color.orange),并用 hline() 函数在0位绘制一条灰色的水平中轴线。
然后,我们设定策略的亏损限制:
// 将日内亏损上限设为100个货币单位
strategy.risk.max_intraday_loss(value=100, type=strategy.cash)
为了限制日内亏损,我们调用 strategy.risk.max_intraday_loss() 函数。我们将其 value 参数设为100,并使用 strategy.cash 作为 type 参数。这将使得我们的策略在当日亏损达到或超过100个图表品种的计价货币时,自动停止交易。接下来,我们定义交易的触发条件:
// 定义交易条件
enterLong = ta.crossover(momValue, 0) or
ta.crossover(momValue, emaMom)
enterShort = ta.crossunder(momValue, 0) or
ta.crossunder(momValue, emaMom)
我们创建了 enterLong 和 enterShort 这两个布尔变量。前者在动量线上穿0轴或其EMA均线时为 true。后者则在动量线下穿0轴或其EMA均线时为 true。最后,我们根据这些条件提交订单:
// 提交交易订单
if enterLong
strategy.entry("EL", strategy.long)
if enterShort
strategy.entry("ES", strategy.short)
当 enterLong 为真时,我们通过 strategy.entry() 开立多头仓位。当 enterShort 为真时,则开立空头仓位。
虽然这些 if 语句中没有直接体现,但它们的执行实际上都受制于策略的日内亏损状况。因为一旦亏损触及上限,strategy.risk.max_intraday_loss() 就会阻止所有交易。所以,即便 enterLong 或 enterShort 此时为 true,任何新的订单也都不会被发送。下面是这个策略在图表上的实际表现:
该策略交易欧洲斯托克50指数CFD已有一段时间。之后,在一次剧烈的价格下跌前,它建立了一个30手合约的多头仓位。这次下跌触发了策略设置的最大日内亏损上限,TradingView随之平掉了该策略的仓位。在该交易时段的剩余时间内,我们的脚本便无法再交易该品种。
示例:限制并追踪策略的日内亏损
下方的策略在两条移动平均线相互交叉时进行交易。这本身并无特别之处,但我们额外编写了代码来计算并绘制每日的亏损情况。这使得我们可以直观地看到策略的日内亏损是如何演变的。
当然,我们不会让这个亏损无休止地增长。通过 strategy.risk.max_intraday_loss() 函数,我们将日内亏损上限设定为1,000个货币单位。这是策略的完整代码:
//@version=5
strategy(title="最大日内亏损示例", overlay=false)
// 计算移动平均线
fastMA = ta.ema(close, 10)
slowMA = ta.ema(fastMA, 20)
// 将策略风险限制在1000
strategy.risk.max_intraday_loss(value=1000, type=strategy.cash)
// 提交订单
if ta.crossover(fastMA, slowMA)
strategy.entry("EL", strategy.long, qty=10)
if ta.crossunder(fastMA, slowMA)
strategy.entry("ES", strategy.short, qty=10)
// 获取昨日交易结束时的权益
newDay = dayofmonth != dayofmonth[1]
closeEquity = 0.0
closeEquity := newDay ? strategy.equity[1] : closeEquity[1]
// 计算当日的亏损
todaysLosses = math.min(strategy.equity - closeEquity, 0)
// 在图表上绘制数值
plot(todaysLosses, style=plot.style_area, color=color.red)
hline(-1000, color=color.blue, linestyle=hline.style_solid)
让我们来逐步解析这段脚本。首先,我们通过 strategy() 函数定义策略的基本设置。然后使用 ta.ema() 计算两条指数移动平均线(EMA),一条是基于收盘价的10周期均线,另一条是基于第一条均线的20周期均线。接着,我们限制策略的日内亏损:
// 将策略风险限制在1000
strategy.risk.max_intraday_loss(value=1000, type=strategy.cash)
这里,我们调用 strategy.risk.max_intraday_loss() 并传入两个参数。value 设为1000,type 设为 strategy.cash。这两者结合,便将策略的日内亏损上限设定为1,000个图表交易品种的货币单位。随后,我们提交策略的订单:
// 提交订单
if ta.crossover(fastMA, slowMA)
strategy.entry("EL", strategy.long, qty=10)
if ta.crossunder(fastMA, slowMA)
strategy.entry("ES", strategy.short, qty=10)
这两个 if 语句分别利用 ta.crossover() 和 ta.crossunder() 函数,在均线发生金叉或死叉时,通过 strategy.entry() 建立多头或空头仓位。
为了追踪策略的日内亏损,我们首先需要知道它在当天开始时的权益是多少。为此,我们使用了以下代码:
// 获取昨日交易结束时的权益
newDay = dayofmonth != dayofmonth[1]
closeEquity = 0.0
closeEquity := newDay ? strategy.equity[1] : closeEquity[1]
我们在这里创建的 newDay 变量,用于判断当前K线是否为新一天的第一根K线。当 dayofmonth(月份中的日期)与其前一根K线的值不同时,newDay 就为 true。
然后,我们声明了一个 closeEquity 变量,并用 := 将其初始化为持久变量。为了让它能记录每日开盘时的权益,我们使用了条件运算符(?:)。该运算符检查 newDay 是否为 true。如果是,就意味着当前是新一天的第一根K线,那么前一根K线收盘时的权益(strategy.equity[1])就是我们需要的期初权益。如果不是新的一天,我们就让 closeEquity 保持其前一根K线的值(closeEquity[1])。
现在我们知道了策略开始一天的本金,便可以计算出其日内亏损了:
// 计算当日的亏损
todaysLosses = math.min(strategy.equity - closeEquity, 0)
为了计算当日亏损,我们用策略的当前权益(strategy.equity)减去当日开始时的权益(closeEquity)。如果策略盈利,结果为正;如果亏损,则为负。但在这个脚本中,我们只关心亏损情况,所以我们使用Pine Script的 math.min() 函数,取当日盈亏和0之间的较小值,这样就只保留了负值(即亏损)。接下来便是将结果绘制出来:
// 在图表上绘制数值
plot(todaysLosses, style=plot.style_area, color=color.red)
hline(-1000, color=color.blue, linestyle=hline.style_solid)
这里,我们用 plot() 函数将 todaysLosses 变量以面积图的形式绘制出来。同时,我们用 hline() 函数在-1,000的位置画了一条水平线,这样可以更方便地观察策略何时触及了最大日内亏损上限。以下是该策略在图表上的表现:
这个比特币的交易日进行得并不顺利。策略执行了数笔亏损交易。随后,一旦亏损额度超过了$1,000的门槛,TradingView便停止了该策略。但这个停止仅限于当天——到了第二天,策略便重获新生,可以再次发送新的订单。
(请注意,我们编写的这段用于追踪日内亏损的代码,仅适用于那些交易时段与日历日相匹配的品种。这是因为我们的判断基准是 dayofmonth。如果一个品种的交易时段跨越午夜,我们的代码就需要进行修改,以追踪自定义的交易时段。)
优点与局限性
为了总结本文,让我们来看看 strategy.risk.max_intraday_loss() 的长处与不足。优点方面:一是易于使用,我们只需简单地调用它,我们的日内亏损就受到了限制;二是可靠性高,这条风险规则几乎可以确保我们的策略不会遭受灾难性的日内亏损。如果需要自己编写同样功能的代码,我们不仅要进行大量的测试,而且即使如此,也总有可能存在未知的Bug。
局限方面:一是无法条件化启用,我们不能根据条件来动态执行此函数,因此,无论市场状况或策略表现如何,它在整个回测期间都将保持激活状态。二是无法条件化设置,我们不能根据条件来动态设置日内亏损的金额,因此,无论市场是有利还是波动剧烈,我们的策略都将使用同一个固定的每日亏损上限。三是不考虑交易次数,该函数不关心亏损是由多少笔交易造成的。一笔由意外价格跳动引起的巨额亏损,和由15笔日内交易累积起来的同样亏损,其背后可能反映的策略问题是完全不同的。
本文探讨了 strategy.risk.max_intraday_loss() 如何限制日内亏损。但TradingView还提供了更多风险管理工具:strategy.risk.allow_entry_in() 可以禁用多头或空头交易,使策略只做单边;strategy.risk.max_cons_loss_days() 限制连续亏损的天数;strategy.risk.max_drawdown() 限制策略的整体最大资金回撤;strategy.risk.max_position_size() 限制策略的最大持仓规模;strategy.risk.max_intraday_filled_orders() 限制策略的日内成交订单总数。我们可以单独使用这些函数,也可以将它们组合进同一个策略中。
简单总结一下:当达到设定的日内亏损上限时,strategy.risk.max_intraday_loss() 函数会让我们的策略暂停当日的交易。更准确地说,当亏损上限被触发时,TradingView首先会取消所有挂单,然后发送市价单平掉当前持仓,此后便阻止策略生成任何新交易。直到下一个交易时段开始,我们的策略才能重新交易。
该函数有两个参数:value 用以设定最大日内亏损的阈值,type 则定义了衡量亏损的方式。strategy.cash 表示按固定货币金额计算,strategy.percent_of_equity 则表示按权益百分比计算。我们无法通过代码逻辑来有条件地设置这些参数(或函数本身)。因此,它在所有K线上都有效,并且在整个回测和实盘期间都使用同一个固定的日内亏损上限。
为了衡量日内亏损,该函数关注的是交易品种的交易时段,而非日历日。因此,它计算的是在交易日内发生的亏损。如果一个品种的交易时间是从当日17:00到次日17:00,那么在17:00之后发生的亏损将被计入新一天的限额。当我们在日线或更高时间框架的策略中使用该函数时,它转而限制在单根价格K线内所发生的亏损。






