为什么负资本不会停止交易?
每个TradingView策略都是基于一定的初始资金进行回测的。我们可以通过 strategy() 函数的 initial_capital 参数,或者在策略设置窗口中通过”Initial Capital”选项来设定这个金额。
但有时,这笔交易资金似乎形同虚设。因为当资金过低(甚至变为负数)时,TradingView策略似乎仍然在继续生成交易。这究竟是为什么?
在模拟成交一个订单之前,TradingView会进行几项检查,例如,它会检查策略的风险管理规则和金字塔(加仓)设置。但是,当我们基于固定的数量或固定的现金金额来设定订单大小时,检查账户资金是否充足并不在这些检查范围之内。因此,即使资金已不足以购买预设数量的股票,TradingView依然会执行这个订单。
这种情况可能看起来很奇怪,甚至令人困惑。但这既不是TradingView的bug,也不是你策略中的错误,更不意味着 initial_capital 参数坏了。恰恰相反,这正是TradingView被设计的工作方式。
TradingView使用策略的初始资金主要用于两个方面:一是在回测结束时,计算各项性能指标(如净利润、最大回撤等);二是在启用按权益百分比下单时,计算订单的规模。
基于权益的订单规模确实会停止交易
我们如何设定订单规模,决定了当资金过低或变为负数时,TradingView是否会停止交易:当订单规模是固定的数量或固定的现金金额时,即使策略资金已经为负,TradingView仍会继续交易;而当我们将订单规模设定为基于策略权益的百分比时,一旦策略权益不足以购买哪怕一个单位的合约、股票或手数,TradingView就不会再生成新订单。
提示:要将默认订单大小设置为权益的百分比,我们需要将
strategy()函数的default_qty_type参数设为strategy.percent_of_equity。我们也可以直接在策略设置的属性标签页中,通过”Order Size”选项来完成此设置。
从回测的角度来看,对于使用固定数量或固定金额下单的方式,系统忽略资金状况是有一定道理的。毕竟,这种设置方式本身就隐含地告诉了TradingView:无论资金如何变化,交易的规模始终保持不变。因此,订单大小与策略的资金变化是脱钩的。
通过代码检查资金是否充足
我们完全可以通过编写一些自定义代码,来让策略在资金不足时停止交易。
内置变量 strategy.equity 可以返回策略当前的权益值。通过在每次生成交易信号时检查这个变量,我们就可以在资金过低时阻止交易的发生。
例如,如果 strategy.equity 的值已经小于我们计划的交易规模(订单数量乘以收盘价),我们就可以做两件事:要么根据剩余的权益相应地减小订单数量,要么因资金不足而直接放弃这次交易。
为什么默认订单规模设置不起作用?
在TradingView中,我们有两种方式来设置策略的默认订单数量:一是通过策略设置窗口属性标签页中的”订单大小”选项,二是通过在代码中使用 default_qty_type 和 default_qty_value 参数。
但有时我们发现,即使设置了这些选项,策略的实际交易数量也并未改变。这是为什么呢?
代码中的订单数量会覆盖默认设置
上述两种方式设定的都只是一个默认值。这个默认值并不会在所有情况下都被使用。只有当策略的下单代码没有明确指定订单数量时,TradingView才会采用这个默认设置。
换句话说,规则是这样的:当代码在执行开仓函数时没有指定数量,策略将使用你在设置中或代码中定义的默认订单数量;当代码在执行开仓函数时明确指定了一个数量,那么这个指定的数量将会覆盖默认订单数量的设置。
所以,最终是否使用默认订单数量,完全取决于策略代码的具体写法。
如何检查代码是否覆盖了默认订单数量
策略的代码本身就能告诉我们,默认订单数量是在哪里以及如何被覆盖的。我们可以通过两个步骤来检查:先在策略代码中找到所有 strategy.entry() 和 strategy.order() 函数的调用——这是仅有的两个可以开立仓位的订单函数;然后检查这些函数调用是否设置了开仓数量。有两种可能的方式:一是通过一个名为 qty 的关键字参数,例如 strategy.order(id="EL", long=true, qty=10);二是通过位置参数(列表中的第3个参数),例如 strategy.entry("Buy", strategy.long, 10)。
只要在函数调用中,订单数量被赋予了一个具体的值,那么这个值就会覆盖掉策略的默认订单数量设置。
如何修复代码以使用默认订单数量
一旦我们找到了覆盖默认订单数量的代码,就可以轻松地修复这个问题:如果代码中使用了关键字参数 qty,我们只需将 qty 参数及其值整个删除即可,例如将 strategy.order(id="EL", long=true, qty=10) 修改为 strategy.order(id="EL", long=true);如果代码使用的是位置参数,我们将开仓函数的第三个参数(即代表数量的参数)的值改为 na,例如将 strategy.entry("Buy", strategy.long, 10) 修改为 strategy.entry("Buy", strategy.long, na)。
我们甚至可以条件性地将 qty 参数设为 na。通过这种方式,我们可以在代码计算的订单数量和TradingView的默认订单数量之间灵活切换。
为什么退出订单总是先进先出?
在TradingView中,有两个函数可以用来平掉一个特定的入场交易:strategy.exit() 函数和 strategy.close() 函数。但无论我们如何使用这两个函数,TradingView似乎总是默认采用先进先出(first in, first out,FIFO)的平仓顺序。我们该如何解决这个问题呢?
问题所在:无法从指定的入场交易平仓
为了能控制哪个平仓指令对应哪个开仓指令,TradingView在 strategy.exit() 函数中提供了 from_entry 参数。同样,strategy.close() 函数的第一个参数也是用来指定要平掉哪个入场单的。
这意味着,我们的代码可以写出类似这样的逻辑:
// 优先平掉第二个入场单
if firstExit
strategy.close("EL 2")
这段 if 语句检查第一个平仓信号是否出现。如果出现,strategy.close() 应该明确地去平掉ID为 “EL 2” 的那个入场单。
但通常情况下,这似乎并不会按预期工作。下面是策略测试器窗口中交易列表的一个例子:
虽然订单的注释(Comment)是正确的,但实际的平仓顺序却并非如此:第一个平仓指令并没有平掉 “EL 2″。相反,TradingView仍然是先平掉了第一个入场单,然后才平掉了第二个。
这种先进先出的行为与我们代码中设定的逻辑相悖。那么,我们该如何修正它呢?
解决方案:组合使用close_entries_rule和指定入场ID
在TradingView中,我们完全可以实现对平仓顺序的精确控制,但这需要满足两个条件:一是在 strategy() 函数中,将 close_entries_rule 参数的值设为 "ANY";二是在平仓时,明确使用订单函数来指定要平掉的具体入场ID——strategy.exit() 函数使用 from_entry 参数来实现此功能,strategy.close() 函数则通过其第一个参数 id 来实现。
如果这两个条件没有同时满足,TradingView就会始终强制执行其默认的先进先出规则,即便我们的代码中已经明确指定了平仓目标。
示例策略:覆盖默认的FIFO平仓规则
让我们将这两个步骤应用到一个示例策略中。下方的脚本会对每个仓位进行两次开仓,然后通过 strategy.close() 分两次平仓。
但我们不采用TradingView默认的FIFO规则,而是指示 strategy.close() 优先平掉第二次的入场单。这样,策略实际上就实现了后进先出(last in, first out,LIFO)的平仓顺序。
策略的代码如下:
//@version=5
strategy(title="从指定入场平仓", overlay=true,
pyramiding=2, close_entries_rule="ANY")
// 定义交易条件
newDay = dayofmonth != dayofmonth[1]
firstEntry = newDay and dayofweek == dayofweek.monday
secondEntry = newDay and dayofweek == dayofweek.tuesday
firstExit = newDay and dayofweek == dayofweek.thursday
secondExit = newDay and dayofweek == dayofweek.friday
// 提交入场订单
if firstEntry
strategy.entry("EL 1", strategy.long)
if secondEntry
strategy.entry("EL 2", strategy.long)
// 生成平仓订单
if firstExit
strategy.close("EL 2") // 优先平掉第二个入场单
if secondExit
strategy.close("EL 1") // 之后再平掉第一个入场单
这个策略的逻辑被有意简化了,让我们聚焦于关键的两个步骤。第一步,为了让平仓单可以平掉任意一个入场单,我们将 close_entries_rule 设置为 "ANY":
strategy(title="从指定入场平仓", overlay=true,
pyramiding=2, close_entries_rule="ANY")
有了 "ANY" 这个值,我们的代码就可以按任意顺序平仓,TradingView将不再强制执行FIFO规则。
第二步,我们在代码中明确指示要平掉哪个入场单。在这个策略里,我们是这样使用 strategy.close() 函数的:
// 生成平仓订单
if firstExit
strategy.close("EL 2")
if secondExit
strategy.close("EL 1")
这里的第一个平仓指令的目标是第二个入场单("EL 2"),而第二个平仓指令的目标是第一个入场单("EL 1")。
由于我们同时满足了这两个必要条件,现在让我们看看TradingView是如何处理平仓的:
成功了!现在,第二个入场订单(”EL 2″)被优先平掉了。一天之后,第一个入场订单(”EL 1″)才被平掉。
简单总结一下:TradingView策略的平仓订单可以按照任意顺序来平掉入场单,但必须满足两个前提条件。首先,我们需要在 strategy() 函数中将 close_entries_rule 参数的值设为 "ANY";然后,我们的平仓函数(strategy.exit() 或 strategy.close())必须明确指定要平掉哪一个入场单的ID。如果我们不这样做,TradingView就会始终坚持使用先进先出的规则来平仓。




