mcpField
P
pine-debugger

pine-debugger

github.com/traderspost开发223463 次安装14 周前更新
代码仓库

关于 pine-debugger

pine-debugger 是用于排查 TradingView 中 Pine Script 代码问题的技能,涵盖变量、循环、重绘、高周期数据和绘图对象的调试方法,并整理常见错误。

主要功能

  1. 01标签与表格变量检查器
  2. 02循环与 NA 诊断
  3. 03重绘检查
  4. 04security 调用检查
  5. 05UDT 与绘图状态检查

适用场景

  1. 01排查循环卡住的脚本
  2. 02追踪意外出现的 NA 值
  3. 03比较历史与实时行为
  4. 04诊断绘图缺失或位置异常

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/traderspost/pinescript-agents --skill pine-debugger

安装后在 Claude Code 中输入/pine-debugger确认已加载。

详细介绍

Pine Script 调试器

专门用于在 TradingView 常常不透明的环境中添加调试工具并排查 Pine Script 代码问题。

⚠️ Pine Script v6 2025 年常见陷阱

重要:2025 年 3 月重大变更——For 循环边界

现在,for 循环会在每次迭代时重新计算边界。

首选修复方案:使用 for...in 循环:

// ✅ BEST: Use for...in for arrays (safe and clean)
for element in myArray
    // Process element - safe even if array is modified

// ✅ BEST: Use for...in with index when needed
for [index, element] in myArray
    // Have both index and value

// ✅ for...in works with matrices too
for row in myMatrix
    // row is an array of the current row's values

// ✅ for...in works with maps (key-value pairs)
for [key, value] in myMap
    // Iterate in insertion order

备用方案:如果必须使用传统的 for 循环,则缓存边界:

// ❌ BUG: Infinite loop or unexpected behavior
var arr = array.new<int>()
for i = 0 to array.size(arr)  // Re-evaluated each iteration!
    array.push(arr, i)        // Array grows, loop never ends

// ✅ FALLBACK: Cache the boundary before the loop
arrSize = array.size(arr)
for i = 0 to arrSize
    array.push(arr, i)

**调试检查:**如果循环似乎卡住,请改用 for...in,或确认边界已缓存。

行续接错误

最常见的语法错误。根据 2025 年 12 月更新的规则:

// ❌ ERROR: "end of line without line continuation"
text = condition ?
    "value1" :
    "value2"

// ✅ FIX: Keep ternary on one line
text = condition ? "value1" : "value2"

// ✅ Inside parentheses - any indentation now works (Dec 2025)
plot(series,
    title="Plot",
    color=color.blue)

// ⚠️ Outside parentheses - still requires non-multiple-of-4 indentation
condition = value1 > value2 and
     value3 > value4   // Must use odd indentation (e.g., 5 spaces)
买卖价变量返回 NA
// ❌ UNEXPECTED: Always na on most charts
bidValue = bid  // Only works on 1T (tick) timeframe!

// ✅ DEBUG: Check timeframe
if barstate.islast
    label.new(bar_index, high,
        "Bid: " + (na(bid) ? "N/A (not 1T)" : str.tostring(bid)))
局部作用域中的 Plot
// ❌ ERROR: "Cannot use 'plot' in local scope"
if condition
    plot(value)

for i = 0 to 10
    plot(close[i])

// ✅ FIX: Use conditional plotting at global scope
plot(condition ? value : na)

核心调试工具包

1. 基于标签的值检查器
// Debug label showing multiple values
if barstate.islast
    debugText = "RSI: " + str.tostring(rsiValue, "#.##") + "\n"
    debugText += "MA: " + str.tostring(maValue, "#.##") + "\n"
    debugText += "Signal: " + (buySignal ? "BUY" : "NEUTRAL")
    label.new(bar_index, high * 1.05, debugText,
        style=label.style_label_down,
        color=color.yellow,
        textcolor=color.black)
2. 基于表格的变量监视器
// Real-time variable monitor table
var table debugTable = table.new(position.top_right, 2, 10,
    bgcolor=color.new(color.black, 80), border_width=1)

if barstate.islast
    table.cell(debugTable, 0, 0, "Variable",
        bgcolor=color.gray, text_color=color.white)
    table.cell(debugTable, 1, 0, "Value",
        bgcolor=color.gray, text_color=color.white)

    table.cell(debugTable, 0, 1, "Bar Index", text_color=color.white)
    table.cell(debugTable, 1, 1, str.tostring(bar_index), text_color=color.yellow)

    table.cell(debugTable, 0, 2, "Close", text_color=color.white)
    table.cell(debugTable, 1, 2, str.tostring(close, "#.####"), text_color=color.yellow)

    table.cell(debugTable, 0, 3, "Signal Active", text_color=color.white)
    sigColor = signalActive ? color.green : color.red
    table.cell(debugTable, 1, 3, signalActive ? "YES" : "NO", text_color=sigColor)
