You generated a Pine Script strategy with AI
Here’s what it takes to actually trade it
You described your idea to an AI, it handed back a block of Pine Script, and it compiled on the first try. That part is genuinely the easy part now. The part nobody tells you about is that code that compiles and code that trades are two different bars to clear, and Pine Script has a long list of rules that decide which side of that line you land on.
None of this is about whether the AI you used is any good — we haven’t tested that, and this post won’t either. It’s about the language and the platform underneath the code: if what got generated is an indicator() instead of a strategy(), if it declares a version but uses syntax from another one, or if it quietly leaks future data into a backtest, the code will still compile — it just won’t do what you think it does once it’s live. This post walks through those gaps in the order you’ll actually hit them.
The requirements block to paste on top of your request
Start here, because most of the rest of this post is explaining why each of these lines exists. Paste this above your own plain-language description of the strategy. Every item is a rule you can verify afterwards, and asking for a numbered reply is what makes that verification cheap:
Before you write anything, apply all of these and confirm each one by number
in your reply:
1. Declare the script with strategy(), not indicator(). I need order-fill
alerts, and placeholders with the "strategy" prefix only work in alerts
created from a strategy.
2. Put //@version=6 on the first line. Do not leave the version line out.
3. Use v6 syntax only: no "when =" argument on order functions (wrap the call
in an "if" block instead); every switch/if that returns a value must have a
default/else branch; cast explicitly with bool() where a bool is expected.
4. State explicitly what you set margin_long and margin_short to, and why.
5. If you use request.security() with lookahead = barmerge.lookahead_on, the
series must be offset by [1]. If the logic doesn't need lookahead, don't
use it at all.
6. Gate the entry and exit conditions on barstate.isconfirmed.
7. The alert message must be one single line of valid JSON, no line breaks,
with exactly these fields: [paste your receiver's field list]
Strategy logic: [describe your entries and exits in plain language]The reason for item 4 in particular — asking it to state the value rather than just set one — is that defaults changed between versions, and a value stated out loud is one you can argue with. The next few sections are the “why” behind each item, in the order you’ll hit them.
Gap one: did you get an indicator or a strategy?
Pine Script scripts fall into two declared types, and the difference isn’t cosmetic. TradingView’s reference manual defines them plainly: indicator() is “a declaration statement that identifies the script as an indicator,” while strategy() “designates the script as a strategy” and unlocks order placement. The official Strategies documentation is more specific still: strategies “simulate trades across historical and realtime bars” and can “place, modify, and cancel hypothetical orders,” capabilities an indicator simply does not have.
If you asked an AI for “a strategy that buys on RSI oversold,” there’s a plausible reading of that request that produces a script that plots RSI crossovers — technically correct, visually convincing, and structurally an indicator. It will compile cleanly. It will look right on the chart. And it has no strategy.entry() anywhere in it, because indicator() scripts don’t have access to that namespace at all.
| indicator() | strategy() | |
|---|---|---|
| Plots values on the chart | ||
| Simulates orders (strategy.entry/exit) | ||
| Backtest / Strategy Tester results | ||
| alertcondition() alerts work | ||
| Order-fill alerts with {{strategy.*}} data |
That last row is the one that actually breaks automations, and it’s worth quoting directly. TradingView’s official alert placeholder documentation splits placeholders into groups and states, in plain language:
That single sentence explains a failure mode that shows up constantly when people wire AI-generated code into a webhook: the alert message includes something like {{strategy.order.action}} or {{strategy.order.contracts}}, the alert fires without any error, and the placeholders come through as empty text — because the script is an indicator, so there is no order, no fill, and nothing for those placeholders to reference. TradingView’s UI doesn’t stop you from typing strategy placeholders into an indicator’s alert message. It just won’t fill them in.
If you need those order-fill placeholders, the official FAQ lays out the fix directly: replace the indicator() declaration with strategy(), then reuse the same trigger logic you already have (a crossover, a breakout condition) and add an order command — strategy.entry("Long", strategy.long) — to actually place the simulated trade the alert will later report on.
alertcondition() vs alert() — one more mix-up worth flagging
Separately from the strategy/indicator split: alertcondition() only works in indicators. TradingView’s docs say that placing it inside a strategy script “will not cause a compilation error, but alerts cannot be created from them.” The code runs fine; the alert option just never shows up in the “Create Alert” dialog. alert(), by contrast, works in both script types and can carry a dynamically built message.
Gap two: which Pine version did you actually get?
Pine Script is currently on v6, following v5 (October 2021) and v4 (June 2019). The version isn’t cosmetic — it’s a compiler instruction, and the official docs are explicit about what happens if you skip it:
No warning, no error — a script missing //@version= quietly compiles under version 1 rules, which is old enough to reject syntax you’d expect to just work. And even when the version line is present, the v5→v6 migration guide lists several changes that let old-style code compile under the wrong assumptions rather than fail outright. A few of the most common ones:
| Change | Older behavior | v6 behavior |
|---|---|---|
| switch / if with a unique-type result | No default/else needed; uncovered cases silently return na | Must include default/else or the script fails to compile |
| when parameter on order functions | strategy.entry("Long", strategy.long, when = cond) | Removed — must wrap in if cond instead |
| Default margin_long / margin_short | 0 — strategy never gets margin-called | 100 — orders needing more than available funds are rejected, and losses can trigger a margin call |
| int/float used where bool is expected | Implicitly cast to bool | Must be explicit: bool(x) |
The margin_long/margin_short default is the one that bites quietest: the same v5 code, recompiled with a v6 declaration and no other edits, can go from a backtest that never gets margin-called to one riddled with rejected orders and forced liquidations — same logic, different default. If an AI’s training data mixes v5 and v6 examples, generated code can plausibly land on either side of any of these rules without you noticing until the backtest — or the live alert — behaves differently than you expected.
So don’t audit this by reading the code. Ask for it: “What are margin_long and margin_short set to in this script? If they aren’t set explicitly, what are the defaults under the version it declares?” A reply that names both values and the declared version is one you can check against the table above in about ten seconds. A reply that says “the defaults are fine” hasn’t answered the question.
Gap three: does the backtest look too good to be true?
TradingView’s own documentation defines repainting plainly: “script behavior causing historical vs realtime calculations or plots to behave differently.” It’s worth pausing on the next line, because it reframes the whole topic: “more than 95% of indicators in existence exhibit some form of repainting behavior.” Repainting isn’t inherently a bug — it’s a spectrum, and the docs sort causes into rough tiers from “widespread and usually fine” up to “unacceptable.”
The unacceptable tier is the one that matters for an AI-generated strategy you’re about to trust with real orders: leaking future information into the past. The most common way this happens is request.security() called with lookahead = barmerge.lookahead_on but without the matching [1] offset. TradingView’s docs describe the consequence directly:
The docs even publish a worked example under a comment reading FUTURE LEAK! DO NOT USE! — the pattern is common enough that TradingView flags it by name:
// FUTURE LEAK! DO NOT USE!
htfClose = request.security(syminfo.tickerid, "D", close, lookahead = barmerge.lookahead_on)
// Correct: offset by [1] so only confirmed data is used
htfClose = request.security(syminfo.tickerid, "D", close[1], lookahead = barmerge.lookahead_on)This one is here as a shape to recognize, not something to memorize. You’re looking for two things on the same line: lookahead_on and a [1]. If the first appears without the second, you have a question to ask — and if you’d rather not eyeball it, “list every request.security() call and tell me, for each, whether lookahead is on and whether the series is offset by [1]” gets you the same answer as a table.
Get this wrong and the historical backtest sees prices before they “happened” — which is exactly why it can look unreasonably good. In real time, no future bars exist, so the same code behaves completely differently once it’s live. TradingView’s stance on this is unambiguous: “Using lookahead to produce misleading results is not allowed in script publications.”
The fix, per the same documentation, is to accept a small tradeoff: pair lookahead_on with [1], add and barstate.isconfirmed to trigger conditions, or set the alert frequency to trigger only on bar close. TradingView is candid that this costs you something: “You can’t have your cake and eat it too” — avoiding repainting means your signal fires a little later, always.
Gap four: turning it into an alert TradingView will actually fire
A strategy that backtests correctly still needs a correctly configured alert before anything reaches a webhook. Two settings matter most:
| Setting | What it means | Why it matters here |
|---|---|---|
| Alert type | alertcondition() vs alert() vs strategy order-fill event | Determines which placeholder set is valid in the message — mixing them produces empty fields, not errors |
| Frequency: Once per bar close | Waits for the bar to close before firing | The most direct fix for the repainting behavior above |
alert() call in the code builds the message itself, so you don’t retype it in the dialog. The alert_message= argument on an order function only surfaces when an order-fill event fires and the message uses {{strategy.order.alert_message}}. The Message field in the Create Alert dialog is the only place {{strategy.*}} placeholders work, and only when the alert came from a strategy. The click-by-click version — chart top-right clock, Add Alert, Condition = your strategy, Notifications tab, Webhook URL — is in Writing Pine Script With Claude.There’s a structural detail worth knowing before you rely on this for real money: when you create an alert from a strategy, TradingView copies the entire script to its servers at that moment. The official docs are explicit that this copy then “runs independently from the chart’s strategy in your browser, and changes to your chart’s strategy will have no effect on the operation of its copy running on our servers.” Go back and tweak the AI-generated code afterward, and the live alert keeps running the old version until you delete it and create a new one.
It’s also worth knowing that notifications only fire for realtime fills — “notifications are not sent for orders on historical bars” — and that a single alert is rate-limited to 15 triggers within any 3-minute window, past which TradingView stops it.
Gap five: the part no AI touches — webhook to your exchange
Everything above happens inside Pine Script, running on TradingView’s servers. None of it knows your exchange account exists. The strategy’s equity is a number you typed into initial_capital; TradingView’s own documentation calls the whole simulation a “broker emulator” that fills “hypothetical orders.” There is no reference in the language to your real API keys, your real balance, or what happens if an order gets rejected — because an alert, at the end of the chain, is just a message sent to a URL. What happens after that is entirely outside Pine Script and outside anything an AI writing Pine could have generated for you.
That gap — receiving the webhook, translating it into an authenticated exchange order, sizing the position, applying your own risk limits, and handling what happens when an order fails — is what a receiving service has to do.
It’s the piece TVSBot handles: a per-user webhook URL secured with an independent token plus a payload secret, exchange API keys stored encrypted with Fernet (AES-128-CBC with HMAC-SHA256), orders placed through your own account on any of 7 supported exchanges, and risk settings you configure yourself — per-trade caps, a maximum position count, a daily loss limit, a kill switch. New strategies have dry-run checked by default, so you can watch a signal process end-to-end against simulated funds first.
The audit prompt: paste the code back and ask this
Everything in the checklist below can also be asked as a question. This is the version to send when you’d rather have the answers listed out than go looking for them yourself — and holding back the rewrite is deliberate, because it shows you whether it actually read the script it’s about to change:
Here is a Pine Script I want to trade live. Don't rewrite it yet — answer these:
1. Is the declaration statement indicator() or strategy()? Quote the line.
2. What //@version= does it declare? If there isn't one, say so plainly.
3. What are margin_long and margin_short set to? If they aren't set, what are
the defaults under the version declared in point 2?
4. List every request.security() call and, for each, whether lookahead is on
and whether the series is offset by [1].
5. List every placeholder my alert message would need, and say whether each
one is valid for the script type you identified in point 1.
6. Is the alert message a single line of valid JSON with no line breaks?
Then give me a corrected version, and tell me what you changed.
[paste your script here]Point 5 is the one that catches the most common live failure — a strategy placeholder sitting in an indicator’s alert message, which fails silently rather than loudly.
Checklist: before you trust AI-generated Pine with real money
- Declaration statement: does it say
indicator()orstrategy()? If you need order-fill alerts, it has to bestrategy(). - Version line: is
//@version=present and set to the version you actually asked for? Missing it silently falls back to v1 rules. - v5/v6 defaults: check
margin_long/margin_short, anyswitch/ifwithout a default/else branch, and any leftoverwhen =parameter on order functions. - Lookahead pairing: any
request.security()call usinglookahead = barmerge.lookahead_onmust also offset the series by[1]. - Alert type match: confirm the placeholders in your alert message (
{{strategy.*}}vs{{plot_0}}-style) actually match whether you built an indicator or a strategy alert. - Frequency set to Once per bar close if you want the signal to reflect a confirmed bar rather than an in-progress one.
- Alert message is valid, single-line JSON matching whatever your webhook receiver expects to parse.
- Dry-run before real funds: run the full chain — alert to webhook to simulated order — before pointing it at live capital.
FAQ
Can AI write Pine Script that actually trades?
strategy() rather than an indicator(), whether the version syntax matches what was intended, and whether the logic avoids repainting. These are properties of the generated code you can verify yourself against TradingView’s documentation, not a claim about any particular AI tool.Why is my TradingView alert message missing the order details?
indicator() but the alert message uses strategy placeholders like {{strategy.order.price}}. TradingView’s own alert placeholder documentation states plainly that placeholders with the “strategy” prefix only work in strategy alerts — in an indicator alert they resolve to nothing, with no error shown anywhere.Do I need to know Pine Script to use AI-generated code live?
request.security() calls pair lookahead_on with a [1] offset. The checklist above covers what to look for.Why did my backtest look great but live results don't match?
request.security() call that leaks future prices into historical bars. TradingView’s docs note that over 95% of indicators repaint in some form, and the unacceptable form (future leaks) can make a backtest look far better than live trading will ever reproduce.Does Pine Script know my real exchange balance?
Get started
Bring the Pine Script your AI generated, and TVSBot handles the webhook-to-exchange leg — your own API keys, dry-run first, risk limits you set.
Start free