How to Build an AI Pine Script Swing Trading Strategy
Prompt AI for a Pine Script v6 swing trading strategy, catch repainting and warm-up errors in the output, then route a confirmed signal to a broker.
Marketing
An AI Pine Script swing trading strategy should be treated as a draft, even when it compiles. The main risks are higher-timeframe future leaks, unconfirmed bar values, missing warm-up checks, and indicator lengths that represent different calendar periods when the chart timeframe changes.
A reliable workflow starts with a precise Pine Script v6 prompt, continues with a manual code review, and ends with realtime alert testing before broker execution. The worked example below uses confirmed bars, a non-repainting higher-timeframe EMA, ATR-based exits, and explicit guards against undefined indicator values. For choosing the assistant itself, see our guide to the best AI for Pine Script.
What Counts as Swing Trading
Multi-Day Holds and Position State
Swing strategies hold positions across multiple bars or sessions. The script must therefore know whether a position is open, where its protective levels were established, and whether an exit has occurred as execution advances bar by bar.
Variables declared normally are initialized again on each execution. Adding the var keyword initializes a variable once and preserves its updates across subsequent bars.1 This is useful for storing a stop price, target price, entry state, or another value that must survive beyond the signal bar.
Intraday scripts also need correct state management, but persistent state becomes especially visible when a trade remains open through session boundaries. Whenever AI-generated code manually tracks a position, verify that the relevant variables use var or derive their state directly from strategy.position_size.
Higher-Timeframe Trend Filters
A swing system may trade signals on a lower chart timeframe while using a daily or weekly trend filter. Pine retrieves that context through request.security(). Scripts can read the chart timeframe through timeframe.period and compare timeframe durations through timeframe.in_seconds().2
This validation matters because a supposed higher-timeframe input can accidentally be set below the chart timeframe. It also helps control complexity. Pine permits up to 40 unique request.*() calls, or 64 on the Ultimate plan, so multiple symbols and filters can consume the allowance quickly.3
ATR-Based Stops for Swings
Average True Range measures volatility as an absolute price distance, not a percentage of price. An ATR value from a lower-priced instrument is therefore not directly comparable with one from a higher-priced instrument.4
ATR also incorporates its prior value when smoothing. Results depend partly on where the calculation begins, and StockCharts calculates at least 250 earlier periods to improve accuracy.4 An ATR multiple ties the stop distance to how far the instrument has recently been moving, rather than to a fixed tick count that means something different on every symbol.
Writing a Precise AI Prompt
Pin the Pine Script Version
Specify Pine Script v6. Version 6 removed the when parameter from applicable strategy.*() functions, changed default strategy margin percentages to 100, and enables dynamic request.*() calls by default.5 Without a pinned version, an AI assistant may return old syntax or logic whose behavior changed during migration.
Name Exact Inputs and Conditions
List every required input and define each condition. A useful request names the fast EMA length, slow EMA length, higher-timeframe EMA length, higher timeframe, ATR length, stop multiple, target multiple, entry rule, and discretionary exit rule.
Ask for documented input.* calls with numeric defaults and minimum values. Avoid prompts such as “make it trend-following.” If that style is your goal, define the mechanics or compare the result with a dedicated guide to AI trend-following strategies.
Require Confirmed-Bar Entry Logic
Require barstate.isconfirmed on entry and conditional exit signals. This restricts the condition to the bar's closing iteration, when prices are confirmed.6 Signals arrive later than logic based on a fluctuating realtime close, but historical and realtime behavior become more reproducible.
Worked Example: Prompt to Code
The Initial Prompt and Draft
Use a prompt with requirements that can be checked individually:
“Write a Pine Script v6 long-only swing strategy. Use configurable fast and slow EMAs on the chart for crossover entries. Require price to be above a configurable higher-timeframe EMA. Request only the last confirmed higher-timeframe EMA. Add configurable ATR stop and target multiples. Gate entries and crossover exits with barstate.isconfirmed. Reject a higher timeframe that is not above the chart timeframe. Add explicit na guards. Use named input.* calls and no calc_on_every_tick.”
A first draft will commonly contain a strategy() declaration, an EMA crossover, an HTF request, and a strategy.exit() call. Compilation only proves that the syntax is acceptable. It does not prove that historical data was available when shown or that realtime signals will match the strategy report.
Reviewing the Draft for Errors
- Inspect every HTF request for
lookahead_onwithout a[1]expression offset. - Check whether EMA and ATR values can be
naduring initialization. - Confirm that each fixed length is intentional for the selected chart timeframe.
- Verify that entries and rule-based exits require a confirmed bar.
Using lookahead_on without offsetting the requested expression leaks future HTF information into historical bars.6 Early historical references can also return na when the referenced bar does not exist.1
The Corrected Version 6 Code
//@version=6
strategy("Confirmed HTF Swing Strategy", overlay = true,
margin_long = 100, margin_short = 100,
commission_type = strategy.commission.percent, commission_value = 0.05,
slippage = 2)
int fastLength = input.int(20, "Fast EMA Length", minval = 1)
int slowLength = input.int(50, "Slow EMA Length", minval = 2)
int htfEmaLength = input.int(50, "HTF EMA Length", minval = 2)
string higherTimeframe = input.timeframe("1D", "Higher Timeframe")
int atrLength = input.int(14, "ATR Length", minval = 1)
float stopMultiple = input.float(2.0, "ATR Stop Multiple", minval = 0.1, step = 0.1)
float targetMultiple = input.float(3.0, "ATR Target Multiple", minval = 0.1, step = 0.1)
if timeframe.in_seconds(higherTimeframe) <= timeframe.in_seconds()
runtime.error("Higher Timeframe must be above the chart timeframe.")
float fastEma = ta.ema(close, fastLength)
float slowEma = ta.ema(close, slowLength)
float atrValue = ta.atr(atrLength)
// Cross checks run on every bar, outside the lazily evaluated `and` chains below.
bool bullCross = ta.crossover(fastEma, slowEma)
bool bearCross = ta.crossunder(fastEma, slowEma)
float confirmedHtfEma = request.security(
syminfo.tickerid,
higherTimeframe,
ta.ema(close, htfEmaLength)[1],
lookahead = barmerge.lookahead_on)
bool indicatorsReady = not na(fastEma) and not na(slowEma) and not na(atrValue) and not na(confirmedHtfEma)
bool longEntry = barstate.isconfirmed and indicatorsReady and strategy.position_size == 0 and bullCross and close > confirmedHtfEma
bool ruleExit = barstate.isconfirmed and strategy.position_size > 0 and bearCross
string entryMessage = '{"ticker":"' + syminfo.ticker + '","action":"buy"}'
string exitMessage = '{"ticker":"' + syminfo.ticker + '","action":"exit"}'
var float activeStop = na
var float activeTarget = na
if longEntry
activeStop := close - atrValue * stopMultiple
activeTarget := close + atrValue * targetMultiple
strategy.entry("Long", strategy.long, alert_message = entryMessage)
strategy.exit("ATR Exit", "Long", stop = activeStop, limit = activeTarget, alert_message = exitMessage)
if barstate.isconfirmed and strategy.position_size > 0 and not na(activeStop) and not na(activeTarget)
strategy.exit("ATR Exit", "Long", stop = activeStop, limit = activeTarget, alert_message = exitMessage)
if ruleExit
strategy.close("Long", alert_message = exitMessage)
if strategy.position_size == 0 and strategy.position_size[1] > 0
activeStop := na
activeTarget := na
plot(fastEma, "Fast EMA", color = color.teal)
plot(slowEma, "Slow EMA", color = color.orange)
plot(confirmedHtfEma, "Confirmed HTF EMA", color = color.blue, linewidth = 2)
plot(activeStop, "Active Stop", color = color.red, style = plot.style_linebr)
plot(activeTarget, "Active Target", color = color.green, style = plot.style_linebr)
The crossovers are computed in the global scope because v6 evaluates and conditions lazily, so a history-dependent call placed late in a chain may not run on every bar.5 The declaration sets commission and slippage on purpose: with no commission arguments a strategy applies no commission, and slippage defaults to 0 ticks.7 Use values that match your broker. The HTF expression uses the documented combination of a one-bar offset and lookahead_on, returning the last confirmed HTF value across historical and realtime bars.6 The corrected strategy will signal later than a repainting draft. That delay is the expected cost of confirmation.
Why AI Misses Warm-Up Bars
What Warm-Up Bars Mean
A script starts at the first accessible bar and executes sequentially. A reference to unavailable history returns na. In TradingView's own example, a 20-bar ta.highest() call plots na on the first 19 bars, because it needs the current bar plus 19 earlier ones.1
Longer EMA, weekly, or smoothed ATR calculations require more history before their output should be trusted. AI often focuses on the signal expression and omits the initialization period entirely.
Guarding Against Early Bar Errors
Create a single readiness condition covering every entry input:
bool indicatorsReady = not na(fastEma) and not na(slowEma) and not na(atrValue) and not na(confirmedHtfEma)
Then require that condition before entry. For ATR research, include enough earlier data to let smoothing stabilize. StockCharts uses at least 250 earlier periods for its displayed ATR calculations.4 A productive follow-up prompt is: “What happens on the first N bars where each indicator has no valid value?”
Why AI Ties Length to Timeframe
Fixed Lengths Across Timeframes
A 20-bar length represents 100 minutes on a 5-minute chart but about four trading weeks on a daily chart. Changing the timeframe therefore changes the calendar span of the rule. ATR also changes with timeframe because each bar contributes a different range, while its absolute value remains dependent on the instrument's price scale.4
Scaling Inputs to Chart Timeframe
Use timeframe.period to inspect the chart interval and timeframe.in_seconds() to compare it with an expected interval.2 A timeframe string combines a multiplier and a unit letter, such as 1D for one day, and minutes are the one unit with no letter. There is no hour unit, so one hour is written 60; 1H is not valid.2
Ask AI either to scale lengths against a clearly defined base timeframe or reject charts outside an approved range. Validation is usually easier to audit because automatic scaling can change the intended strategy logic.
Why AI Leaks Future HTF Data
The Lookahead Future Leak
request.security() defaults to barmerge.lookahead_off. If AI changes it to lookahead_on without offsetting the expression, historical bars can receive HTF values before those values were knowable.3 This can make a strategy report appear unusually clean because the filter has advance knowledge of the completed HTF bar.
TradingView calls this dangerously misleading, and using lookahead to produce misleading results is not allowed in script publications.6
The Confirmed HTF Pattern
request.security(symbol, timeframe, expression[1], lookahead = barmerge.lookahead_on)
This is the pattern TradingView documents for requesting confirmed higher-timeframe values. The offset and lookahead_on work as a pair: together they return the last confirmed HTF value on historical and realtime bars. Merely switching to lookahead_off does not prevent an unconfirmed HTF value from changing on realtime bars and repainting after a restart.6
A Review Checklist for Swing Strategies
Repainting and Confirmation Checks
- Every HTF request uses an expression offset with
lookahead_on. - Entry and conditional exit signals require
barstate.isconfirmed. - The strategy avoids unnecessary
calc_on_every_tickandvaripbehavior.
Both tick-by-tick strategy calculations and varip state can create realtime behavior that historical bars cannot reproduce.6
Timeframe and Warm-Up Checks
- Lengths are deliberately fixed, scaled, or restricted to an approved timeframe.
- All entry indicators have explicit
naguards. - The test range includes sufficient earlier history for ATR smoothing.
Execution Mechanics and Limits
strategy.exit()uses current v6 syntax withoutwhen.- Commission and slippage are set explicitly rather than left at zero.
- Gap assumptions are reviewed in the strategy report.
- The script remains within the applicable unique
request.*()limit.
When an order level is crossed in the gap between bars, TradingView's broker emulator fills the order at the next bar's open rather than at the crossed level.7 Review those trades individually instead of assuming every simulated stop or target receives its specified price.
From Chart Alert to Live Execution
Generating the Alert Message
Use alert() for condition-driven events or the alert_message parameter on order-generating functions for order fill messages. Alerts trigger only on realtime bars, and dynamic messages can include calculated values through str.tostring().8
The example already builds its order messages as JSON with ticker and action fields, so the alert's Message field only needs the {{strategy.order.alert_message}} placeholder. Keep the payload aligned with the event it represents, and recreate the running TradingView alert after changing the script or its inputs because an existing alert retains a saved copy of its original configuration.8
Webhook Payload and Test Signals
A TradersPost signal is JSON sent to a strategy webhook. It can carry ticker, action, and quantity, plus optional instructions such as stopLoss and takeProfit. If the signal carries a quantity, the subscription's Allow signal override setting next to its quantity settings decides whether that value is used instead of the configured sizing.
{
"ticker": "AAPL",
"action": "buy",
"quantity": 1,
"test": true
}
When test is true, the signal is processed as a test and no order is sent to the broker. Use this mode to compare the TradingView Alert Log with the received webhook signal before enabling live submission. Then test with a paper trading account where available and monitor realtime alerts after deployment.
Bottom Line
- Pin Pine Script v6 and name every input, condition, timeframe, and confirmation requirement.
- Use
expression[1]withlookahead_onfor confirmed higher-timeframe data. - Guard EMA, ATR, and requested values against
naduring warm-up. - Validate fixed lengths against the chart timeframe and review gap fills.
- Test JSON alerts before allowing any webhook signal to reach a broker.
AI can accelerate implementation, but it does not replace review. Use the broader AI for Pine Script guide to evaluate coding tools, then compare category-specific logic with guides to mean reversion strategies, breakout and opening range strategies, or momentum strategies.
After the strategy passes the checklist, connect its confirmed-bar alert to a broker through a webhook. Start with test:true, verify the received payload, and move to paper execution before considering live automated orders.
Frequently Asked Questions
Can AI Write a Working Strategy?
AI can produce Pine code that compiles, but compilation does not detect HTF future leaks, unconfirmed conditions, warm-up omissions, or timeframe-dependent assumptions. Treat the first response as a draft and review it against the checklist.
Why Does My Strategy Repaint?
Common causes are lookahead_on without an expression offset and conditions based on an unconfirmed realtime close, high, or low. Correcting either issue usually makes signals later because confirmed information arrives later.
Which Pine Version Should I Request?
Request Pine Script v6 explicitly. This prevents the assistant from relying on removed parameters or older request behavior and makes the generated code easier to compare with current documentation.
How Many Warm-Up Bars Are Needed?
The answer depends on the longest calculation, the requested timeframe, and the available dataset. Guard every required value against na. For stable ATR research, include substantial prior history rather than starting the test immediately when the minimum length becomes available.
How Do I Trade It Live?
Generate a realtime TradingView alert with JSON containing the required signal fields, send it to the webhook URL, and inspect the received signal. Process a test payload first, then use paper execution where available before enabling live broker submission.
References
1 TradingView: Execution Model
2 TradingView: Timeframes
3 TradingView: Other Timeframes and Data
4 StockCharts ChartSchool: Average True Range
5 TradingView: Migrating to Pine Script Version 6
6 TradingView: Repainting
7 TradingView: Strategies
8 TradingView: Alerts