Library · Pine Script v5 · Library

RiskMetrics

by ivansoft· code credited to TradingView

A Pine Script v5 library that tracks compounded monthly return percentages in an array, capped at a maximum number of periods, so that ratio calculations can be built on them.

View on GitHub

GitHub stars1on the repository
Pine versionv5runs as is
Lines of code227
Settings3inputs
LicenceNone statedfile: MPL-2.0
Last update1 year ago1 Mar 2025

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 library, select all of the template text and paste. Press Save and give it a name.
  4. 4Publish or import itA library draws nothing. Publish it from the editor, then import its functions in your own scripts.

Source Code

227 lines of Pine Script v5 from edu_lib/risk_metrics.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

risk_metrics.pine
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TradingView

// @version=5
library("RiskMetrics")

// RiskMetrics Library
// v2, 2024.11.05

// This code was written using the recommendations from the Pine Script™ User Manual's Style guide:
//    https://www.tradingview.com/pine-script-docs/writing/style-guide/



//#region ———————————————————— Library functions


// @function                Adds a new `value` at the end of an `id` array, then removes the array's first element 
//                          if its size exceeds the specified `maxSize`. 
// @param id                (array<any type>) An array of any type to queue a new `value` into.
// @param maxSize           (simple int) The maximum size of the `id` array.
// @param value             (<type of the array's elements>) The value of the element pushed into the `id` array.
// @returns                 (<type of the array's elements>) The de-queued element value.
arrayQueue(id, int maxSize, value) =>
    id.push(value)
    if id.size() > maxSize
        id.shift()


// @function                Converts a UNIX timestamp in milliseconds to the total number of whole months since 
//                          midnight UTC on January 1, 1970.
// @param t                 (series int) The UNIX timestamp to evaluate.
// @returns                 (int) The total number of months elapsed from January 1, 1970 to the `t` timestamp.
months(series int t) =>
    int result = (year(t, "UTC") - 1970) * 12 + month(t, "UTC") - 1


// @function                (Overload 1 of 2) Tracks monthly return percentages and queues them into an array for ratio  
//                          calculations. 
// @param percentChange     (series float) The change percentage. The function compounds non-na values from each 
//                          chart bar within monthly periods to calculate the periodic changes. 
// @param maxPeriods        (simple int) The maximum number of monthly returns to store in the returned array.
// @returns                 (array<float>) An array containing the overall percentage changes for each month, limited 
//                          to the maximum specified by `maxPeriods`.
export getPeriodicReturns(series float percentChange, simple int maxPeriods) =>
    var array<float> result = array.new<float>()
    var bool   chartHasHistory = false
    var float  base         = 100.0
    var bool   isIntramonth = timeframe.in_seconds() <= timeframe.in_seconds("1M")
    bool       isNewPeriod  = ta.change(fixnan(time("1M", session = session.regular))) != 0
    int        monthChanges = na(time[1]) or isIntramonth ? 0 : months(time) - months(time[1])
    if not na(percentChange)
        base *= 1.0 + (percentChange / 100.0)
        chartHasHistory := true
    if isNewPeriod
        if chartHasHistory
            if monthChanges > 1
                for _ = 2 to monthChanges
                    arrayQueue(result, maxPeriods, 0.0)
            float periodicChange = base - 100.0
            arrayQueue(result, maxPeriods, periodicChange)
        base := 100.0
    result


// @function                (Overload 2 of 2) Tracks monthly excess return percentages and queues the values into an 
//                          array. 
// @param percentChange     (series float) The change percentage. The function compounds non-na values from each 
//                          chart bar within monthly periods to calculate the periodic changes. 
// @param benchmark         (series float) The benchmark percentage to compare against `percentChange` values. 
//                          The function compounds non-na values from each bar within monthly 
//                          periods and subtracts the results from the compounded `percentChange` values to 
//                          calculate the excess returns. For consistency, ensure this series has a similar history 
//                          length to the `percentChange` with aligned non-na value times. 
// @param maxPeriods        (simple int) The maximum number of monthly excess returns to store in the returned array.
// @returns                 (array<float>) An array containing monthly excess returns, limited 
//                          to the maximum specified by `maxPeriods`.
export getPeriodicReturns(series float percentChange, series float benchmark, simple int maxPeriods) =>
    var array<float> result = array.new<float>() 
    var bool   benchHasHistory = false
    var bool   chartHasHistory = false
    var float  base1        = 100.0
    var float  base2        = 100.0
    var bool   isIntramonth = timeframe.in_seconds() <= timeframe.in_seconds("1M")
    bool       isNewPeriod  = ta.change(fixnan(time("1M", session = session.regular))) != 0 
    int        monthChanges = na(time[1]) or isIntramonth ? 0 : months(time) - months(time[1])
    if not na(percentChange)
        base1 *= 1.0 + (percentChange / 100.0)
        chartHasHistory := true
    if not na(benchmark)
        base2 *= 1.0 + (benchmark / 100.0)
        benchHasHistory := true
    if isNewPeriod 
        if chartHasHistory and benchHasHistory 
            if monthChanges > 1
                for _ = 2 to monthChanges
                    arrayQueue(result, maxPeriods, 0.0)
            float periodicExcess = base1 - base2
            arrayQueue(result, maxPeriods, periodicExcess)
        base1 := 100.0
        base2 := 100.0
    result


