Indicator · Pine Script v5 · Own pane

Monte Carlo Simulation [Kioseff Trading]

by regalouisei· code credited to KioseffTrading

This indicator performs Monte Carlo simulations to model future price paths or user-defined strategy return series.

View on GitHub

GitHub stars2on the repository
Pine versionv5runs as is
Lines of code508
Settings18inputs
LicenceNone statedfile: MPL-2.0
Last update8 months ago25 Feb 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

508 lines of Pine Script v5 from pinescript/editors_picks/monte-carlo-simulation-your-strategy-kioseff-trading.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

monte-carlo-simulation-your-strategy-kioseff-trading.pine
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © KioseffTrading

//@version=5
indicator("Monte Carlo Simulation [Kioseff Trading]", max_lines_count = 500, overlay = false, max_labels_count = 500, max_boxes_count = 500)

justPriceS = input.string   (defval = "Price",  title = "I Want To Simulate: ", options = ["Price" , "My Strategy"])
seed       = input.int      (defval = 0, title = "Set Seed", minval = 0)
distT      = input.string   (defval = "Normal", title = "Type", options = ["Normal", "Bootstrap"])
txt        = input.text_area(defval = "-3\n-2\n-1\n0\n1\n2\n3\n *If You Selected 'My Strategy'\n(Paste Your Returns Here Following This Format)", title ="Returns")
cumu       = input.float    (defval = 70, title = "Cumulative Probability Target", maxval = 99, minval = 5) / 100
lin        = input.bool     (defval = false,  title = "Line Plot Only")
lab        = input.bool     (defval = false,  title = "Represent With Circles")
hist       = input.bool     (defval = false,  title = "Probability Distribution Only")
binT       = input.string   (defval = "Rice", title = "Binning Method", options = ["Sturges", "Rice", "Square Root", "Custom"])
sims       = input.int      (defval = 250,    title = "Simulations", minval = 1)
forx       = input.int      (defval = 15,     title = "Forecast") + 1
only       = input.bool     (defval = false,  title = "Show Best Case / Worst Case Only")
back       = input.int      (defval = 200,    title = "Historical Data Points", minval = 5, group = "Simulate Price Instead", step = 10)
lineS      = input.string   (defval = "Solid", title = "Line Style", options= ["Solid", "Dotted", "Dashed"], group = "Line Style")

finS = switch lineS

    "Dotted" => line.style_dotted
    "Solid"  => line.style_solid
    "Dashed" => line.style_dashed

var ret    = array.new_float(), endPoints = array.new_float()

justPrice = justPriceS == "Price"

if lab 
    only := false
    hist := false

if hist 
    only := false
    lin  := false
    
binSize(size) =>  

    switch binT 
        
        "Square Root" => math.sqrt(size) 
        "Rice"        => 2 * (math.pow(size, 1./3))
        "Sturges"     => math.log(size) / math.log(2) + 1, 
        "Custom"      => input.int(20, title = "Custom Bin Amount (If Selected)", minval = 5, maxval = 200, group = "Custom Bin Amount")

txt2       = input.text_area(defval = "",     title = "Additional Returns")
txt3       = input.text_area(defval = "",     title = "Additional Returns")
txt4       = input.text_area(defval = "",     title = "Additional Returns")


Gauss(z, avg, std) => 
    
    avg + std * z

method boot(array <float> id) => 


    id.get(int(math.random(0, id.size(), seed)))

distribution() => 

    avg    = ret.avg(), std = ret.stdev(false), size = ret.size()
    r1     = math.random(0.01, 0.99, seed)
    r2     = math.random(0.01, 0.99, seed)  
    result = math.sqrt  (-2.0 * math.log(r1)) * math.cos(2.0 * math.pi * r2)

    switch distT

        "Normal"   => Gauss(result, avg, std)
        =>            ret.boot()


method num(float id, val) => 

    switch justPrice 

        false => id + val 
        =>       id * math.exp(val)

method dete (matrix <float> id, val, i, x, int col, matrix<color> id2, color val2) => 

    if not lab

        if i < id.rows()

            id.set (i, col, val)
            id2.set(i, col, val2)
    else 
        
        if x == forx - 1 and i < 1000

            id .set(i, 0, val)
            id2.set(i, 0, val2)