3. 循环迭代调试器
// Debug loop iteration (use for...in for safety)
var array<float> values = array.new<float>()
var int loopIterations = 0

loopIterations := 0

// ✅ PREFERRED: Use for...in (safe, no boundary issues)
for [idx, val] in values
    loopIterations += 1
    // Process val directly, idx available if needed

// Alternative: Traditional for with cached boundary
// boundarySize = array.size(values)
// for i = 0 to boundarySize - 1
//     loopIterations += 1

if barstate.islast
    label.new(bar_index, high,
        "Loop ran " + str.tostring(loopIterations) + " times\n" +
        "Array size: " + str.tostring(array.size(values)))
4. 重绘检测器
// Detect potential repainting
var bool repaintDetected = false
var float previousValue = na

if not barstate.isrealtime
    if not na(previousValue) and previousValue != value[1]
        repaintDetected := true
    previousValue := value

if barstate.islast and repaintDetected
    label.new(bar_index, high * 1.1, "⚠️ REPAINTING DETECTED",
        style=label.style_label_down,
        color=color.red,
        textcolor=color.white)
5. NA 值跟踪器
// Debug na propagation
debugNa() =>
    result = "NA Debug:\n"
    result += "value1: " + (na(value1) ? "NA ❌" : "OK ✓") + "\n"
    result += "value2: " + (na(value2) ? "NA ❌" : "OK ✓") + "\n"
    result += "result: " + (na(result) ? "NA ❌" : "OK ✓")
    result

if barstate.islast
    label.new(bar_index, low, debugNa(),
        style=label.style_label_up,
        color=color.orange)
6. Security 函数调试器
// Debug request.security() calls
[htfClose, htfTime] = request.security(syminfo.tickerid, "D",
    [close, time],
    lookahead=barmerge.lookahead_off)

if barstate.islast
    secDebug = "HTF Debug:\n"
    secDebug += "Close: " + str.tostring(htfClose, "#.##") + "\n"
    secDebug += "Time: " + str.format_time(htfTime, "yyyy-MM-dd") + "\n"
    secDebug += "Current TF: " + timeframe.period
    label.new(bar_index, high, secDebug)
7. 输入激活状态调试器(2025 年 7 月)
// Debug conditional inputs
showAdvanced = input.bool(false, "Show Advanced")
advValue = input.int(10, "Advanced Value",
    active=showAdvanced,  // July 2025 feature
    tooltip="This input is conditional")

if barstate.islast
    label.new(bar_index, high,
        "showAdvanced: " + str.tostring(showAdvanced) + "\n" +
        "advValue: " + str.tostring(advValue) + "\n" +
        "(advValue active: " + str.tostring(showAdvanced) + ")")

UDT 调试技巧

1. UDT 状态检查器
// Debug UDT field values with a method
type Regression
    int startTime
    int endTime
    int startBarIndex
    int endBarIndex
    float slope
    float intercept
    line centerLine = na

// Add debug method to UDT
method debug(Regression this, int yPos) =>
    if barstate.islast and not na(this.startTime)
        debugText = "Regression Debug:\n"
        debugText += "startTime: " + str.format_time(this.startTime, "MM-dd HH:mm") + "\n"
        debugText += "endTime: " + str.format_time(this.endTime, "MM-dd HH:mm") + "\n"
        debugText += "startBar: " + str.tostring(this.startBarIndex) + "\n"
        debugText += "endBar: " + str.tostring(this.endBarIndex) + "\n"
        debugText += "slope: " + str.tostring(this.slope, "#.######") + "\n"
        debugText += "intercept: " + str.tostring(this.intercept, "#.##") + "\n"
        debugText += "hasLine: " + (na(this.centerLine) ? "NO ❌" : "YES ✓")
        label.new(bar_index, yPos, debugText,
            style=label.style_label_left,
            color=color.blue,
            textcolor=color.white)

// Use it
if debugMode
    myRegression.debug(high)
2. UDT 数组检查器表格
// Debug all items in a UDT array
var table udtTable = table.new(position.bottom_left, 6, 12,
    bgcolor=color.new(color.black, 80))

