Pine Script via Claude
Automate your trading without writing code
90% of retail traders have a chart-reading routine that works — but they can't articulate it as if-then rules. Claude solves that. You describe the setup in plain English, it spits out Pine Script v6.
By the end of this post you'll be able to take any setup you can describe and turn it into a strategy you can backtest in TradingView and route to TVSBot for live execution.
Why Claude, not ChatGPT or Copilot?
I've shipped Pine with all three. Real differences:
- Copilot: Built for autocomplete, not from-scratch generation. Great at finishing the line you're typing. Ask it to generate a strategy from a verbal description and you'll often get an indicator instead of a strategy.
- ChatGPT: Capable but tends to mix syntax from different Pine versions. You have to pin "Pine Script v6" in the prompt and confirm the output starts with
//@version=6or TradingView's compiler rejects it. - Claude: Most reliable on structured code with long context. You can dump the full strategy spec, risk rules, TVSBot JSON schema, and TradingView Pine doc snippets in one shot — Claude keeps the whole picture coherent. The free tier (Sonnet 4.5) handled all 5 example prompts in this post first-try.
The three-step pipeline
Mental model first:
- Strategy description → Claude prompt (the 5-section template below).
- Claude → Pine Script v6 with
strategy()order commands (no JSON and no webhook secret inside the Pine). - Pine → TradingView backtest. Paste into Pine Editor → Add to chart → check Strategy Tester (Net Profit, Max Drawdown, Win Rate).
- Alert + TVSBot webhook. Chart clock → Add Alert → condition = your strategy → Message box → paste the single-line JSON from the "TV template" button on your TVSBot strategies page (a strategy() script's alerts fire on order fills) → Notifications tab → paste the TVSBot webhook URL. Your webhook secret goes only in this alert dialog.
You never write Pine yourself.
The 5-section prompt template
Every good Pine prompt has these 5 sections. Drop any one and Claude has to guess; guess wrong and you burn 3–4 turns iterating:
[1. Who I am and what I trade]
I trade BTC perpetuals on the 1-hour chart with USD 10K on
Binance USDT-M Futures.
[2. Entry conditions]
Go long when the close breaks the prior 20-bar high
(excluding the current bar) AND RSI(14) > 50.
[3. Exit conditions]
Close when price breaks the prior 10-bar low. Or when price
retraces ATR(14) × 2 from the post-entry high.
[4. Risk rules]
Stop = entry-bar low minus ATR(14) × 0.5.
Max risk per trade = 1% of account.
Max one position at a time, no pyramiding.
[5. Output spec]
Output a complete Pine Script v6 strategy(). Don't add alertcondition()
(alertcondition() is for indicator() scripts only — a strategy() script's
alerts fire on order fills).
Don't write alert(), JSON, or a webhook secret in the Pine code.
I'll paste TVSBot's "TV template" single-line JSON into the TradingView
alert dialog, in this format:
{"secret":"<your webhook secret>","strategy":"<your TVSBot strategy name>","action":"{{strategy.order.alert_message}}","symbol":"BTCUSDT","exchange":"binance","market_type":"futures","qty_type":"margin_usdt","qty":50,"leverage":3}
I'll fill in the secret myself on TradingView.
How to fill action: with no exit logic at all, use {{strategy.order.action}}.
As soon as the strategy has an exit (strategy.close / strategy.exit / stop-loss / take-profit),
every strategy.entry / strategy.close / strategy.exit must set alert_message
(entries "buy" / "sell", exits "close" — action word only;
to flip, call strategy.close(..., alert_message="close") first and then strategy.entry in the new direction (its own "buy"/"sell"):
two fills = two alerts, so TVSBot fully closes before opening the new side — don't rely on an opposite-direction strategy.entry alone,
since TVSBot sizes every order by its own qty and a fixed-margin "sell" does not cleanly flip a long), and the action in the
alert-dialog JSON becomes {{strategy.order.alert_message}}
({{strategy.order.action}} only sends buy/sell, never close, so an exit would be treated as an ordinary trade and not close cleanly).Paste these 5 sections to Claude verbatim and 95% of the time you get usable Pine on the first try.
study() instead of indicator(), differentna handling).{{ticker}} (perpetuals become BTCUSDT.P). Easiest: copy the JSON from the "TV template" button on your TVSBot strategies page — secret, strategy name and exchange come pre-filled.5 strategies you can steal
Strategy 1: Donchian Breakout
Plain English: buy on a 20-bar high break, exit on a 10-bar low break. Stop 0.5×ATR below the entry bar low.
Pine Script v6 strategy().
Entry: close > ta.highest(high, 20)[1] (the prior bar's value, not current).
Exit: close < ta.lowest(low, 10)[1].
Stop loss: entry-bar low minus 0.5 * ta.atr(14).
Sizing: 100% equity (default_qty_value=100, default_qty_type=strategy.percent_of_equity).
No alertcondition(), and no alert() or JSON inside the Pine (alerts fire on order fills; I paste the JSON in the alert dialog).
Every strategy.entry / strategy.close / strategy.exit must set alert_message — the action word only, never a secret:
long entry alert_message="buy", short entry alert_message="sell", every exit that explicitly calls strategy.close / strategy.exit (stop, take-profit, flatten) alert_message="close".
To flip, first call strategy.close(..., alert_message="close"), then strategy.entry in the new direction (alert_message="buy"/"sell"): two fills = two alerts, so TVSBot fully closes first and then opens the new side.
Don't rely on an opposite-direction strategy.entry alone: TVSBot sizes every order by its own qty, so a fixed-margin "sell" does not cleanly flip a long (depending on position mode it may only reduce it partially, or reverse it with the wrong size).
Because {{strategy.order.action}} only ever sends buy/sell, never close,
the single-line JSON I paste into the alert dialog uses {{strategy.order.alert_message}} as its action:
{"secret":"<your webhook secret>","strategy":"<your TVSBot strategy name>","action":"{{strategy.order.alert_message}}","symbol":"BTCUSDT","exchange":"binance","market_type":"futures","qty_type":"margin_usdt","qty":50,"leverage":3}
pyramiding=0, process_orders_on_close=true.Why this is a good first strategy: the logic is two lines. Backtests on BTC 1H over the last year tend to land at 35–45% win rate with R:R 1:3+, net positive.
Strategy 2: EMA Cross
Buy when EMA20 crosses above EMA60, short the inverse. Stop at the slow EMA.
Pine v6 strategy().
Long entry: ta.crossover(ta.ema(close, 20), ta.ema(close, 60)).
Short entry: ta.crossunder(ta.ema(close, 20), ta.ema(close, 60)).
Stop: 0.3% below ema60 for longs, mirror for shorts.
Take profit: 1:2 risk/reward (target = entry + risk*2).
pyramiding=0. On a reverse signal, close first and then reverse (see below).
No alertcondition(), and no alert() or JSON inside the Pine (alerts fire on order fills; I paste the JSON in the alert dialog).
Every strategy.entry / strategy.close / strategy.exit must set alert_message — the action word only, never a secret:
long entry alert_message="buy", short entry alert_message="sell", every exit that explicitly calls strategy.close / strategy.exit (stop, take-profit, flatten) alert_message="close".
To flip, first call strategy.close(..., alert_message="close"), then strategy.entry in the new direction (alert_message="buy"/"sell"): two fills = two alerts, so TVSBot fully closes first and then opens the new side.
Don't rely on an opposite-direction strategy.entry alone: TVSBot sizes every order by its own qty, so a fixed-margin "sell" does not cleanly flip a long (depending on position mode it may only reduce it partially, or reverse it with the wrong size).
Because {{strategy.order.action}} only ever sends buy/sell, never close,
the single-line JSON I paste into the alert dialog uses {{strategy.order.alert_message}} as its action:
{"secret":"<your webhook secret>","strategy":"<your TVSBot strategy name>","action":"{{strategy.order.alert_message}}","symbol":"BTCUSDT","exchange":"binance","market_type":"futures","qty_type":"margin_usdt","qty":50,"leverage":3}Heads up: EMA20/60 needs 1H or higher. On 5-minute charts the noise will whipsaw you.
Strategy 3: RSI Mean Reversion with trend filter
RSI(14) crosses back above 30 from below → buy. Exit at RSI 50. Mirror for shorts. Critical: only longs above EMA200, only shorts below.
Pine v6 strategy().
RSI period 14.
rsiLong = ta.crossover(ta.rsi(close, 14), 30) and close > ta.ema(close, 200)
rsiShort = ta.crossunder(ta.rsi(close, 14), 70) and close < ta.ema(close, 200)
Long entry: rsiLong
Short entry: rsiShort
Long exit: ta.rsi(close, 14) >= 50 OR close < ta.ema(close, 200)
Short exit: ta.rsi(close, 14) <= 50 OR close > ta.ema(close, 200)
Stop: fixed -1.5% (ranges don't have ATR-friendly volatility patterns).
No alertcondition(), and no alert() or JSON inside the Pine (alerts fire on order fills; I paste the JSON in the alert dialog).
Every strategy.entry / strategy.close / strategy.exit must set alert_message — the action word only, never a secret:
long entry alert_message="buy", short entry alert_message="sell", every exit that explicitly calls strategy.close / strategy.exit (stop, take-profit, flatten) alert_message="close".
To flip, first call strategy.close(..., alert_message="close"), then strategy.entry in the new direction (alert_message="buy"/"sell"): two fills = two alerts, so TVSBot fully closes first and then opens the new side.
Don't rely on an opposite-direction strategy.entry alone: TVSBot sizes every order by its own qty, so a fixed-margin "sell" does not cleanly flip a long (depending on position mode it may only reduce it partially, or reverse it with the wrong size).
Because {{strategy.order.action}} only ever sends buy/sell, never close,
the single-line JSON I paste into the alert dialog uses {{strategy.order.alert_message}} as its action:
{"secret":"<your webhook secret>","strategy":"<your TVSBot strategy name>","action":"{{strategy.order.alert_message}}","symbol":"BTCUSDT","exchange":"binance","market_type":"futures","qty_type":"margin_usdt","qty":50,"leverage":3}Case study: without the EMA200 filter, BTC 1H over the past year: 47% win rate, -3% net. With the filter: 51% win rate, +8.2% net. One filter line, entirely different strategy.
Strategy 4: Bollinger + MACD
Body closes above the upper BB AND MACD line above zero → long. Mirror for shorts on the lower band. Dual confirmation cuts most chop.
Pine v6 strategy().
BB(20, 2.0): destructure with [bbMiddle, bbUpper, bbLower] = ta.bb(close, 20, 2.0)
MACD(12, 26, 9).
Long: close > bbUpper AND MACD line > MACD signal AND MACD line > 0
Short: close < bbLower AND MACD line < MACD signal AND MACD line < 0
(Note: ta.bb() returns a [middle, upper, lower] tuple — middle comes first,
and it cannot be indexed, so it must be destructured.)
Stop: ta.atr(14) * 1.5
Exit: ATR trailing stop using ta.atr(14) * 3
No alertcondition(), and no alert() or JSON inside the Pine (alerts fire on order fills; I paste the JSON in the alert dialog).
Every strategy.entry / strategy.close / strategy.exit must set alert_message — the action word only, never a secret:
long entry alert_message="buy", short entry alert_message="sell", every exit that explicitly calls strategy.close / strategy.exit (stop, take-profit, flatten) alert_message="close".
To flip, first call strategy.close(..., alert_message="close"), then strategy.entry in the new direction (alert_message="buy"/"sell"): two fills = two alerts, so TVSBot fully closes first and then opens the new side.
Don't rely on an opposite-direction strategy.entry alone: TVSBot sizes every order by its own qty, so a fixed-margin "sell" does not cleanly flip a long (depending on position mode it may only reduce it partially, or reverse it with the wrong size).
Because {{strategy.order.action}} only ever sends buy/sell, never close,
the single-line JSON I paste into the alert dialog uses {{strategy.order.alert_message}} as its action:
{"secret":"<your webhook secret>","strategy":"<your TVSBot strategy name>","action":"{{strategy.order.alert_message}}","symbol":"BTCUSDT","exchange":"binance","market_type":"futures","qty_type":"margin_usdt","qty":50,"leverage":3}Strategy 5: Session filter (overlay on any strategy)
You only want trades during the Asia session, UTC 00:00–08:00 (= GMT+8 08:00–16:00). Skip the EU/US chop.
Add a session filter to my existing EMA Cross strategy:
- Use hour(time, 'UTC') to get the current UTC hour.
- Define inSession = hour >= 0 and hour < 8.
- Add "and inSession" to the entry conditions only.
- Open positions follow normal stop/take logic regardless of session.
- Don't change the strategy() declaration or the existing order logic.Session filters often spike win rate. Let Claude wire the timezones — it'll save you 20 minutes of timezone math.
Risk wording — 4 lines that decide ship vs trash
For Claude's Pine to be live-money-ready, these 4 lines must be in the prompt:
| Rule | Wording |
|---|---|
| Stop loss | Stop = entry ± ATR(14) × 1.5 |
| Position size | Max risk per trade = 1% of account |
| Max position | pyramiding=0, auto-flip on opposite signal |
| Alert payload | {secret, strategy, action, symbol, exchange, market_type, qty_type:"margin_usdt", qty, leverage} |
Translating trader-speak to Claude-speak
The hardest part for new traders: you KNOW when you'd buy, but you can't articulate it. Rosetta stone:
| Trader-speak | Claude prompt |
|---|---|
| "Big red candle" | (close - open) > ta.atr(14) × 1.0 AND close < open |
| "Bullish MA stack" | EMA(20) > EMA(50) > EMA(200) all simultaneously |
| "Breaks the neckline" | close > prior-N-bar high (N default 20) |
| "Pulls back to key support" | low touches prior-30-bar low ±0.5%; specify what "support" means (prior low / swing low / a specific MA) |
| "Volume + price up together" | close > close[1] AND volume > ta.sma(volume, 20) |
Real case study: "I trade news + breakouts" → automated strategy
A friend's actual workflow. He watches news, then buys when a big candle breaks recent highs. I asked: "what specifically?" He couldn't articulate. We sat down and decomposed:
- "Big candle" quantified: body (close - open) > prior 14-bar ATR × 1.5.
- "Breaks recent highs" quantified: bar high > max of prior 50-bar highs.
- "Buy in" quantified: enter at next bar open. No chasing.
- Stops added: entry-bar low minus 1×ATR. No fixed take profit — ATR×3 trailing stop.
We pasted that description to Claude. The output strategy, backtested on BTC 1H for 6 months:
Net Profit: +$4,820 (48.2% on $10K)
Total Trades: 31
Win Rate: 38.7% (12W / 19L)
Avg Win: +$543
Avg Loss: -$132
R:R: 4.1
Max Drawdown: -$1,210 (-12.1%)
Sharpe: 1.82Win rate isn't impressive but R:R does the work. Net positive. Better than his "news + vibes" workflow because now there's a rule. When it loses, he knows why.
Top 6 Claude-Pine failure modes
1. Asking for alertcondition() inside a strategy — the silent one
This is the only failure on this list that throws no error at all. alertcondition() works in indicators only. TradingView’s docs put it plainly: inside a strategy script it “will not cause a compilation error, but alerts cannot be created from them”. The script compiles, the backtest runs, and then the condition simply never appears in the Create Alert dialog — with nothing anywhere telling you why. A strategy's alerts fire on order fill events: in Create Alert pick your strategy as the condition and paste the "TV template" single-line JSON into the Message box, with action {{strategy.order.action}}. alert() also works, but its message lives in your Pine source, which would force your webhook secret into the code — not recommended. Full detail in AI-generated Pine to live trading.
2. Pine version syntax drift
Without an explicit "Pine Script v6" up top, Claude occasionally emits study() (removed in v5) or drops //@version=6. Always pin v6. v5 → v6 has breaking changes too — TradingView's own migration guide states that "the when parameter for order creation functions was deprecated in v5 and is removed in v6" — so mixed-version output fails to compile outright. The AI-generated Pine to live trading article has the full v5 → v6 change list.
3. Multi-line alert message → TV rejects
The JSON you paste into TradingView's alert dialog must be a single-line valid JSON string — a line break or a missing quote and the webhook answers 400. Safest: copy it from the "TV template" button on your TVSBot strategies page instead of asking Claude to rewrite it.
4. Missing ta. prefix
Pine v5 and later require ta. on built-ins. Claude sometimes emits ema(...) directly, which fails compilation with Could not find function 'ema'. Add "all built-ins must use the ta.* prefix" to the end of the prompt.
5. strategy() vs indicator()
For automation you need strategy(). indicator() can't call strategy.entry / strategy.close. Claude may default to indicator() unless you specify strategy() explicitly.
6. Repainting (most insidious)
If your entry uses the currently un-closed bar, signals flicker until the bar closes. Backtest looks suspiciously good because entries land at the bar's high. Fix: use [1] to reference the prior (closed) bar, or set process_orders_on_close=true on the strategy.
// Bad: uses unclosed current bar's high
entrySignal = high > ta.highest(high, 20)
// Good: locked to prior closed bar
entrySignal = high[1] > ta.highest(high[1], 20)Wiring to TVSBot — 3 steps
- Prepare the TVSBot alert JSON: copy it from the "TV template" button on your TVSBot strategies page (secret, strategy name and exchange pre-filled; pick
{{strategy.order.action}}as the action; if the strategy has exits, pick{{strategy.order.alert_message}}instead and make everystrategy.entry/strategy.close/strategy.exitin the Pine setalert_message("buy" / "sell" / "close")). For spot strategies, the exit alert_message must be "sell", not "close": close is a futures-only action. If you edit it yourself it needs at least secret, strategy, action, symbol, exchange, qty_type and qty. Write the symbol in exchange format (e.g. BTCUSDT), and make exchange match the exchange your API key is on. - Set the TradingView alert: chart top-right clock → Add Alert → Condition = your Pine strategy (a strategy() script's alerts fire on order fills) → paste the single-line JSON into the Message box. If you exit with
strategy.close/strategy.exit,{{strategy.order.action}}only sends buy/sell, never close — follow the last template on /docs/pine-templates and send close viaalert_message. - Paste the TVSBot webhook URL: Notifications tab → Webhook URL → URL from your dashboard → Create. Enter your webhook secret only in this alert dialog — never in the Pine source, never into Claude.
Advanced: turn an entire trading book into a Claude prompt
Some traders want to implement systems from books (Turtle Trader, Market Wizards). Pattern:
I have the Turtle System 2 specification:
- Entry: break above prior 55-day high (long) / below prior 55-day low (short)
- Exit: opposite 20-day break
- Stop: 2N rule (N = 20-day ATR), 2% account risk per unit
- Pyramiding: add 1 unit per 0.5N favorable move, max 4 units
- Position limits: 4 per market, 6 per correlated group, 12 directional
Write this as a Pine v6 strategy() for BTC/USDT 1D.
pyramiding=4. N = ta.atr(20). Per-unit risk = 2% account via
strategy.percent_of_equity. Keep inline comments referencing
the original Turtle rules so I can tune them later.Claude can translate book-level logic into code with comments — much faster than reading the book then learning Pine yourself.
FAQ
Q: Is the free tier enough?
A: For 3–5 strategies per day, yes. If you iterate hard (20 revisions of the same strategy in a day), you'll want Pro ($20/mo) or the API.
Q: What if Claude's strategy loses money in backtest?
A: Look at Strategy Tester first. If win rate < 30% and R:R < 1:2, the logic is broken — don't tweak code, redesign. If win rate > 40% but net negative, fees are eating you — add a filter to reduce trade count.
Q: Can I share my prompt with other traders?
A: Yes — actually the TVSBot marketplace is built for it. Publish your strategy and earn 70% of subscriber fees. See marketplace.
Q: Claude vs TradingView's built-in Pine Genie?
A: Pine Genie is convenient (in-IDE) but quota-limited. Claude is more flexible — you can dump the whole strategy + risk rules in one shot. In practice: Genie for small edits, Claude for from-scratch.
Get started
Wire your Claude-built Pine strategy to live trading. Signing up is free — building strategies and dry-run testing cost nothing; you only subscribe when you want live orders.
Start free