atr = ta.atr(14)

method mult(float id, up = 1.002, dn = .998) =>
    
    switch 

        justPrice           => id - atr
        math.sign(id) == -1 => id * up
        math.sign(id) == 1  => id * dn

if last_bar_index - bar_index <= back and justPrice 

    ret.push(math.log(close / close[1]))

method iSwitch(matrix <float> id, lastClos, i, x, colData, col, matrix <float> minMax) => 

    switch only

        false and not hist => id.dete(lastClos, i, x, x, colData, col) 
        
        =>       minMax.set(0, x, math.min(nz(minMax.get(0, x), 1e8 ), lastClos)), 
                 minMax.set(1, x, math.max(nz(minMax.get(1, x), -1e8), lastClos))


method endTrim (array <box> histBox, array<float> val, array<float> freq, array<label> histLabs) => 

    if freq.size() > 0 and val.size() == freq.size() and histLabs.size() == freq.size() and histBox.size() == freq.size()
        for i = 0 to freq.size() - 1
            switch val.get(i) == 9e9
                true => histBox.get(i).delete(), histLabs.get(i).delete()
                =>      break

        for i = freq.size() - 1 to 0
            switch val.get(i) == 9e9
                true => histBox.get(i).delete(), histLabs.get(i).delete()
                =>      break
// 

method returnCheck(array<float> id, array<string> id2) => 

    if id2.size() > 0 
        for i = 0 to id2.size() - 1
            if not na(str.tonumber(id2.get(i)))
                id.push(str.tonumber(id2.get(i)))

