Jayadev Rana Get a custom script
Free Pine Script Indicators / Signal Forge, Part 3 (Stop, Targets, Dashboard)
Free indicator · Pine Script v6

Signal Forge, Part 3 (Stop, Targets, Dashboard)

The finished Signal Forge: every filtered signal gets an ATR stop and three take profit levels drawn on the chart, a trade tracker counts on history how many signals reached each target or were stopped first, and one JSON alert fires on closed candles for bots and webhooks.

IndicatorPine Script v6OverlayFree

Get the codeDownload .pine.txt

Snapshots

The finished Signal Forge on gold 1h, with the entry, stop and target lines for the current trade and the dashboard counting how many signals reached target 1, 2 and 3 or were stopped before target 1.
The finished Signal Forge on gold 1h, with the entry, stop and target lines for the current trade and the dashboard counting how many signals reached target 1, 2 and 3 or were stopped before target 1.
The engine and filters from Parts 1 and 2 back on the chart before the stop and targets are added.
The engine and filters from Parts 1 and 2 back on the chart before the stop and targets are added.
The end of the complete script: the dashboard rows and the JSON alert.
The end of the complete script: the dashboard rows and the JSON alert.

How it works

  1. On each kept signal the stop goes 1.5 ATR away and targets 1, 2 and 3 sit at 1R, 2R and 3R, plotted only while the trade is open.
  2. A var state machine walks every candle, records which target was reached or whether the stop came first, and moves the stop to entry after target 1.
  3. The dashboard shows position, signal counts and outcomes, and alert() sends one JSON message per closed candle with the symbol, side, entry, stop and targets.

Settings

SettingDefaultWhat it does
Stop, in ATRs1.5Stop distance; targets are 1R, 2R and 3R of it
Show stop and targetstrueDraw the entry, stop and target lines
ATR length / ATR multiplier10 / 3.0The Part 1 trailing engine
Chop, volume and higher timeframe filtersonThe Part 2 filters, each with its own switch

Source code

In TradingView: open the Pine Editor, create a new indicator, paste the code, then click "Add to chart".

