Porting Daily Range indicator from PineScript to Indie

This post shows how to port a Pine Script indicator to TakeProfit’s Indie language. As an example, I used the Daily Range indicator by ©AndreSolbach — a popular tool on TradingView with ~1.7K stars.

Daily Range (50) on TakeProfit platform
Daily Range (50) on TakeProfit platform

The latest version of the Daily Range script on TradingView looks like this:

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/// © AndreSolbach//@version=5indicator(title="Daily Range", precision=2, overlay=true)days_used_for_calculation = input.int(defval=50, title="Days used for calculation", minval=1, tooltip="How many past days should be used to calculate the Daily Range", group="Settings")text_color = input.color(defval=color.rgb(233, 30, 99, 0), title="Text color", tooltip="Color of the Daily Range value", group="Settings")block_mult = 0.0var1 = falseif barstate.islast    var1 := true    if (syminfo.mintick == 0.00001) or ((syminfo.mintick == 0.001) and str.contains(syminfo.ticker, "JPY"))        block_mult := syminfo.mintick * 10    else if (syminfo.mintick == 0.01)        block_mult := syminfo.mintick * 100    else if (syminfo.mintick == 0.0000001)        block_mult := syminfo.mintick * 10000000            else if (syminfo.mintick == 1.0)                block_mult := syminfo.mintick * 1    else                block_mult := syminfo.mintick / syminfo.mintickcalcResult_ = ((1 / block_mult) * (math.sum(high-low, days_used_for_calculation)) / (days_used_for_calculation))calcResult = request.security(syminfo.tickerid, "D", calcResult_[0] , barmerge.gaps_off, barmerge.lookahead_on)plot(var1 ? calcResult : na, color=text_color, display=display.status_line, editable=false)

The first step in rewriting it in Indie is to create a class Main (decorated with @indicator) for the main instrument and a function HighLowSma (decorated with @sec_context) for the 1-day time frame. Accordingly, the code that calculates calcResult_ should be placed in HighLowSma:

@sec_contextdef HighLowSma(self):    # calculating calcResult_@indicator('Daily Range')class Main(MainContext):    def __init__(self):        self._high_low_sma = self.calc_on(time_frame=TimeFrame.from_str('1D'), sec_context=HighLowSma, lookahead=True)        def calc(self):        return self._high_low_sma[0]

Using Indie syntax, the body of HighLowSma becomes this:

def HighLowSma(self, days_used_for_calculation):    block_mult = nan    if self.is_last_bar:        if self.info.tick_size == 0.00001 or (self.info.tick_size == 0.001 and 'JPY' in self.info.ticker):            block_mult = self.info.tick_size * 10        elif self.info.tick_size == 0.01:            block_mult = self.info.tick_size * 100        elif self.info.tick_size == 0.0000001:            block_mult = self.info.tick_size * 10000000        elif self.info.tick_size == 1.0:            block_mult = self.info.tick_size * 1        else:            block_mult = self.info.tick_size / self.info.tick_size    return 1 / block_mult * Sum.new(MutSeriesF.new(self.high[0] - self.low[0]), days_used_for_calculation)[0] / days_used_for_calculation

After adding all the necessary decorators and imports, the code becomes this:

# indie:lang_version = 5from math import nanfrom indie import indicator, sec_context, param, format, MutSeriesF, param_ref, MainContext, TimeFrame, plot, colorfrom indie.algorithms import Sum@sec_context@param_ref('days_used_for_calculation')def HighLowSma(self, days_used_for_calculation):    block_mult = nan    if self.is_last_bar:        if self.info.tick_size == 0.00001 or self.info.tick_size == 0.001 and 'JPY' in self.info.ticker:            block_mult = self.info.tick_size * 10        elif self.info.tick_size == 0.01:            block_mult = self.info.tick_size * 100        elif self.info.tick_size == 0.0000001:            block_mult = self.info.tick_size * 10000000        elif self.info.tick_size == 1.0:            block_mult = self.info.tick_size * 1        else:            block_mult = self.info.tick_size / self.info.tick_size    return 1 / block_mult * Sum.new(MutSeriesF.new(self.high[0] - self.low[0]), days_used_for_calculation)[0] / days_used_for_calculation@indicator('Daily Range', format=format.PRICE, precision=2, overlay_main_pane=True)@param.int('days_used_for_calculation', default=50, min=1, title='Days used for calculation')@plot.line(color=color.rgba(233, 30, 99), display_options=plot.LineDisplayOptions(status_line=True))class Main(MainContext):    def __init__(self):        self._high_low_sma = self.calc_on(time_frame=TimeFrame.from_str('1D'), sec_context=HighLowSma, lookahead=True)        def calc(self):        return self._high_low_sma[0] if self.is_last_bar else nan

Note: This indicator requests daily data for 50 days, so on lower timeframes (e.g., 1 minute), there may not be enough historical bars, resulting in nan values in the indicator’s output data. On a 1-hour chart, however, the history depth should be sufficient to cover the requested period without issues.

In addition, the HighLowSma code can be simplified: all the if statements except the first one set block_mult to 1.0, and Sum.new(..., len)[0] / len can be rewritten as Sma.new(..., len)[0]. With a little refactoring, the code can become:

# indie:lang_version = 5from math import nanfrom indie import indicator, sec_context, param, format, MutSeriesF, param_ref, MainContext, TimeFrame, plot, colorfrom indie.algorithms import Sma@sec_context@param_ref('days_used_for_calculation')def HighLowSma(self, days_used_for_calculation):    block_mult = nan    if self.is_last_bar:        block_mult = 1.0        if self.info.tick_size == 0.00001 or self.info.tick_size == 0.001 and 'JPY' in self.info.ticker:            block_mult = self.info.tick_size * 10    return 1 / block_mult * Sma.new(MutSeriesF.new(self.high[0] - self.low[0]), days_used_for_calculation)[0]@indicator('Daily Range', format=format.PRICE, precision=2, overlay_main_pane=True)@param.int('days_used_for_calculation', default=50, min=1, title='Days used for calculation')@plot.line(color=color.rgba(233, 30, 99), display_options=plot.LineDisplayOptions(status_line=True))class Main(MainContext):    def __init__(self):        self._high_low_sma = self.calc_on(time_frame=TimeFrame.from_str('1D'), sec_context=HighLowSma, lookahead=True)        def calc(self):        return self._high_low_sma[0] if self.is_last_bar else nan

With that, the Daily Range indicator is fully ported to Indie. The final version is cleaner, easier to maintain, and uses Indie’s composable architecture for clarity and efficiency. Now it’s ready for production use or further customization.

indie
pinescript
educational
porting_to_indie
daily_range

Comments
No Comments yet image

Be the first to comment

Publish your first comment to unleash the wisdom of crowd.