request.security lookahead 陷阱:回測漂亮實盤對不上
你把「4 小時 RSI 掉到 30 就在 1 小時做多」交給 AI,它回一支 Pine Script 策略,也就是 TradingView 圖表用的那套腳本語言。request.security 那行看起來乾淨,貼上圖表按下 Add to chart,回測第一秒就讓你僵在椅子上:一年勝率 92%、Sharpe 3.1、最大回撤 4%。
你把 alert 掛上、對到執行端的 webhook(也就是把 TradingView 的訊號打回自己伺服器的那條 HTTP 通道)、真金白銀開下去。三天內只進兩個訊號、兩個都反向;第五天回頭跑同一份腳本,權益曲線畫出來跟你上禮拜看到的不像。你檢查 alert、檢查 payload、檢查伺服器時間——全部沒事。
真正壞掉的地方,在 request.security() 那一行的 lookahead 參數上。它的行為跟你從名字直覺出來的相反。TradingView 官方 Pine Script v6 文件在這個參數的小節裡,正中央放了一段大寫警告——藏在英文文件深處,AI 生程式碼時不會替你翻譯出來。所以這篇不教你寫 Pine,而是給你三件事:出事背後的機制、回頭該問 AI 的問題,以及可以自己驗收的方式。
request.security() 用了 barmerge.lookahead_on、且 expression 沒配 [1] 這種歷史位移,那份高時間框架資料在歷史 K 棒上會「還沒發生就先看到」。回測讀到的是那根 HTF K 棒收盤後才知道的高、低、收盤,實盤時你拿不到。TradingView 官方在該參數小節裡把這種寫法的回測結果稱為「unrealistic」,並在腳本發布規範裡直接說「not allowed as script publications」。request.security 到底在替你做什麼——先把它拆成兩層
你在 1 小時圖表上寫 request.security(syminfo.tickerid, "240", close),直覺是「這是拿 4 小時的收盤」。這句字面沒錯,但省略了兩件事,出事的就是被省略的這兩件。
第一件:HTF 資料不是每根圖表 K 棒都會有新的
以官方文件的實際案例來說,在 1 分鐘圖上請求 1 小時資料,函式「只在覆蓋到 1 小時 K 棒開盤或收盤時刻的那幾根 1 分鐘 K 棒上」回傳新值。其他 1 分鐘 K 棒該拿到什麼,由 gaps 參數決定。你沒寫 gaps 就是預設值 barmerge.gaps_off,這時候函式在歷史 K 棒上用上一次已確認的值填空、在即時 K 棒上用最新的浮動值填空。
第二件:歷史 K 棒與即時 K 棒的行為官方明講不一樣
歷史 K 棒上,request.security() 只在對方 timeframe 的 K 棒確認收盤時回傳新的歷史值。即時 K 棒上,它會在每次圖表 K 棒更新時重新計算並回傳,直到對方 K 棒真的收盤才「commit」那個值。這代表你在圖表上看到的即時 4 小時收盤,是一個會隨 tick 變動的暫時性數字。
| 情境 | 函式回傳 | 有沒有 commit |
|---|---|---|
| 歷史 K 棒、HTF 已確認 | 該 HTF K 棒的最終 close | 有 |
| 歷史 K 棒、HTF 尚未新確認 | 上一次已確認的 close(gaps_off 預設) | 沿用先前的 commit |
| 即時 K 棒、HTF 未收盤 | 當下浮動的 close,逐 tick 重算 | 沒有——收盤才 commit |
| 即時 K 棒、HTF 剛剛收盤 | 那個 HTF K 棒的最終 close | 有 |
request.security() 在歷史/即時 K 棒的預設行為對照,來源:TradingView Pine Script v6 官方文件〈Other timeframes and data〉的 gaps 與 Historical and realtime behavior 兩節(查證日期 2026 年 8 月)。
把這兩件事放在一起看:回測用的是「歷史 K 棒 + HTF 已 commit 的值」。實盤在 HTF 沒收盤前用的是「浮動、會 rollback」的值。這已經是兩個很不一樣的訊號來源了。lookahead 陷阱是這個結構縫隙被再放大一層之後的產物。
lookahead 這個 flag 到底調的是什麼?答案跟直覺相反
lookahead 有兩個合法值:barmerge.lookahead_off(預設)與 barmerge.lookahead_on。名字給你的直覺是「要不要往前看」,看似只是效能或風格差別。
官方對這個參數的實際定義
當你在請求 HTF 資料時,lookahead 的值決定了「函式在歷史 K 棒上能不能取到那根 K 棒實際發生的時間點之後的值」——也就是這份資料在歷史上會不會帶有 lookahead bias。
更白話:lookahead_on 讓函式在歷史回放時,能在該 HTF K 棒還沒結束的那些子 K 棒上,直接讀到這根 HTF K 棒收盤之後才知道的最終值。
官方那段大寫警告,逐字放這裡
官方在同一節放了一段警告:「Programmers should exercise extreme caution when using lookahead in their requests, especially when requesting data from higher timeframes.」下面接了一段 Notice:用 lookahead 讓未來資料洩漏進歷史的腳本「are extremely misleading. As such, they are not allowed as script publications」,理由是「the retrieved data was not knowable at the time of each bar. Furthermore, the same behavior is impossible to reproduce on realtime bars」。
為什麼歷史 K 棒上你幾乎察覺不到,實盤才炸開
同樣一支腳本、兩段時間會出現完全不同的權益曲線——這件事本身就是這個 bug 的簽名。原因在於歷史與即時兩種狀態下,lookahead_on(沒加位移)產生的訊號來源根本不是同一個。
歷史狀態:策略正在讀「還沒發生」的收盤
策略在每根 1 小時 K 棒上,都能直接讀到那根 K 棒所屬 4 小時 K 棒的最終收盤——即使那根 4 小時 K 棒還沒結束。相當於你在 1 小時 K 棒的當下,知道 4 小時後才會定案的收盤。這種資訊優勢會系統性地把進場點壓在對的方向,回測畫出來的當然漂亮。
即時狀態:訊號來源被抽掉、還會被覆寫
那根 4 小時 K 棒還沒結束。TradingView 沒有時光機。就算你設了 lookahead_on,實盤能取到的最多是「當下浮動的 close」,這個值會隨每個 tick 變。而且腳本重載一次,先前的即時 K 棒就變成歷史 K 棒、又會被 lookahead_on 覆寫成該 HTF K 棒收盤後的最終值。這就是為什麼第五天回頭跑同一支腳本,畫出來的權益曲線跟上禮拜不一樣。
官方 lookahead 小節的示範腳本說明列了同一件事:無位移的 lookahead_on「repaint their results after the user reloads the script」。
這件事最容易被誤診成 webhook 或執行端的問題。你會反覆去查 alert 有沒有觸發、payload 有沒有送到、伺服器有沒有回 200。那些查完全部沒事——因為 alert 邏輯是照策略講的去算的,只是策略在實盤看到的世界,跟它在回測看到的世界本來就是兩個世界。
| 回測(歷史 K 棒) | 實盤(即時 K 棒) | ||
|---|---|---|---|
| 訊號來源 | 該 HTF K 棒的最終 close(未來資料) | 當下浮動 close,逐 tick 變 | |
| 是否 repaint | 否(歷史被覆寫成最終值) | 是——腳本重載後前一段又被覆寫 | |
| 勝率/Sharpe | 系統性偏高 | 跟隨機接近,甚至更差 | |
| 你能查到的線索 | 幾乎沒有——都很正常 | 同一支腳本兩次回測結果不同 |
官方唯一標為「recommended」的寫法,以及為什麼它反直覺
這個部分反直覺,但一定要照做:要修好,是把 lookahead_on 留著,並在 expression 加一個 [1] 的歷史位移。
官方原文:「The most reliable approach to achieve non-repainting results is to use an expression argument that only references past bars (e.g., close[1]) while using barmerge.lookahead_on as the lookahead value.」
為什麼是「保留 on、加 [1]」而不是直接關掉
關掉之後你會拿到「上一個已確認 HTF K 棒的收盤,但值的更新時間跟 HTF 收盤時刻對齊」。這在歷史 K 棒是合理的,但你會發現腳本重載後,實盤那段的訊號位置跟歷史那段對不齊。
加 [1] + lookahead_on 的組合,做的是同一件事:永遠取「上一根已確認的 HTF K 棒」,而且不論歷史或即時,取值的時間點都對齊在新 HTF K 棒開盤的那一刻,前後一致。
這段官方文件也說得白:「applying an offset to the expression effectively prevents the requested data from repainting when the script restarts its executions and eliminates lookahead bias in the historical series」。同一頁的 htfPrices() library 範例,最後一行就是這個模式:
// TradingView 官方 htfPrices() 範例(Pine Script v6 官方文件〈In libraries〉節)
request.security(
tickerID,
timeframe,
[open[1], high[1], low[1], close[1]],
lookahead = barmerge.lookahead_on
)代價:訊號會慢一根 HTF K 棒
你所有 HTF 訊號現在會慢一根——原本用「這根 4 小時 K 棒的收盤」進場,改用「上一根 4 小時 K 棒的收盤」。這是修法要付的價,也是為什麼很多論壇文章不願意這樣寫:改完之後,回測數字會從那組讓你僵在椅子上的數字,掉到接近隨機。那個掉下來的差距,就是 lookahead bias 原本一直在幫你的地方。
expression 內部本身就是位移過的(哪怕是被 ta.wma(close, length)[1] 這種形式包住),lookahead_on 是可接受的。判準是「這個表達式在實盤時能不能真的取得到」,而不是「函式回傳值有沒有 na」。拿到 AI 給的多時間框架策略,直接貼這五題回去問
跟 AI 幫你生 Pine 策略那篇的做法一樣,先不要讓它改,先要它逐條回答。這五題設計成刻意讓它自曝——沒踩到這個陷阱的腳本第 1 題就答完了,其餘四題會很快;踩到的,第 3 題以下會開始搪塞。
這是一支 TradingView Pine Script v6 策略,會接 webhook 實盤下單。
在改任何一行之前,先逐條編號回答我下面五題,答完再給修正版。
1. 這支腳本裡的每一個 request.security() 呼叫,
請一支一支列出來,並標出:
(a) timeframe 參數的值
(b) expression 參數的值
(c) 是否有傳 lookahead,傳的是哪個值(barmerge.lookahead_on / off)
(d) expression 裡有沒有用歷史位移(例如 close[1]、high[1])
2. 對於任何一個 (c) 是 barmerge.lookahead_on 而 (d) 是「沒有」的呼叫,
說明它在歷史 K 棒與即時 K 棒上分別會回傳什麼值,
兩者是不是同一個時間點的資料。
3. 承第 2 題:如果我把這支腳本裝到一個沒動過的圖表上、按 Add to chart,
再重載一次,前一次回測畫面上的訊號位置會不會被覆寫?
會的話,是哪幾支呼叫造成的?
4. 我另外掛了一顆 alert 走 webhook 送到執行端。
在 HTF 那根 K 棒還沒結束的時候,我的執行端會不會收到訊號?
如果會,收到的當下拿到的訊號依據,跟這根 K 棒收盤後回頭看的依據是不是同一個?
5. 如果我把所有 request.security() 都改成「lookahead = barmerge.lookahead_on
加上 expression 內部所有值都補上 [1] 歷史位移」,
哪些訊號會消失?哪些訊號會晚一根 HTF K 棒才出現?請逐條列出。
[把你的策略貼在這裡]第 3 題是這一輪的核心。好的答案會直接說「會被覆寫,因為 barmerge.lookahead_on 沒加位移」。如果它回你「不會,Pine Script 是決定性的所以每次重載結果一樣」——這句字面沒錯(同一份程式碼 + 同一段歷史當然算出一樣的結果),但避開了「先前的即時 K 棒現在變成歷史 K 棒」這個實際會發生的事。這種回答讓你多一分懷疑。
第 5 題的用途是替你估修好後的訊號密度變化。如果它答不出來,那它多半也沒真的懂它自己剛剛寫了什麼——這時候別讓它改,把整支重寫。
對照你的回測與實盤,找出對應的機制
把上面幾件事串起來,多數「回測漂亮實盤對不上」的回報都能對應到下面幾種機制。這三個症狀只涵蓋 lookahead 陷阱與周邊近親——如果你的問題不在這幾條上,可能是 alert 沒有記憶、或 webhook 的 qty_type 傳錯。
症狀一:回測畫面漂亮,實盤第一週就對不上
request.security() 呼叫嗎?lookahead_on 保留、expression 加 [1]。修完回測數字會掉下來,掉下來的差距就是 bias 一直在幫你的地方。症狀二:同一支腳本,兩次回測結果不一樣
lookahead_on 覆寫成該 HTF K 棒收盤後的最終值——訊號位置移動了。官方文件在 lookahead 小節的示範腳本說明就寫這件事。改成推薦寫法後,重載也不會影響歷史那一段。症狀三:實盤訊號比回測還密,一根 K 棒進出好幾次
indicator() 還是 strategy() 宣告的?strategy() 是第一步。calc_on_every_tick 或 calc_on_order_fills;這兩個選項會讓策略在即時 K 棒也逐 tick 重算,等於把 indicator 的行為疊回來。上線前的自檢清單
在你把這支策略指向真錢之前,把下面這張清單走一遍。每一項都不需要你會寫 Pine——不是在瀏覽器裡改一個下拉選單,就是直接問 AI 一個明確的問題:
- 把整份腳本的每一個
request.security()呼叫列出來,逐支確認lookahead傳的是什麼值——沒傳就是預設的barmerge.lookahead_off。 - 凡是
lookahead = barmerge.lookahead_on的呼叫,expression裡的每個值都要有[1](或[n])位移。tuple 版本要每一項都補到,缺一個就跟沒補一樣。 - 用
strategy()宣告,不要用indicator()——多時間框架 + indicator 幾乎必然實盤跟回測對不上。 - 回測畫面截圖存一份,一週之後回頭跑同一支腳本、同一段區間,兩張圖比對——訊號位置或權益曲線有位移,是最直接的 repaint 證據。
- alert 的 Frequency 設成 Once Per Bar Close,或在程式碼裡用
barstate.isconfirmed擋住條件——這條不是 lookahead 陷阱本身,但常常疊在同一個症狀上(見 alert 沒有記憶那篇)。 - 回測結果放到執行端 dry-run 對一遍,別直接上真錢——這一步能抓到不只 lookahead 一種 bug。
誠實的一段:修完了不代表這支策略會賺
Lookahead bias 修好之後,回測數字掉到接近隨機,這件事本身不是 bug——那才是實盤能複製的數字。有些策略修完就變成沒有 edge,那不是修的方法錯了,是這支策略原本的 edge 只存在於 「知道未來 4 小時收盤」這個作弊條件下。我們沒辦法幫你決定要不要放棄這支策略。能做的是先讓數字誠實,你才有機會拿它做決定。
Pine Script 本身也有幾件事修不了:它無從得知你的 webhook 有沒有被下游收到、無從去重、無從追蹤實際下單結果。這些是執行端的職責範圍。TVSBot 的做法是把每一筆訊號連同原始 payload 與處理紀錄存下來、逐筆可查,讓你在對照回測與實盤時,能實際核對送出去的訊號長什麼樣子。但這裡要講清楚:那是事後查得到,不是自動修 lookahead。真正要修策略本身的 bias,只能回到上面那份 checklist。
常見問題
我用 lookahead_off(預設值),是不是就完全安全?
gaps 小節寫得清楚:即使 lookahead_off,在即時 K 棒上函式回傳的仍是「當下浮動的 close」——這個值會隨每個 tick 變。要完全消掉這一層,得把 expression 用 [1] 位移,或用 barstate.isconfirmed 擋住條件。為什麼很多論壇腳本寫 lookahead_on 卻不加 [1]?他們錯了嗎?
expression 本身已經在別的地方位移過(例如被 ta.wma(close, length)[1] 這種形式包住),那 lookahead_on 是可接受的。逐支呼叫檢查 expression 裡有沒有位移,是最保險的判準。舊腳本與非自有腳本
Pine v1 或 v2 抄下來的舊 security() 呼叫要怎麼處理?
security() 呼叫,除非 expression 已經有 [] 位移,都要當可疑處理。如果我不能改腳本(例如是別人賣我的策略),有辦法在執行端擋掉嗎?
下單函式與資料來源的關係
策略用 strategy.entry() 而不是自己組 alert message,能繞過這個問題嗎?
strategy.entry() 呼叫的觸發條件,仍是你腳本裡那些讀取 HTF 資料的 if 判斷。判斷本身用了洩漏未來的資料,下單指令就是照著洩漏來的訊號送出去。這個問題出在資料來源,跟你怎麼發訂單無關。Get started
TVSBot 把 TradingView 的 alert 轉成跨 7 家交易所的自動下單——非託管、用你自己的 API key,先 dry-run 對過再上真錢,逐筆訊號可 Replay 查證。lookahead 這種源頭 bug 得回到 Pine 修,但送出後每一筆訊號長什麼樣,我們幫你留紀錄。
免費開始使用