if debugMode and barstate.islast
    // Header row
    table.cell(udtTable, 0, 0, "#", text_color=color.gray)
    table.cell(udtTable, 1, 0, "StartTime", text_color=color.gray)
    table.cell(udtTable, 2, 0, "EndTime", text_color=color.gray)
    table.cell(udtTable, 3, 0, "Slope", text_color=color.gray)
    table.cell(udtTable, 4, 0, "HasLine", text_color=color.gray)

    // Use for...in to iterate UDT array
    row = 1
    for [idx, reg] in regressions
        if row < 11  // Limit rows
            table.cell(udtTable, 0, row, str.tostring(idx), text_color=color.white)
            table.cell(udtTable, 1, row, str.format_time(reg.startTime, "MM-dd"), text_color=color.yellow)
            table.cell(udtTable, 2, row, str.format_time(reg.endTime, "MM-dd"), text_color=color.yellow)
            slopeColor = reg.slope >= 0 ? color.green : color.red
            table.cell(udtTable, 3, row, str.tostring(reg.slope, "#.####"), text_color=slopeColor)
            lineStatus = na(reg.centerLine) ? "❌" : "✓"
            table.cell(udtTable, 4, row, lineStatus, text_color=color.white)
            row += 1
3. Time 与 bar_index 对比调试器
// Debug time-based coordinates vs bar_index
if debugMode and barstate.islast
    currentTimeFormatted = str.format_time(time, "yyyy-MM-dd HH:mm")
    barsAgo = bar_index - startBarIndex

    debugText = "Coordinate Debug:\n"
    debugText += "Current bar_index: " + str.tostring(bar_index) + "\n"
    debugText += "Current time: " + currentTimeFormatted + "\n"
    debugText += "─────────────────\n"
    debugText += "Start bar_index: " + str.tostring(startBarIndex) + "\n"
    debugText += "Start time: " + str.format_time(startTime, "yyyy-MM-dd HH:mm") + "\n"
    debugText += "Bars ago: " + str.tostring(barsAgo) + "\n"
    debugText += "─────────────────\n"

    // Check if bar_index would hit 5000 limit
    if barsAgo > 5000
        debugText += "⚠️ WOULD EXCEED 5000 BAR LIMIT!\n"
        debugText += "Use xloc.bar_time instead\n"
    else
        debugText += "✓ Within bar_index limit\n"

    label.new(bar_index, high * 1.02, debugText,
        style=label.style_label_down,
        color=barsAgo > 5000 ? color.red : color.green)
4. 绘图对象状态调试器
// Debug whether UDT drawing objects exist
method debugDrawings(Regression this) =>
    status = "Drawing Status:\n"
    status += "centerLine: " + (na(this.centerLine) ? "NULL ❌" : "EXISTS ✓") + "\n"
    status += "upperLine: " + (na(this.upperLine) ? "NULL ❌" : "EXISTS ✓") + "\n"
    status += "lowerLine: " + (na(this.lowerLine) ? "NULL ❌" : "EXISTS ✓") + "\n"
    status += "fill: " + (na(this.fill) ? "NULL ❌" : "EXISTS ✓")
    status

if debugMode and barstate.islast
    for [i, reg] in regressions
        if i == 0  // Just debug first one
            label.new(bar_index, low, reg.debugDrawings(),
                style=label.style_label_up,
                color=color.purple)
5. UDT 创建流程调试器
// Track when UDTs are created
var int udtCreationCount = 0
var array<string> creationLog = array.new<string>()

// When creating new UDT
if shouldCreateNew
    udtCreationCount += 1
    logEntry = "Created #" + str.tostring(udtCreationCount) + " at bar " + str.tostring(bar_index)
    array.push(creationLog, logEntry)

    newReg = Regression.new(
        startTime = time,
        startBarIndex = bar_index,
        // ...
    )
    array.push(regressions, newReg)

// Display creation log
if debugMode and barstate.islast
    logText = "UDT Creation Log:\n"
    logText += "Total created: " + str.tostring(udtCreationCount) + "\n"
    logText += "─────────────────\n"

    // Show last 5 entries using for...in
    startIdx = math.max(0, array.size(creationLog) - 5)
    for [i, entry] in creationLog
        if i >= startIdx
            logText += entry + "\n"

    label.new(bar_index - 10, high, logText,
        style=label.style_label_right,
        color=color.teal)
6. xloc.bar_time 与 xloc.bar_index 验证器
// Verify time coordinates work correctly
if debugMode
    // Draw test lines with both methods
    if barstate.islast
        testStartBar = bar_index - 100
        testStartTime = time[100]

        // bar_index method (may fail if >5000 bars back)
        testLine1 = line.new(testStartBar, close[100], bar_index, close,
            xloc=xloc.bar_index,
            color=color.red,
            width=2)

        // bar_time method (unlimited lookback)
        testLine2 = line.new(testStartTime, close[100] * 0.99, time, close * 0.99,
            xloc=xloc.bar_time,
            color=color.green,
            width=2)

        label.new(bar_index, low * 0.98,
            "Red: xloc.bar_index\nGreen: xloc.bar_time\n(Should overlap if working)",
            style=label.style_label_up,
            color=color.yellow)