if barstate.islastconfirmedhistory 

    subs    = str.split(txt , "\n")
    subs1   = str.split(txt2, "\n")
    subs2   = str.split(txt3, "\n")
    subs3   = str.split(txt4, "\n")

    if not justPrice

        ret.returnCheck(subs ), ret.returnCheck(subs1),
        ret.returnCheck(subs2), ret.returnCheck(subs3)

    sum  = switch justPrice 

        false => ret.sum()
        =>       close

    [row, column] = switch lab 

        false => [math.floor(500 / forx), forx] 
        =>       [           1000, 1          ]
    

    lines    = array.new_line()
    minMax   = matrix.new<float>(2, forx + 1)
    lineData = matrix.new<float>(row, column)
    colData  = matrix.new<color>(row, column)

    res = 0.

    for i = 0 to sims

        col = color.rgb(math.random(0, 255), math.random(0, 255), math.random(0, 255))

        lastClose = sum

        if not lab
            lineData.dete(sum, i, 0, 0, colData, col)


        for x = 1 to forx - 1
 
            res       := distribution()
            lineData  .iSwitch(lastClose, i, x, colData, col, minMax)
            lastClose := lastClose.num(res)

        endPoints.push(lastClose.num(res))
        log.info(str.tostring(lastClose.num(res)))

    if not hist

        if only 

            minMax.set(0, 0, sum)
            minMax.set(1, 0, sum)

            for i = 1 to forx 

                line.new(bar_index + i - 1, minMax.get(0, i - 1), bar_index + i, minMax.get(0, i), color = color.red , style = finS)
                line.new(bar_index + i - 1, minMax.get(1, i - 1), bar_index + i, minMax.get(1, i), color = color.lime, style = finS)

        else 

            if not lab
                for x = 0 to lineData.rows() - 1
                    for i = 1 to lineData.columns() - 1

                        modulo = i % forx

                        line.new(bar_index + modulo - 1, lineData.get(x, i - 1), bar_index + modulo, lineData.get(x, i), 
                                         color = colData.get(x, i), 
                                         style = finS
                                         )
            else 

                for x = 0 to lineData.rows() - 1

                    random = int(math.random(1, 250, seed))

                    switch label.all.size() >= 500

                        false => label.new(bar_index + random, lineData.get(x, 0), color = colData.get(x, 0), 
                                             text      = "◉", 
                                             style     = label.style_text_outline, 
                                             size      = size.tiny, 
                                             textcolor = chart.fg_color
                                             )

                        true  => box.new  (bar_index + random, lineData.get(x, 0), bar_index + random, lineData.get(x, 0), 
                                             text_color = colData.get(x, 0), 
                                             text       = "◉", 
                                             text_size  = size.tiny
                                             )

        if not justPrice

            line.new  (bar_index - 1, ret.sum(), bar_index, ret.sum(), color = chart.fg_color, extend = extend.left)

            label.new (bar_index - 1, ret.sum(), text = str.tostring(ret.sum()), size = size.tiny, 

                                 style     = label.style_label_down, 
                                 color     = #00000000, 
                                 textcolor = color.white
                                 )

    min = 1e8, max = -1e8, xmax = int(-1e8)

    if not only and not hist


        endPoint = switch lab 

            false => line.all .size() - 1
            =>       label.all.size() - 1

        for i = 0 to endPoint


            switch lab

                false => min := math.min(line.all .get(i).get_y2(), min),
                         max := math.max(line.all .get(i).get_y2(), max)

                =>       min  := math.min(nz(label.all.get(i).get_y (),  1e8), min ),
                         max  := math.max(nz(label.all.get(i).get_y (), -1e8), max ), 
                         xmax := math.max(nz(label.all.get(i).get_x(), int(-1e8)), xmax)

        if lab

            if box.all.size() > 0 

                for i = 0 to box.all.size() - 1  

                    btm = box.all.get(i).get_bottom()
                    min  := math.min(min,  nz(btm,  1e8))
                    max  := math.max(max,  nz(btm, -1e8))
                    xmax := math.max(xmax, nz(box.all.get(i).get_right (), int(-1e8)))
    else 

        if not hist

            min := minMax.min()
            max := minMax.max()


    if not lab and not hist

        for i = 0 to forx - 1

            label.new(bar_index + i, min.mult(), str.tostring(i), style = label.style_label_up, color = #00000000, textcolor = chart.fg_color, size = size.tiny)


    if not hist

        [xcoor, txtt, xcoor2] = switch lab 

            false => [bar_index - 2, justPrice ? "Time #" : "Trade #", bar_index + forx + 1]
            =>       [bar_index - 4, "All Results For " + (justPrice ? "Time # " : "Trade # ") + str.tostring(forx - 1), xmax + 5]


        label.new(xcoor, min.mult(), text = txtt, color = #00000000, 
                             textcolor = chart.fg_color, 
                             size      = size.tiny, 
                             style     = label.style_label_up)

        line.new (bar_index - 3, min.mult(), xcoor2, min.mult(), color = chart.fg_color)
        line.new (xcoor2,        min.mult(), xcoor2, max.mult(), color = chart.fg_color)

        calc = (max.mult() - min.mult()) / 10

        for i = 0 to 10 

            label.new(xcoor2, min.mult() + (i * calc), str.tostring(min.mult() + (i * calc), format.mintick), 
                         textcolor = chart.fg_color, 
                         style     = label.style_label_left, 
                         size      = size.tiny, 
                         color     = #00000000
                         )


    if not only and not lin
 

        k = math.ceil(binSize(endPoints.size()))

        maxR  = endPoints.max()
        minR  = endPoints.min()

        freq  = array.new_float(k, hist ? 0 : sum) 
        count = array.new_float(k, 0)
        val   = array.new_float(k, 9e9)


        sizE = (maxR - minR) / k 
        

        for i = 0 to endPoints.size() - 1

            ind = math.floor((endPoints.get(i) - minR) / sizE) 

            if ind >= 0 and ind < k

                freq.set(ind, freq.get(ind) + 1)
                count.set(ind, count.get(ind) + 1)

                if endPoints.get(i) < val.get(ind)
                    val.set(ind, endPoints.get(i))

        newMin = 1e8, newMax = -1e8

        if not hist

            if not only

                for i = 0 to line.all.size() - 1

                    newMin := math.min(newMin, line.all.get(i).get_y2()) 
                    newMax := math.max(newMax, line.all.get(i).get_y2()) 

            else 
                newMin := minMax.min(), newMax := minMax.max()

        else 
            newMin := close * .975, newMax := close * 1.025

        if not lab 

            histLabs = array.new_label(), histBox = array.new_box()

            if not hist

                freqMin = freq.min(), freqMax = freq.max(), freqRange = freqMax - freqMin, minimum = 1e8

                for i = 0 to freq.size() - 1

                    freq.set(i, ((freq.get(i) - freqMin) / freqRange) * (newMax - newMin) + newMin)

                    if freq.get(i) != newMin 
                        minimum := math.min(minimum, freq.get(i))

                for i = 0 to freq.size() - 1 


                    pretext = switch justPrice 

                        true => str.tostring((val.get(i) - close) / close, "###.0000") + "\n" + str.tostring(count.get(i))
                        =>      str.tostring(val.get(i), format.mintick) + "\n" + str.tostring(count.get(i))


                    strtxt = switch val.get(i) == 9e9
                        true => "--" 
                        =>      pretext

                    histBox.push(box.new(bar_index   + forx + 6 + i, newMin, bar_index + forx + 6 + i + 1, math.max(freq.get(i), minimum), 
                                                 bgcolor = color.new(#6929F2, 50), 
                                                 border_color = chart.fg_color
                                                 ))

                    histLabs.push(label.new(bar_index + forx + 6 + i, newMin, "   " + strtxt, 
                                                 style     = label.style_label_up, 
                                                 color     = #00000000, 
                                                 textcolor = chart.fg_color, 
                                                 size      = size.tiny))

                    histBox.endTrim(val, freq, histLabs)

            else 

                for i = 0 to freq.size() - 1 

                    pretext = switch justPrice
                        
                        true => str.tostring((val.get(i) - close) / close, "###.0000") + "\n" + str.tostring(count.get(i))
                        =>      str.tostring(val.get(i), format.mintick) + "\n" + str.tostring(count.get(i))
                    
                    strtxt = switch val.get(i) == 9e9

                        true => "--" 
                        =>      pretext


                    histBox.push(box.new(bar_index - 20 + i , 0, bar_index - 20 + i + 1, math.max(freq.get(i), 0.2), 
                                     bgcolor      = color.new(#6929F2, 50), 
                                     border_color = chart.fg_color))



                    histLabs.push(label.new(bar_index - 20 + i, 0,
                          "   " + strtxt, 
                                         style     = label.style_label_up, 
                                         color     = #00000000, 
                                         textcolor = chart.fg_color, 
                                         size      = size.tiny
                                         ))

                    histBox.endTrim(val, freq, histLabs)


            [yloc1, yloc2] = switch hist
                
                false => [newMin, newMax]
                =>       [0, freq.max()]

            xloc  = box.all.last ().get_right() + 2
            xloc1 = box.all.first().get_left()

            line.new(xloc , yloc1, xloc, yloc2, color = chart.fg_color)
            line.new(xloc1, yloc1, xloc, yloc1, color = chart.fg_color)

            calc = ((count.max() / count.sum() * 100) - (count.min() / count.sum() * 100)) / 10
            calcy = (yloc2 - yloc1) / 10
            label.new(xloc1 - 1, yloc1, "Return\nCount", style = label.style_label_up, color = #00000000, textcolor = chart.fg_color, size = size.tiny)
            
            for i = 0 to 10 

                label.new(xloc, yloc1 + (calcy * i), text = str.tostring(count.min() + (calc * i), format.percent), 

                                 style     = label.style_label_left,
                                 color     = #00000000, 
                                 textcolor = chart.fg_color,
                                 size      = size.tiny
                                 )

            highest = count.indexof(count.max())

            if not lab and not lin

                for i = 1 to count.size() - 1
    
                    slice = count.slice(highest - i, highest + i + 1)
    
                    if slice.sum() / count.sum() >= cumu 
                    
                        for x = highest - i to highest + i
                            histBox.get(x).set_bgcolor(color.new(#6929F2, 50)) 
                        
                        label.new(
                                 math.round(
                                     math.avg(
                                         histBox.get(highest).get_left(), histBox.get(highest).get_right())), histBox.get(highest).get_bottom(), 
                                         text = "Cumulative Prob: " + str.tostring(slice.sum() / count.sum() * 100, format.percent), 
                                         color = #00000000, textcolor = #6929F2, size = size.tiny)
    
                        left  = histBox.get(highest - i)
                        right = histBox.get(highest + i)
                        top   = histBox.get(highest)
    
                        l = line.new(left.get_left(), top.get_top(), left.get_left(), top.get_bottom()    , color = #00000000)
                        r = line.new(right.get_right(), top.get_top(), right.get_right(), top.get_bottom(), color = #00000000)
                        linefill.new(l, r, color.new(chart.fg_color, 75))
    
                        break

floatna () => 

    [float(na), float(na), float(na), float(na), color(na)]

[o, h, l, c, color] = switch justPrice 

    false =>             floatna()
    true and not hist => [open, high, low, close, close > open ? color.aqua : color.red]
    =>                   floatna()
    
plotcandle(o, h, l, c, color = color, wickcolor = color, bordercolor = chart.fg_color, display = display.pane)

//

Overview

Monte Carlo Simulation [Kioseff Trading] generates projected paths using either a Gaussian distribution or bootstrap resampling from historical data or pasted return sets. The script draws simulation trajectories, probability distributions, or best and worst case boundaries in a separate pane. Users can adjust the number of simulations, forecast horizon, binning methodology, random seed, and cumulative probability targets.

Details

Published by

regalouisei on GitHub

Code credit

KioseffTrading, as written in the script's header

Type

Indicator, Pine Script v5

Draws

Separate pane below the chart

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
I Want To Simulate: stringPricejustPriceS
Set Seedint0seed
TypestringNormaldistT
Returnstext-3 -2 -1 0 1 2 3 *If You Selected 'My Strategy' (Paste Yourtxt
Cumulative Probability Targetfloat70cumu
Line Plot Onlyboolfalselin
Represent With Circlesboolfalselab
Probability Distribution Onlyboolfalsehist
Binning MethodstringRicebinT
Simulationsint250sims
Forecastint15forx
Show Best Case / Worst Case Onlyboolfalseonly
Show all 18 settings
SettingTypeDefaultVariable
Historical Data Pointsint200back
Line StylestringSolidlineS
Custom Bin Amount (If Selected)int20Custom Bin Amount (If Selected)
Additional Returnstexttxt2
Additional Returnstexttxt3
Additional Returnstexttxt4
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

Asymmetrical RSI

by everget
948 stars

Asymmetrical RSI is an indicator that computes a custom relative strength index from the rate of change of a user-selected source. It plots the resulting ARSI line in a separate pane with color coding that turns green above the overbought level and red below t

Indicator v4 Oscillators & momentum
Updated 10 days ago · GPL-3.0 GitHub repository of Asymmetrical RSI

This indicator calculates momentum setups based on the Relative Strength Index (RSI) and plots in a separate pane. It offers multiple scanning modes such as Continuous Break, Continuous Retracement, Flip Break, Flip Retracement, and both regular and hidden div

Indicator v6 Oscillators & momentumDivergence Repaints
Updated 1 year ago · MPL-2.0 GitHub repository of Momentum Setup - RSI Directional Momentum Indicator
549 stars

Range Volume Change compares volume inside a chosen session window with the same window on the previous day and plots that change as a percentage. It accumulates volume during the window and, when enabled, volume outside it, drawing growing columns in a separa

Indicator v5 VolumeSessions & time Repaints
Updated 10 months ago · None stated GitHub repository of Range Volume Change

Beep Boop

by magic8bot
409 stars

This indicator calculates a modified MACD oscillator and signal line alongside a trend EMA filter, plotting the output as a stylized column histogram in a separate pane. Instead of plotting true MACD distance, it quantifies positive and negative histogram mome

Indicator v4 Oscillators & momentumTrend
Updated 3 years ago · MIT GitHub repository of Beep Boop

Mojito v3 Strategy

by namuan
318 stars

The Mojito v3 Strategy indicator evaluates volatility term structure by calculating the ratio between front-month and second-month VIX futures contracts. It smooths this ratio using a configurable median filter and compares the result against user-defined uppe

Indicator v5 Volatility & bands
Updated 2 months ago · MIT GitHub repository of Mojito v3 Strategy

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers