Pine Script

RSI 與 MACD 背離偵測:Pine Script 實作為何比肉眼難

2026-09-28·8 分鐘閱讀

你在圖上看到明顯的背離,往回翻歷史,五個裡有四個對。同樣的邏輯一寫成 Pine Script——也就是 TradingView 內建的策略腳本語言——貼上去跑,警報要嘛不響、要嘛在 K 棒還沒收就跳、要嘛比你眼睛慢五根 K 棒。

問題不在你,也不在 Pine 語法。是「背離」這件事在**事後看**跟**每根 K 棒即時判斷**兩個世界,本來就對不起來——人眼可以往回掃一整張圖挑轉折點,程式在當下這根 K 棒只知道「到目前為止」。這篇不重講背離的意義(RSI 與 MACD 兩篇教學裡有),也不寫「穩定的背離策略」——那需要回測與參數搜索,不是一篇文章能給的。只講三件事:人眼與程式之間差在哪、Pine 上最小可行的實作是什麼、以及 alert 送出去之後最容易踩到的陷阱。

先講結論
一:`ta.pivothigh(source, left, right)` 需要「右邊 right 根 K 棒」都比 pivot 低才會確認,所以你在當下這根 K 棒看到的 pivot 永遠是 right 根前的事——right=5 就是延遲 5 根 K 棒。二:即時的 RSI/MACD 讀數在 K 棒收之前會持續變,把訊號綁在 `barstate.isconfirmed` 之後才判定,不然警報會在同一根 K 棒響好幾次還互相矛盾。三:「上一個 pivot」不會自動被 Pine 記住,你要自己用 `var` 存起來,不然每根 K 棒都在跟自己比。

落差一:你回頭看的「前一個高點」,程式當下不知道

人眼判定背離的方式,是往圖表左邊掃、挑一個看得出來的高點,然後看今天的高點有沒有超過、RSI 有沒有跟上。這個動作對程式來說有一個很不友善的假設:你已經知道那個高點是高點了。

程式只能用 ta.pivothigh(source, leftBars, rightBars) 找轉折點,而這個函式的語意是「左邊 leftBars 根都比它低、且右邊 rightBars 根也都比它低才算」。這意味著它一定要等到 rightBars 根 K 棒之後才有辦法確認,也就是你在當下這根拿到的 pivot 位置,其實是 rightBars 根之前發生的事——這不是 bug,是設計。

5 根
right=5 時,pivot 確認的延遲根數
na
尚未確認的當下 K 棒回傳值
2 個
背離最少需要的 pivot(上一個 + 這一個)

也就是說:你想比較「這一個 pivot high 有沒有低於前一個 pivot high」,最快的時機是這一個 pivot 剛剛被確認的那一根 K 棒——比訊號真正的高點晚 rightBars 根。left 與 right 通常設一樣(3~5 是常見值),愈大延遲愈久、雜訊也愈少。

落差二:barstate.isconfirmed 之前的訊號不算數

RSI、MACD 這些指標在當下這根 K 棒的值,會隨著 close 一路變到收盤。K 棒還沒收之前你看到的「RSI 68」下一秒可能變 71 或 65——同一根 K 棒上,你的背離判定會來回觸發好幾次,而每一次都會送 alert 出去。這叫 repainting,是 Pine 教學裡反覆講、實作上仍反覆踩的陷阱。

處理方式只有一種:把「這一次條件是否成立」的判定綁在 barstate.isconfirmed 之後(表示這根 K 棒已收盤、值不會再變)。或者更保守,用上一根 K 棒的值 close[1]、rsi[1] 來判斷——寫進 Pine 就是 and barstate.isconfirmed 或 and not barstate.isrealtime。

這件事跟 Pine Script 的 Alert 沒有記憶 那篇講的是同一層問題:Pine 的每根 K 棒都是一次獨立的計算,你如果不明確告訴它「等這根 K 棒穩定下來再說」,它會用中間值送出訊號,而中間值幾秒後就不在了。

落差三:「上一個 pivot」不會自動記在變數裡

ta.pivothigh() 每次呼叫都告訴你「當下這根有沒有一個新確認的 pivot」——有就回傳 pivot 值,沒有就回 na。它不會替你存「上一個 pivot 是多少、在哪一根」。

要判斷背離,你至少需要兩個 pivot high 拿來比較:上一個與這一個。Pine 提供兩種寫法:

  • var 變數自己存:宣告 var float lastHigh = na,每次拿到新的 pivot 就把舊值搬走、把新值塞進來
  • ta.valuewhen(cond, source, occurrence):直接問「上上次 cond 成立時 source 是多少」