// @function                Calculates the downside deviation of an array of `returns` compared to a `targetReturn`. 
//                          Callable as a method or a function.
// @param returns           (array<float>) An array containing the sequence of returns to analyze. 
// @param targetReturn      (series float) The benchmark return to compare against each value in the `returns` array.
// @returns                 (float) The downside deviation of compared returns, i.e., the volatility of all `returns` 
//                          values below the `targetReturn`.
method downsideDeviation(array<float> returns, series float targetReturn) =>
    float sum = 0.0
    for r in returns
        if r >= targetReturn
            continue
        sum += math.pow(r - targetReturn, 2)
    sum /= returns.size()
    float result = math.sqrt(sum)


// @function                Calculates the Sharpe ratio for an array of periodic returns.  
//                          Callable as a method or a function.
// @param returnsArray      (array<float>) An array of periodic return percentages, e.g., returns over monthly periods. 
// @param annualBenchmark   (series float) The annual rate of return to compare against `returnsArray` values. When 
//                          `periodsPerYear` is `na`, the function divides this value by 12 to calculate a 
//                          monthly benchmark. If `periodsPerYear` has a specified value, the function divides the rate
//                          by that value instead.
// @param forceCalc         (series bool) If `true`, calculates the ratio on every call. Otherwise, ratio calculation 
//                          only occurs on the last available bar. Optional. The default is `false`.
// @param periodsPerYear    (simple int) If specified, divides the annual rate by this value instead of the value  
//                          determined by the time span of the chart's data.
// @returns                 (float) The Sharpe ratio, which estimates the excess return per unit of total volatility. 
export method sharpeRatio( 
     array<float> returnsArray, series float annualBenchmark, series bool forceCalc = false, 
     simple int periodsPerYear = na 
 ) => 
    var int numberOfperiods = nz(periodsPerYear, 12)
    if barstate.islast or forceCalc
        float fixedPeriodReturn = annualBenchmark / numberOfperiods
        float standardDev       = returnsArray.stdev()
        float avgReturn         = returnsArray.avg()
        float result            = (avgReturn - fixedPeriodReturn) / standardDev
        


// @function                Calculates the Sortino ratio for an array of periodic returns.
//                          Callable as a method or a function.
// @param returnsArray      (array<float>) An array of periodic return percentages, e.g., returns over monthly periods. 
// @param annualBenchmark   (series float) The annual rate of return to compare against `returnsArray` values. When 
//                          `periodsPerYear` is `na`, the function divides this value by 12 to calculate a 
//                          monthly benchmark. If `periodsPerYear` has a specified value, the function divides the rate
//                          by that value instead.
// @param forceCalc         (series bool) If `true`, calculates the ratio on every call. Otherwise, ratio calculation 
//                          only occurs on the last available bar. Optional. The default is `false`.
// @param periodsPerYear    (simple int) If specified, divides the annual rate by this value instead of the value  
//                          determined by the time span of the chart's data.
// @returns                 (float) The Sortino ratio, which estimates the excess return per unit of downside 
//                          volatility.
export method sortinoRatio( 
     array<float> returnsArray, series float annualBenchmark, series bool forceCalc = false, 
     simple int periodsPerYear = na 
 ) => 
    var int numberOfperiods = nz(periodsPerYear, 12)
    if barstate.islast or forceCalc
        float fixedPeriodReturn = annualBenchmark / numberOfperiods
        float downsideDev       = returnsArray.downsideDeviation(fixedPeriodReturn)
        float avgReturn         = returnsArray.avg()
        float result            = (avgReturn - fixedPeriodReturn) / downsideDev 
//#endregion



//#region ———————————————————— Example Code


// Specify the timeframe of the returns, the annual benchmark percentage, and the max number of periods to analyze.
string tfInput         = input.timeframe("1M", "Returns timeframe") 
float  percentageInput = input.float(2,        "Benchmark percentage (Annual)", minval = 0) 
int    numOfTFsInput   = input.int(60,         "Max no. of periods used") 

// @variable `true` if the selected timeframe is higher than the current chart's timeframe. 
var bool isHtf = timeframe.in_seconds(tfInput) > timeframe.in_seconds()

// @variable The one-bar arithmetic `close` return. Calculates the return from `open` to `close` on the first bar.
float returnPercent = nz(ta.roc(close, 1), 100.0 * (close - open) / open)
// @variable The last confirmed `returnPercent` value from the `tfInput` timeframe with gaps. 
float htfReturn = request.security(syminfo.tickerid, tfInput, returnPercent[1], barmerge.gaps_on, barmerge.lookahead_on)
// @variable The chart symbol's sampled percent change value.
float changeValue = isHtf ? htfReturn : returnPercent

