Monte Carlo Price Probabilities
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © peacefulLizard50262

//@version=5
indicator("Monte Carlo", max_lines_count = 500, overlay = true, max_bars_back = 500)

arb_price = input.price(0, "Price", confirm = true)
sims = input.int(5, "Number of Sims", 1)
steps = input.int(5, "Number of steps into the future", 2)
accuracy = input.int(7, "Rounding Level")
lookback = input.int(0, "Start Bar Index", 0)
show_bar_index = input.bool(false, "Show Bar Index")

percent(a, b)=>
    ((a-b)/b) * 100

logReturn(a, b)=>
    math.log(a / b) * 100

addLogReturn(number, logReturn) =>
    result = number * math.exp(logReturn / 100)
    result

movement_probability(accuracy)=>
    var movement_size_green = array.new<float>(1)
    var count_green = array.new<int>(1)
    var movement_size_red = array.new<float>(1)
    var count_red = array.new<int>(1)
    var candle = array.new<int>(2, 0)
    change = math.round(logReturn(close, close[1]), accuracy)

    if close > open
        if array.includes(movement_size_green, change)
            index = array.indexof(movement_size_green, change)
            array.set(count_green, index, array.get(count_green, index) + 1)
            array.set(candle, 0, array.get(candle, 0) + 1)
        else
            array.push(movement_size_green, change)
            array.push(count_green, 1)
            array.set(candle, 0, array.get(candle, 0) + 1)

    if close < open
        if array.includes(movement_size_red, change)
            index = array.indexof(movement_size_red, change)
            array.set(count_red, index, array.get(count_red, index) + 1)
            array.set(candle, 1, array.get(candle, 1) + 1)
        else
            array.push(movement_size_red, change)
            array.push(count_red, 1)
            array.set(candle, 1, array.get(candle, 1) + 1)

    probability_green = array.new<float>(array.size(count_green))
    probability_red   = array.new<float>(array.size(count_red))
    probability_can   = array.new<float>(2)

    count_green_sum = array.sum(count_green)
    count_red_sum = array.sum(count_red)
    count_can_sum = array.sum(candle)

    for i = 0 to array.size(count_green) - 1
        probability = array.get(count_green, i) / count_green_sum * 100
        array.set(probability_green, i, probability)

    for i = 0 to array.size(count_red) - 1
        probability = array.get(count_red, i) / count_red_sum * 100
        array.set(probability_red, i, probability)

    for i = 0 to 1
        probability = array.get(candle, i) / count_can_sum * 100
        array.set(probability_can, i, probability)
    [movement_size_green, movement_size_red, probability_green, probability_red, probability_can]


monte(steps, movement_size_green, movement_size_red, probability_green, probability_red, probability_can)=>
    // Initialize the array to store the Monte Carlo simulation results
    var monte_array = array.new<float>(steps)

    // Loop through the number of steps
    for i = 0 to steps - 1
        // Generate a random number to determine if it's a green or red move
        random_num_1 = math.random(0, 100, timenow)

        // Check if the random number is less than or equal to the green candle probability
        if random_num_1 <= array.get(probability_can, 0)
            // Pick a green move size based on its probability
            random_num = math.random(0, 100, timenow)
            var cumulative_probability = 0.0
            for j = 0 to array.size(probability_green) - 1
                cumulative_probability := nz(cumulative_probability[1]) + array.get(probability_green, j)
                if random_num <= cumulative_probability
                    // Get the corresponding green move size
                    move_size = array.get(movement_size_green, j)
                    // Add the move size to the monte_array
                    array.set(monte_array, i, move_size)

        else
            // Pick a red move size based on its probability
            random_num = math.random(0, 100, timenow)
            var cumulative_probability = 0.0
            for j = 0 to array.size(probability_red) - 1
                cumulative_probability := nz(cumulative_probability[1]) + array.get(probability_red, j)
                if random_num <= cumulative_probability
                    // Get the corresponding red move size
                    move_size = array.get(movement_size_red, j)
                    // Add the move size to the monte_array
                    array.set(monte_array, i, move_size)
    monte_array

sim(steps, movement_size_green, movement_size_red, probability_green, probability_red, probability_can) =>
    var simulation = array.new<float>(steps)
    movements = monte(steps, movement_size_green, movement_size_red, probability_green, probability_red, probability_can)
    for i = 1 to steps - 1
        array.set(simulation, 0, open)
        array.set(simulation, i, addLogReturn(array.get(simulation, i - 1), array.get(movements, i)))
    simulation

probability_of_monte(arb_price, sims, steps, movement_size_green, movement_size_red, probability_green, probability_red, probability_can)=>
    var sucess = array.new<int>(sims)
    output = array.new<float>(1)
    if arb_price > open
        for i = 0 to sims - 1
            sim = sim(steps, movement_size_green, movement_size_red, probability_green, probability_red, probability_can)
            if array.get(sim, steps - 1) >= arb_price
                array.set(sucess, i, 1)
            else
                array.set(sucess, i, 0)
        probability = array.sum(sucess)/sims * 100

    else
        for i = 0 to sims - 1
            sim = sim(steps, movement_size_green, movement_size_red, probability_green, probability_red, probability_can)
            if array.get(sim, steps - 1) <= arb_price
                array.set(sucess, i, 1)
            else
                array.set(sucess, i, 0)
        probability = array.sum(sucess)/sims * 100

var label barindex = na
var line lines = na
var label price = na

if show_bar_index
    barindex := label.new(bar_index + 1, arb_price, text = str.tostring(bar_index), style=label.style_label_down, textcolor = arb_price > open ? color.green : color.red, color=color.new(color.white, 100))

if bar_index > lookback
    [movement_size_green, movement_size_red, probability_green, probability_red, probability_can] = movement_probability(accuracy)
    probability = probability_of_monte(arb_price, sims, steps, movement_size_green, movement_size_red, probability_green, probability_red, probability_can)[1]
    lines := line.new(bar_index + steps, arb_price, bar_index + steps + 1, arb_price, color = arb_price > open ? color.green : color.red)
    price := label.new(bar_index + steps + 1, arb_price, text=str.tostring(math.round(probability, 2))+"%", style=label.style_label_down, textcolor = arb_price > open ? color.green : color.red, color=color.new(color.white, 100))

if barstate.isconfirmed
    label.delete(price)
    line.delete(lines)
    label.delete(barindex)