兩種都可以,ta.valuewhen 寫起來短但比較慢,var 版本直觀而且好除錯。下一節就用 var 版本示範,跟 tvsbot 既有的 RSI 指標完整教學 與 MACD 指標完整教學 兩篇附的程式碼一致,方便你直接搬過去接。

最小可行實作:RSI 多頭背離偵測

先看要偵測的形狀:價格創新低,但 RSI 沒有創新低——這是多頭背離(bullish divergence),暗示下跌動能在減弱。

先把輸入與 RSI 值準備好:

pine
//@version=5
        indicator("RSI Bullish Divergence (minimal)", overlay=true)

        rsiLen = input.int(14, "RSI length")
        left   = input.int(5,  "Pivot left bars")
        right  = input.int(5,  "Pivot right bars")

        rsi = ta.rsi(close, rsiLen)
        

left 與 right 通常取同一個值——這裡都是 5,代表 pivot 需要左右各 5 根 K 棒都比它高/低才算,換句話說會延遲 5 根 K 棒。接著找 pivot 並把上一個記起來:

pine
// pivot low 需要 right 根 K 棒後才確認;這裡拿到的是 right 根之前的低點
        pl     = ta.pivotlow(low, left, right)
        pl_rsi = ta.pivotlow(rsi, left, right)

        // 把上一個確認的 pivot 存起來,準備跟這一個比對
        var float prevPriceLow = na
        var float prevRsiLow   = na
        

var 是 Pine 的持久變數宣告,只在第一根 K 棒初始化一次,之後每根 K 棒的賦值都會覆寫上去。最後判斷背離並畫上去:

pine
bullishDiv = false
        if not na(pl) and not na(pl_rsi) and not na(prevPriceLow) and not na(prevRsiLow)
            // 價格更低、RSI 反而更高 → 多頭背離
            bullishDiv := pl < prevPriceLow and pl_rsi > prevRsiLow

        // 這一根有新的確認 pivot,就把「上一個」的位置換成這一個
        if not na(pl)
            prevPriceLow := pl
        if not na(pl_rsi)
            prevRsiLow := pl_rsi

        // 只在 K 棒收盤後畫,避免中間值畫上去又消失
        plotshape(bullishDiv and barstate.isconfirmed, "Bull Div",
                 shape.triangleup, location.belowbar,
                 color.new(color.green, 0), size = size.small)
        

這段程式碼是**最小可行**,不是「可以直接拿去實盤」的版本。它會漏偵測、也會誤報——這是背離偵測本質上就有的問題,不是實作缺陷。放進帳戶之前要做的事,見下一節與最後的 FAQ。

三個實作陷阱:範例對照

寫背離腳本會踩的問題,八成落在這三格裡。左欄是天真作法,右欄是應該的作法。

情境常見的寫法(會壞)應該的寫法
找上一個高點用 high[10] 這種固定回看幾根 K 棒去猜ta.pivothigh 找確認 pivot,再用 var 記住上一個
K 棒未收就送訊號條件成立就送,沒有 barstate.isconfirmed條件外面套 and barstate.isconfirmed,同一根只算一次
alert 觸發次數用 alertcondition 預設會每次滿足都觸發alert() 搭配 alert.freq_once_per_bar_close,一根 K 棒只送一次

這張表是給你比對用的,不用背——關鍵是每一格都在解決同一個問題:**當下這根 K 棒的值會變,所以任何「跟過去比」的邏輯都必須等這根 K 棒穩定下來再判定**。這三個習慣任缺一個,你的策略回測看起來很漂亮,實盤 alert 就開始亂送。

alert 要送出去,就別用 alertcondition

alertcondition() 這個舊 API,也就是 Pine 早期給你「觸發警報」的老函式,要你在 TradingView 圖表上手動開一個對應的警報、選 condition、綁 webhook。webhook 就是把警報 payload 自動送到你自己伺服器的那條通道,訊息內容也是在警報設定裡填。

用它做背離偵測有兩個限制:訊息不能帶動態值(例如「這一次 RSI 比前一次高多少」);觸發頻率不是寫在 Pine 裡,而是要到 Create Alert 對話框選 Frequency 下拉——TradingView Help Center「Differences between alert frequencies」頁列出的四項字面是 Once only/Once per bar/Once per bar close/Once per minute or every time。

新一點的做法是 alert():訊息可以在 Pine 裡即時組出字串,頻率也直接用 Pine 常數指定(例如 alert.freq_once_per_bar_close)。要接自動下單建議直接用它:

pine
if bullishDiv and barstate.isconfirmed
            msg = "RSI bullish divergence | price=" + str.tostring(pl) +
                  " prev=" + str.tostring(prevPriceLow) +
                  " rsi=" + str.tostring(pl_rsi, "#.##")
            alert(msg, alert.freq_once_per_bar_close)
        

