PAYATHON 2026

Pine Script v6:UDT 数组切片复制后 push() 为何越界?

数据小陈

结论

按文档语义,suffix.copy() 应创建独立的数组容器,后续调用 push() 不应仅因为它来自切片而发生越界。现有材料不足以确定这次报错的内部原因,也不能据此认定是 UDT 或 switch 的缺陷。

可以先用一个避开切片的方案:创建新数组,逐项放入需要保留的后缀元素,再替换 self.things。这个方案保留对 Thing 对象的引用,也处理了删除全部元素的情况。下面的代码尚未在 TradingView 中运行验证。

slice() 与 copy() 有什么区别?

array.slice() 创建一个关联原数组指定区间的窗口,array.copy() 则创建新的数组容器。对于 array<Thing>,复制后的元素仍引用原来的 Thing 对象,因此两个数组都可能观察到对象字段的修改。单纯追加或删除副本中的元素,不应改变原数组结构。TradingView 数组文档

切片范围有效时,下面的写法符合文档描述:

suffix = self.things.slice(t.pos + 1, self.things.size())
self.things := suffix.copy()

不能仅用“切片共享原数组”解释复制后 push() 的异常,也没有必要为了追加元素而深复制所有 Thing 对象。

为什么删除 switch 后错误会消失?

Pine 编译器会删除不影响脚本输出的代码。如果删除读取 passed 的 switch 后,测试调用不再影响绘图、日志或其他输出,相关计算可能被一起移除。这可以解释“删除后不再报错”的现象,但仍是根据编译机制做出的推断,尚不能确认当前复现中具体发生了哪些优化。TradingView 编译优化说明

排查时可以保留可观察输出,例如绘制数组大小。相比单纯删除 switch,这样更容易判断数组逻辑是否仍在执行。

如何改为显式复制后缀?

以下示例恢复了 Pine 所需的缩进,并用新数组保存后缀,不再创建 prefix 或 suffix 切片:

//@version=6
indicator("Repro - explicit suffix copy")

type Thing
    int pos = -1
    bool output = false

type Detector
    array<Thing> things = na

method detect(Detector self, Thing t) =>
    if na(self.things)
        self.things := array.new<Thing>()

    self.things.push(t)

    if t.output
        int first = t.pos + 1
        int count = self.things.size()

        if first < 0 or first > count
            runtime.error("Invalid prefix length")

        array<Thing> kept = array.new<Thing>()
        if first < count
            for i = first to count - 1
                kept.push(self.things.get(i))

        self.things := kept

    true

var Detector detector = Detector.new()

Thing thing = switch bar_index
    0 => Thing.new(pos=-1, output=false)
    1 => Thing.new(pos=0, output=true)
    2 => Thing.new(pos=0, output=true)
    3 => Thing.new(pos=-1, output=false)
    => na

bool passed = true
if not na(thing)
    passed := detector.detect(thing)

result_colour = switch
    passed => color.green
    => color.red

bgcolor(result_colour)
plot(detector.things.size(), title="things size")

这里的 first 是追加当前对象后,需要保留的第一个索引:

  • first == 0:保留全部元素。
  • 0 < first < count:只复制后缀。
  • first == count:保留空数组,不进入复制循环。
  • 超出上述范围:显式报错,避免掩盖无效的 t.pos。

按代码逻辑推算,bar 0、1、2、3 处理结束后的数组大小应依次为 1、1、1、2。这是推算结果,尚未经实测。

原复现还需要检查哪些地方?

原复现中的 prefix 没有被使用,而且在 t.pos == -1 时会构造 slice(0, 0)。文档要求切片起始索引小于结束索引,空区间需要单独处理,不能直接当作正常切片使用。如果业务需要前缀,应在区间非空时再创建它。TradingView 切片边界说明

原来的 switch bar_index 只覆盖 0 到 3,继续运行时需要处理没有匹配项的情况。示例通过 => na 和 if not na(thing) 避免对不存在的 Thing 访问字段。

错误堆栈显示 array.insert(),而源码调用的是 push()。仅凭名称差异,无法确定内部实现或错误根因。修正空区间并保留数组输出后,如果原来的 slice().copy() 路径仍稳定报错,可以对照上述显式复制版本,向 TradingView 提交最小复现,进一步确认是否为编译器或运行时问题。

备注:内容仅供参考。