常见问题与解决方案

1. 无限循环(2025 年 3 月)

**症状:**脚本卡住或超时

// ❌ CAUSE: Dynamic boundary grows during loop
for i = 0 to array.size(arr)
    array.push(arr, i)

// ✅ FIX: Cache boundary
size = array.size(arr)
for i = 0 to size
    array.push(arr, i)
2. 意外的 NA 值

**症状:**计算意外返回 na

// Debug NA chain
if na(finalResult)
    // Check each component
    if na(input1)
        label.new(bar_index, high, "input1 is NA")
    else if na(intermediate)
        label.new(bar_index, high, "intermediate is NA")
    else
        label.new(bar_index, high, "Unknown NA source")
3. Security 函数问题

**症状:**从更高时间周期获取的值不正确

// ✅ Correct security() usage
htfValue = request.security(syminfo.tickerid, "D", close,
    gaps=barmerge.gaps_off,
    lookahead=barmerge.lookahead_off)  // Prevent future leak
4. 历史数据与实时数据不匹配

**症状:**策略在回测中有效,实盘中却失效

// Debug historical vs realtime
if barstate.islast
    label.new(bar_index, high,
        "ishistorical: " + str.tostring(barstate.ishistory) + "\n" +
        "isrealtime: " + str.tostring(barstate.isrealtime) + "\n" +
        "isconfirmed: " + str.tostring(barstate.isconfirmed))
5. 字符串过长(2025 年 8 月之前)

**症状:**字符串截断错误

// August 2025: Limit increased to 40,960 characters
// If still hitting limits, chunk the string
longString = ""
for i = 0 to 100
    if str.length(longString) < 40000  // Leave buffer
        longString += "data" + str.tostring(i) + "\n"

调试模式实现

// Master debug toggle
debugMode = input.bool(false, "🔧 Debug Mode", group="Debug")

// Conditional debug elements
var table debugTable = na
if debugMode
    debugTable := table.new(position.top_right, 2, 5,
        bgcolor=color.new(color.black, 80))

if debugMode and barstate.islast
    table.cell(debugTable, 0, 0, "Debug Info", text_color=color.white)
    // ... add debug info

// Debug plots (only when debug mode on)
plot(debugMode ? debugValue : na, "Debug Value", color.yellow)

调试工作流

  1. 识别症状

    • 编译错误?
    • 运行时错误?
    • 值不正确?
    • 性能问题?
  2. 检查 2025 年重大变更

    • For 循环边界(2025 年 3 月)
    • 行续接规则
    • 买卖价是否可用
  3. 添加调试点

    • 在关键计算处添加标签
    • 使用表格监视变量
    • 绘制中间值
  4. 跟踪执行过程

    • 跟踪计算流程
    • 检查条件判断
    • 监视状态变化
  5. 测试边界情况

    • 第一根 K 线(bar_index == 0)
    • NA 值
    • 实时数据与历史数据
    • 不同时间周期
  6. 清理

    • 将调试代码放在 if debugMode 中
    • 或在发布前删除

快速参考:错误消息

错误可能原因修复方法
"end of line without line continuation"三元表达式被拆分或缩进错误将三元表达式保持在一行,使用 5 个空格缩进
"Cannot use 'plot' in local scope"在 if/for/函数中调用 plot()使用 plot(condition ? value : na)
"Loop too long"for 循环中的动态边界缓存边界或使用 for...in
"String is too long"字符串超过 40,960 个字符分块处理字符串或减少内容
到处都是 "na"NA 传播在每个步骤添加 NA 检查
绘图未显示bar_index 回溯超过 5000 根 K 线改用 xloc.bar_time
UDT 线条为 na忘记在 line.new() 后赋值使用 := 将绘图对象赋给 UDT 字段

UDT 专用调试清单

  • 是否在事件发生时(而不是之后)捕获时间值?
  • 是否同时存储了 time 和 bar_index(分别用于绘图和计算)?
  • 绘图对象是否使用 xloc.bar_time?
  • 是否在调用 draw() 之前调用了 delete()?
  • UDT 方法赋值时是否使用 this.field :=?
  • 是否使用 for...in 遍历 UDT 数组?

TradingView 的环境并不透明。务必进行全面测试,并提供多种调试方法。

评论

支持 Markdown