`alert.freq_once_per_bar_close` 意思是「同一根 K 棒最多送一次,而且只在收盤那一刻送」。這是背離訊號能做到的最保守設定——實盤要接執行端([TradingView Webhook 完整教學](/blog/tradingview-webhook-tutorial) 那篇有講怎麼設)之前,就先跑這個設定觀察兩、三天。

誠實的一段:背離不是必然反轉,程式偵測還多一層滯後

背離的統計意義是動能衰竭的一種樣式,不是價格反轉的必然前兆。連續的背離可能維持很久——RSI 一直背離、價格繼續往目標方向走——這種情況叫「hidden divergence」還是「failed divergence」,看你翻的是哪本書。

程式偵測到「一個確認的背離 pivot」通常比人眼晚 3~8 根 K 棒,而背離做為訊號本來就是滯後的。這兩個滯後疊起來:

  • 人眼看到 pivot 到程式確認:延遲 right 根 K 棒(5 根很常見)
  • Pine 送出 alert 到執行端收到 webhook:再多幾秒到十幾秒
  • 執行端市價下單到成交:再一次到數次滑價

你收到訊號的時候,價格可能已經開始反轉、也可能繼續走。任何自動化背離策略都要有另一個確認條件(例如均線交叉、成交量放大)來過濾,這一步不加,背離會變成「風險最大的雜訊來源」而不是訊號。

這篇沒有給你回測結果、也沒有給你參數建議,因為我們沒有跑過各交易對、各時間框架的窮舉——寫出來也是拍腦袋的數字。你要自己拿上面的最小可行實作在 TradingView Strategy Tester 上跑,並且比較「加背離濾網 vs 不加」在同一個標的的長期期望值差多少。有差再往下走,沒差就退回 RSI/MACD 交叉那些更粗但更穩的訊號。

收到背離訊號之後,要不要進場

單一個背離訊號本身不足以做決策,這是決策樹的第一問;下面每一支都能整段拿掉不影響流程,但少任何一支勝率都會惡化。

1
訊號來源是不是收盤後(barstate.isconfirmed)?
不是 →是即時中間值,等這根 K 棒收再看。中間值下一秒可能消失
是 →往下一步
2
大時間框架的方向對嗎(例如日線 RSI 過 50)?
不對 →逆勢背離的假訊號比例很高,通常直接跳過
對 →往下一步
3
有第二個確認條件嗎(成交量、均線、更小時間框架轉折)?
沒有 →只有一個背離就進場,長期期望值通常打平或負;把它當觀察不當進場
有 →照你的策略進場,用背離的 pivot 當初始停損參考

常見問題

參數與時間框架

left 與 right 要設多少?
常見範圍 3~10。愈小訊號愈多也愈雜、延遲愈短;愈大訊號愈乾淨、但會漏掉快速轉折。日線常用 5,1 小時以下常用 3。沒有正確答案,一律在同一標的的 Strategy Tester 上比兩、三組。
可以在 1 分鐘框架上偵測背離嗎?
技術上可以,實務上訊號比雜訊多。5 分鐘 RSI 一天可能背離 20 次,1 分鐘會更嚴重。要用就搭配大時間框架 RSI 過濾,只在大週期方向對的時候動作。

訊號與執行

MACD 背離跟 RSI 背離要選一個嗎?
兩個都可以,機制上一樣——把 ta.pivotlow(rsi, left, right) 換成用 ta.macd() 出來的 MACD line 再取 pivot。MACD 通常比 RSI 早出一點,但假訊號也多。有人兩個同時背離才進場。
alert 送出去之後,執行端要注意什麼?
兩件事。TradingView 的 webhook 有延遲(幾秒到十幾秒),加上背離本身滯後,實際成交價不會是圖上那個 pivot。同一根 K 棒的訊號可能被執行端去重擋掉——這是好事,跟 alert.freq_once_per_bar_close 疊起來剛好雙重保險。
ta.valuewhen 版本會比較好嗎?
寫起來短,但邏輯較繞:ta.valuewhen(not na(pl), pl, 1) 意思是「上一次 pl 不是 na 的時候的值」。要「上一個」而非「這一個」,off-by-one 錯很難查。除錯階段用 var 版本比較好追。

Get started

想把今天學到的東西自動化跑起來?

把 RSI/MACD 背離的 alert 接上自動下單——用你自己的 API key,先 dry-run 觀察兩天再開實盤,風控自己設,收到的每一筆訊號在 dashboard 的 signals 頁逐筆可查。

免費開始使用