signal-forge-part-3.pine.txtGitHubDownload
//@version=6
// Signal Forge, part 3: stop, targets, dashboard. By Jayadev Rana.
indicator("Signal Forge", overlay = true)
// 1. Inputs: how far the trail sits, and the trend baseline
atrLen  = input.int(10, "ATR length", minval = 1)
atrMult = input.float(3.0, "ATR multiplier", minval = 0.5, step = 0.1)
emaLen  = input.int(200, "Trend EMA length", minval = 1)
useEma  = input.bool(true, "Only signal with the EMA trend")
bullCol = input.color(#26a69a, "Bull colour")
bearCol = input.color(#ef5350, "Bear colour")
// 2. Volatility and trend
atr = ta.atr(atrLen)
ema = ta.ema(close, emaLen)
// 3. The trail: it only ever moves in the trend's favour
longStop  = close - atrMult * atr
shortStop = close + atrMult * atr
var float trail = na
var int   dir   = 1
if na(trail)
    trail := longStop
else if dir == 1
    trail := math.max(trail, longStop)
    if close < trail
        dir   := -1
        trail := shortStop
else
    trail := math.min(trail, shortStop)
    if close > trail
        dir   := 1
        trail := longStop
// 4. Draw the trail by direction, and shade it to price
upLine = dir == 1 ? trail : na
dnLine = dir == -1 ? trail : na
pMid = plot(hl2, "Mid", display = display.none)
pUp  = plot(upLine, "Bull trail", bullCol, 2, plot.style_linebr)
pDn  = plot(dnLine, "Bear trail", bearCol, 2, plot.style_linebr)
fill(pMid, pUp, color.new(bullCol, 85), "Bull zone")
fill(pMid, pDn, color.new(bearCol, 85), "Bear zone")
plot(ema, "Trend EMA", color.orange, 2)
// 5. Candles: coloured when trail and EMA agree, grey when not
bullOk = dir == 1 and (not useEma or close > ema)
bearOk = dir == -1 and (not useEma or close < ema)
barcolor(bullOk ? bullCol : bearOk ? bearCol : color.gray)
// 6. Raw signals: a flip, with the trend, on a CLOSED candle only
flipUp   = dir == 1 and dir[1] == -1
flipDown = dir == -1 and dir[1] == 1
rawBuy   = flipUp and bullOk and barstate.isconfirmed
rawSell  = flipDown and bearOk and barstate.isconfirmed

// 7. Filter switches and settings
useChop = input.bool(true, "Chop filter (ADX)", group = "Filters")
adxLen  = input.int(14, "ADX length", minval = 1, group = "Filters")
adxMin  = input.float(20, "Minimum ADX", minval = 0, group = "Filters")
useVol  = input.bool(true, "Volume filter", group = "Filters")
volLen  = input.int(20, "Volume average length", group = "Filters")
volMult = input.float(1.0, "Volume multiple", step = 0.1, group = "Filters")
useHtf  = input.bool(true, "Higher timeframe filter", group = "Filters")
htfTf   = input.timeframe("240", "Higher timeframe", group = "Filters")
htfLen  = input.int(50, "Higher timeframe EMA", group = "Filters")
// 8. Chop filter: no signal when ADX says there is no trend
[diPlus, diMinus, adx] = ta.dmi(adxLen, adxLen)
chopOk = not useChop or adx >= adxMin
// 9. Volume filter: the flip candle must beat average volume
volAvg = ta.sma(volume, volLen)
volOk  = not useVol or volume > volAvg * volMult
// 10. Higher timeframe: last CLOSED candle only, never the live one
[htfC, htfE] = request.security(syminfo.tickerid, htfTf,
  [close[1], ta.ema(close, htfLen)[1]],
  lookahead = barmerge.lookahead_on)
htfBuyOk  = not useHtf or htfC > htfE
htfSellOk = not useHtf or htfC < htfE
// 11. Final signals: the raw signal AND every filter that is on
buy  = rawBuy and chopOk and volOk and htfBuyOk
sell = rawSell and chopOk and volOk and htfSellOk
plotshape(buy, "BUY", shape.labelup, location.belowbar, bullCol,
  text = "BUY", textcolor = color.white, size = size.small)
plotshape(sell, "SELL", shape.labeldown, location.abovebar, bearCol,
  text = "SELL", textcolor = color.white, size = size.small)
alertcondition(buy, "Signal Forge BUY", "BUY {{ticker}} at {{close}}")
alertcondition(sell, "Signal Forge SELL", "SELL {{ticker}} at {{close}}")
// 12. Ghosts: a small grey cross wherever a raw signal was killed
killed = (rawBuy or rawSell) and not (buy or sell)
plotshape(killed, "Filtered out", shape.xcross, location.abovebar,
  color.gray, size = size.tiny)
// 13. Count on history: each kill is blamed on the FIRST filter
var int nRaw  = 0
var int nChop = 0
var int nVol  = 0
var int nHtf  = 0
var int nKept = 0
if rawBuy or rawSell
    htfOk = rawBuy ? htfBuyOk : htfSellOk
    nRaw += 1
    if not chopOk
        nChop += 1
    else if not volOk
        nVol += 1
    else if not htfOk
        nHtf += 1
    else
        nKept += 1

// 14. Risk: the stop is a multiple of ATR, targets are 1R, 2R, 3R
slMult  = input.float(1.5, "Stop, in ATRs", minval = 0.1, step = 0.1,
  group = "Trade")
showLvl = input.bool(true, "Show stop and targets", group = "Trade")

// 15. The trade tracker's memory
var int   side     = 0
var int   entryBar = na
var float entry    = na
var float stop     = na
var float tp1      = na
var float tp2      = na
var float tp3      = na
var int   hits     = 0
var int   nTp1     = 0
var int   nTp2     = 0
var int   nTp3     = 0
var int   nStop    = 0
evt = ""

// 16. Manage the open trade: stop first, then targets in order
reached(price) => side == 1 ? high >= price : low <= price
live = side != 0 and bar_index > entryBar
if live
    if side == 1 ? low <= stop : high >= stop
        evt   := hits == 0 ? "STOP" : "BREAKEVEN"
        nStop += hits == 0 ? 1 : 0
        side  := 0
    else
        if hits < 1 and reached(tp1)
            hits := 1, nTp1 += 1, evt := "TP1", stop := entry
        if hits < 2 and reached(tp2)
            hits := 2, nTp2 += 1, evt := "TP2"
        if hits < 3 and reached(tp3)
            hits := 3, nTp3 += 1, evt := "TP3", side := 0

// 17. A new signal opens a new trade at the close of its candle
if buy or sell
    risk      = slMult * atr
    side     := buy ? 1 : -1
    entryBar := bar_index
    entry    := close
    stop     := close - side * risk
    tp1      := close + side * risk
    tp2      := close + side * 2 * risk
    tp3      := close + side * 3 * risk
    hits     := 0

// 18. Draw the levels only while a trade is live
lvl(x) => showLvl and live ? x : na
plot(lvl(entry), "Entry", color.gray, 1, plot.style_linebr)
plot(lvl(stop), "Stop", bearCol, 2, plot.style_linebr)
plot(lvl(tp1), "TP1", color.new(bullCol, 40), 1, plot.style_linebr)
plot(lvl(tp2), "TP2", color.new(bullCol, 20), 1, plot.style_linebr)
plot(lvl(tp3), "TP3", bullCol, 2, plot.style_linebr)

// 19. The dashboard: live state on top, history underneath
var table tb = table.new(position.top_right, 2, 11,
  bgcolor = color.new(#131722, 5), border_color = #434651,
  border_width = 1)
row(r, txt, val, col) =>
    table.cell(tb, 0, r, txt, text_color = color.silver,
      text_halign = text.align_left, text_size = size.normal)
    table.cell(tb, 1, r, val, text_color = col,
      text_size = size.normal)
num(x) => str.tostring(x)
if barstate.islast
    pos = side == 1 ? "LONG" : side == -1 ? "SHORT" : "FLAT"
    trend = bullOk ? "BULL" : bearOk ? "BEAR" : "MIXED"
    row(0, "Signal Forge", trend, bullOk ? bullCol : bearCol)
    row(1, "Position", pos, side == 0 ? color.silver : color.white)
    row(2, "Raw signals", num(nRaw), color.white)
    row(3, "Removed by filters", num(nChop + nVol + nHtf), color.orange)
    row(4, "Signals kept", num(nKept), color.white)
    row(5, "Reached TP1", num(nTp1), bullCol)
    row(6, "Reached TP2", num(nTp2), bullCol)
    row(7, "Reached TP3", num(nTp3), bullCol)
    row(8, "Stopped before TP1", num(nStop), bearCol)
    row(9, "Stop now", side == 0 ? "-" : num(stop), color.silver)
    row(10, "Next target", side == 0 ? "-" : num(hits == 0 ? tp1 :
      hits == 1 ? tp2 : tp3), color.silver)

// 20. One dynamic alert, as JSON, only when a candle has closed
jStr(k, v) => '"' + k + '":"' + v + '"'
jNum(k, v) => '"' + k + '":' + str.tostring(v, format.mintick)
if buy or sell or evt != ""
    msg = "{" + jStr("symbol", syminfo.ticker)
    msg += "," + jStr("signal", buy ? "BUY" : sell ? "SELL" : "NONE")
    msg += "," + jStr("event", evt)
    msg += "," + jNum("entry", entry) + "," + jNum("stop", stop)
    msg += "," + jNum("tp1", tp1) + "," + jNum("tp2", tp2)
    msg += "," + jNum("tp3", tp3) + "}"
    alert(msg, alert.freq_once_per_bar_close)

Watch it built

This script is written and explained step by step in the video lesson.

Need a custom indicator or strategy?

Non-repainting Pine Script v6, backtested with real costs, alert and webhook ready. Fixed quote within 24 hours.

Free and open source under the Mozilla Public License 2.0. Educational content only, not financial advice. Backtest results are historical and include the costs stated; past performance does not predict future results. © Jayadev Rana · Privacy · Terms