// @variable An array of periodic returns over up to the specified number of periods.
array<float> periodicReturns = getPeriodicReturns(changeValue, numOfTFsInput)
// @variable The Sharpe ratio of the `periodicReturns` based on the benchmark percentage. 
float sharpe = sharpeRatio(periodicReturns,  percentageInput)
// @variable The Sortino ratio of the `periodicReturns` based on the benchmark percentage. 
float sortino = sortinoRatio(periodicReturns, percentageInput)

// Raise an error if the `tfInput` is smaller than the chart's timeframe.
if timeframe.in_seconds(tfInput) < timeframe.in_seconds()
    runtime.error("For accurate ratios, the requested TF must be greater than or equal to the TF of the chart.")

// Plot the `changeValue` series with masked `na` values and fill the space between it and 0 with the `plotColor`.
float plotValue = fixnan(changeValue)
color plotColor = not na(changeValue) and isHtf ? color(na) : plotValue < 0.0 ? color.orange : color.aqua
changePlot      = plot(plotValue, "Sampled price return",  plotColor, precision = 2, format = format.percent)
zeroPlot        = plot(0,         "Zero level",          color.new(color.gray, 60))
fill(changePlot, zeroPlot, color.new(plotColor, 80), "Price return fill")

// Display the base period, number of periods, returns timeframe, and the calculated ratios in a table on the last bar.
if barstate.islast
    var table tfDisplay = table.new(position.bottom_right, 1, 1)
    string periodStr = "1M" 
    string tableTxt  = str.format(
         "Calculation base period:  {0}
         \n# of calculation periods: {1}
         \nReturns timeframe:        {2}
         \nSharpe ratio:             {3}
         \nSortino ratio:            {4}",
         periodStr, periodicReturns.size(), 
         tfInput == "" ? (timeframe.isdwm and timeframe.multiplier == 1 ? "1" : "") + timeframe.period : tfInput, 
         str.tostring(sharpe, "#.#####"), str.tostring(sortino, "#.#####")
     )
    table.cell(
         tfDisplay, 0, 0, tableTxt, bgcolor = color(na), text_color = chart.fg_color, text_size = size.normal,
         text_halign = text.align_left, text_font_family = font.family_monospace
     )
//#endregion

Overview

RiskMetrics has an overload for excess returns over a benchmark. Settings include the returns timeframe, an annual benchmark percentage and the maximum number of periods kept. It draws nothing itself and is imported by other scripts; results depend on how much history the chart has.

Details

Published by

ivansoft on GitHub

Code credit

TradingView, as written in the script's header

Draws

Library (no plots)

Markets

Any symbol

Timeframes

H4 and above

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.

Topics

pinescripttradingvew

Settings

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

SettingTypeDefaultVariable
Returns timeframetimeframe1MtfInput
Benchmark percentage (Annual)float2percentageInput
Max no. of periods usedint60numOfTFsInput
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

261 stars

AIActivationFunctionsBipolarSigmoid is a Pine Script library that provides mathematical implementations of the bipolar sigmoid activation function and its derivatives for machine learning algorithms. It calculates bipolar sigmoid values bounded between -1 and

Library v5 AI / machine learning
Updated 4 years ago · None stated GitHub repository of AIActivationFunctionsBipolarSigmoid

KernelFunctions

by artificial-intelligence-edge
75 stars

KernelFunctions is a Pine Script v5 library that exports kernel weight functions for Nadaraya-Watson style estimation. It calculates a Rational Quadratic kernel, a Gaussian kernel based on a radial basis function, and a periodic kernel that models repeating be

Library v5
Updated 3 days ago · MIT GitHub repository of KernelFunctions

Ta

by nersent
29 stars

This Pine Script v5 library exports thin wrappers around built-in technical analysis functions. It covers moving averages, momentum measures such as RSI and CCI, volatility tools including ATR and standard deviation, range extremes, cross detection, and runnin

Library v5
Updated 11 months ago · Custom licence GitHub repository of Ta

Bands

by heyphat
26 stars

This library exports a Band type and functions that build a simple-moving-average basis with upper and lower envelopes from standard deviation. It classifies a source as above, below, or inside those envelopes, measures band width, and scales an imported z-sco

Library v6 Volatility & bandsMoving averages
Updated 2 months ago · AGPL-3.0 GitHub repository of Bands

Mylib

by ferranbt
21 stars

This script serves as a minimal utility library written in Pine Script v6. It provides an exported helper function that calculates the sum of two numeric arguments. As a reusable code library, it produces no visual plots, charts, or trading signals, and it con

Library v6
Updated 7 weeks ago · MPL-2.0 GitHub repository of Mylib

HLevel

by stupenkov
14 stars

HLevel is a Pine Script v5 utility library designed to manage horizontal price levels for cryptocurrency charts. It defines custom types for pivots and levels, along with methods to initialize, add, track, and expire price levels based on pivot high and pivot

Library v5
Updated 3 years ago · None stated GitHub repository of HLevel

More libraries

Related on ForexR

Trade Your Charts With a Regulated Broker

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

Compare brokers