Skip to content
Back to skills

Pinescript Strategy

ASecurity

Write TradingView Pine Script v6 strategies that behave the same live as in backtest — entries, exits, position sizing, commission and slippage, and the repainting and lookahead traps that make a backtest lie. Includes validated scaffolds and webhook alert payloads for broker automation. Use when asked to write, build or backtest a trading strategy; to add entries, exits, stops or take-profits; when a strategy performs differently live than in testing; or to automate alerts to a broker.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 29, 2026
ai-agentsgoawstestingdebuggingapisecurity

Works with

  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 29, 2026

npx -y skills add jpantsjoha/pinescript-plugin --skill pinescript-strategy --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Pinescript Strategy?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Pinescript Strategy
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jpantsjoha-pinescript-strategy/badge)](https://www.skillsdirectory.com/skills/jpantsjoha-pinescript-strategy)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: pinescript-strategy
description: "Write TradingView Pine Script v6 strategies that behave the same live as in backtest — entries, exits, position sizing, commission and slippage, and the repainting and lookahead traps that make a backtest lie. Includes validated scaffolds and webhook alert payloads for broker automation. Use when asked to write, build or backtest a trading strategy; to add entries, exits, stops or take-profits; when a strategy performs differently live than in testing; or to automate alerts to a broker."
license: MIT
metadata:
  "pinescript-plugin/version": "0.5.0"
  "pinescript-plugin/triggers": "write a strategy, trading strategy, backtest, strategy.entry, strategy.exit, stop loss, take profit, position sizing, repainting, backtest doesn't match live, webhook alert, automate to broker"
  "pinescript-plugin/pine-version": "v6"
---

# Pine v6 strategies

## The thing that actually goes wrong

A strategy that compiles cleanly and backtests beautifully can still lose money,
because **the backtest was measuring something the live market will not repeat.**
Practitioners are blunt about this: *"one overlooked mistake — like a repainting
signal or scope error — can invalidate months of backtesting."*
([PickMyTrade](https://blog.pickmytrade.io/debugging-tradingview-strategies-10-common-pine-script-mistakes/))

The validator now catches three of the five traps below — **S1** (repainting),
**S2** (`ta.*` in a conditional) and **S9** (entry with no exit). Call
`validate_pine_script` and act on those findings.

The other two — unconfirmed-bar entries and optimistic fill assumptions — are
invisible to any static check. **Read for those by eye, every time, before anyone
risks capital.**

## The five traps, in order of cost

### 1. Repainting higher-timeframe data — detected as **S1**

```pine
// WRONG — the current daily bar is still forming; history and live disagree
d = request.security(syminfo.tickerid, "D", close)

// RIGHT — the previous, closed bar
d = request.security(syminfo.tickerid, "D", close[1], lookahead=barmerge.lookahead_off)
```

`lookahead_on` is worse than useless in a strategy: it lets history see data that
did not exist yet, so the backtest is fiction.

### 2. Acting on an unconfirmed bar — NOT detectable

Intrabar, `close` moves. A condition true mid-bar may be false at the close, so the
signal appears and vanishes.

```pine
// Deterministic — evaluate only on a closed bar
if barstate.isconfirmed and longCondition
    strategy.entry("Long", strategy.long)
```

Or set `calc_on_every_tick=false` (the default) and leave it alone.

### 3. `ta.*` inside a conditional — detected as **S2**

```pine
// WRONG — ta.rsi only advances when the branch is taken; its state is corrupted
v = useRsi ? ta.rsi(close, 14) : na

// RIGHT — compute every bar, select afterwards
rsiValue = ta.rsi(close, 14)
v = useRsi ? rsiValue : na
```

Compiles. Silently wrong. The indicator's internal history has gaps.

### 4. Accumulator lifetime — both directions are wrong

```pine
// WRONG — resets every bar, so it is always 1
int wins = 0
wins := wins + 1

// WRONG in v6 — `and` short-circuits, so the increment is skipped
// whenever the left side is false
ok = condition and (wins := wins + 1) > 0

// RIGHT — persist with var, and never hide state changes inside and/or.
// The same applies to ta.*: `x and ta.rsi(close,14) > 50` is a CONDITIONAL ta.* call,
// because v6 short-circuits. Compute it on its own line first.
var int wins = 0
if tradeClosed and profitable
    wins := wins + 1
```

**Now the inverse, which is more common and more expensive.** `var` persists, so a
loop that re-accumulates every bar without a reset grows without bound:

```pine
// WRONG — var means this is never cleared; it adds 10 more closes every bar
var float sum = 0.0
for i = 0 to 9
    sum := sum + close[i]

// RIGHT — a per-bar total must NOT be var
float sum = 0.0
for i = 0 to 9
    sum += close[i]

// ALSO RIGHT — keep var, but reset before the loop
var float sum = 0.0
sum := 0.0
for i = 0 to 9
    sum += close[i]
```

Same trap with `while`, and here it is silent rather than wrong:

```pine
// WRONG — on bar 2 counter is already 5, so the body never runs again
var int counter = 0
while counter < 5
    counter += 1
```

**The question to ask of every accumulator: does its lifetime match its meaning?**
"Total so far" wants `var`. "Total for this bar" must not have it. The validator
does not check this (S3), so it is on you.

v6 introduced short-circuit evaluation. Any assignment tucked into the right-hand
side of `and`/`or` will sometimes not run.

### 5. Optimistic fills — NOT detectable

Default backtest assumptions are generous. State them explicitly:

```pine
strategy("S", overlay=true,
     initial_capital=10000,
     default_qty_type=strategy.percent_of_equity,
     default_qty_value=10,
     commission_type=strategy.commission.percent,
     commission_value=0.05,
     slippage=2,
     process_orders_on_close=true,
     calc_on_every_tick=false)
```

Zero commission and zero slippage flatter every strategy, and flatter high-frequency
ones most.

## Entries and exits

```pine
strategy.entry("Long", strategy.long, comment="MA cross")
strategy.exit("Long Exit", from_entry="Long", stop=stopPrice, limit=targetPrice)
strategy.close("Long", comment="Signal flip")
strategy.close_all(comment="Flat")
```

- `strategy.entry` **reverses** an opposing position by default. To avoid that, close first.
- `strategy.exit` needs `from_entry` matching the entry `id`, or it attaches to everything.
- An entry with no exit is unbounded risk. Every `strategy.entry` needs a matching
  `strategy.exit` or `strategy.close` — detected as **S9**. Note `strategy.cancel`
  withdraws a *pending order*; it does not close a position and does not count.
- `pyramiding` defaults to 0 — additional same-direction entries are ignored unless you raise it.

## Stops and targets

```pine
longStop   = strategy.position_avg_price * (1 - stopPercent / 100)
longTarget = strategy.position_avg_price * (1 + targetPercent / 100)

if strategy.position_size > 0
    strategy.exit("X", from_entry="Long", stop=longStop, limit=longTarget)
```

Derive levels from `strategy.position_avg_price`, not from `close` at signal time —
the fill is not the signal bar's close.

For ATR-based stops, compute ATR **unconditionally**, then use it.

## Runtime state

| Variable | Meaning |
|---|---|
| `strategy.position_size` | Signed; `> 0` long, `< 0` short, `0` flat |
| `strategy.position_avg_price` | Average fill of the open position |
| `strategy.equity` | Capital including open P/L |
| `strategy.netprofit` | Closed-trade profit |
| `strategy.opentrades` / `strategy.closedtrades` | Counts |

## Webhook automation

Strategies are commonly automated by posting JSON to a broker bridge — TradersPost,
PickMyTrade, CrossTrade. The alert message carries the payload:

```pine
alertMessage = '{"ticker":"' + syminfo.ticker + '","action":"buy","sentiment":"long"}'

if longCondition
    strategy.entry("Long", strategy.long, alert_message=alertMessage)
```

`alert_message` on the order call is what the webhook posts. Keep it valid JSON —
build it with `str.tostring()` and explicit concatenation, and check the receiving
platform's required fields (`ticker`, `action`, `sentiment`, `quantity` are common).

`alertcondition()` messages are **static** and cannot carry runtime values; use
`alert()` or order-level `alert_message` when the payload must vary.

## Scaffolds

`references/scaffolds/` — validated in CI:

| Scaffold | Shows |
|---|---|
| `basic-strategy.pine` | Entries, exits, sizing, commission, confirmed-bar gating |
| `risk-managed-strategy.pine` | ATR stops, position sizing from risk %, webhook payloads |

## Checklist before anyone risks money

- [ ] `validate_pine_script` clean
- [ ] Every `request.security` uses `[1]` or an explicit `lookahead_off`
- [ ] Entries gated on `barstate.isconfirmed`, or `calc_on_every_tick=false`
- [ ] All `ta.*` calls unconditional — including on the right of `and`/`or`, which short-circuits
- [ ] Every accumulator's lifetime matches its meaning: `var` for a running total, no `var` (or a reset before the loop) for a per-bar one
- [ ] Commission and slippage set to something realistic
- [ ] Every entry has a matching exit
- [ ] Backtest sample is long enough to include a regime change
- [ ] `alert_message` JSON validated against the broker's schema

Files in this skill

  • SKILL.md8.3 KB
  • references/README.md1.1 KB
  • references/scaffolds/basic-strategy.pine2.6 KB
  • references/scaffolds/risk-managed-strategy.pine2.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.