Indicator · Pine Script v5 · Own pane

Modern Portfolio Theory

by regalouisei· code credited to VanHe1sing

Modern Portfolio Theory is an indicator that computes portfolio metrics across a basket of cryptocurrency assets using user specified weights.

View on GitHub

GitHub stars2on the repository
Pine versionv5runs as is
Lines of code254
Settings33inputs
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

254 lines of Pine Script v5 from pinescript/editors_picks/modern-portfolio-theory.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

modern-portfolio-theory.pine
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © VanHe1sing

//@version=5
indicator("Modern Portfolio Theory", shorttitle = "[MPT]", max_labels_count = 500)

//======================================================================================================================}
// - User Inputs
//======================================================================================================================{

int BarScale = input(150, "Bar Scale")
float rfr    = input.float(0.048, "Risk-Free Rate")
int strt_year= input(2010, "Portfolio Start Year")

data         = timestamp(strt_year, 01, 01, 0, 0)

float weight1 = input.float(10, "Weight 1", group = "Weight's", inline = "W") / 100
float weight2 = input.float(22, "Weight 2", group = "Weight's", inline = "W") / 100
float weight3 = input.float(5, "Weight 3", group = "Weight's", inline = "W") / 100
float weight4 = input.float(27, "Weight 4", group = "Weight's", inline = "W") / 100
float weight5 = input.float(10, "Weight 5", group = "Weight's", inline = "W") / 100
float weight6 = input.float(5, "Weight 6", group = "Weight's", inline = "W") / 100
float weight7 = input.float(3, "Weight 7", group = "Weight's", inline = "W") / 100
float weight8 = input.float(3, "Weight 8", group = "Weight's", inline = "W") / 100
float weight9 = input.float(5, "Weight 9", group = "Weight's", inline = "W") / 100
float weight10= input.float(10, "Weight 10", group = "Weight's", inline = "W") / 100

bool use_sym1 = input.bool(true, "Use Symbol 1?", inline = "Use?")
bool use_sym2 = input.bool(true, "Use Symbol 2?", inline = "Use?")
bool use_sym3 = input.bool(true, "Use Symbol 3?", inline = "Use?")
bool use_sym4 = input.bool(true, "Use Symbol 4?", inline = "Use?")
bool use_sym5 = input.bool(true, "Use Symbol 5?", inline = "Use?")
bool use_sym6 = input.bool(true, "Use Symbol 6?", inline = "Use?")
bool use_sym7 = input.bool(true, "Use Symbol 7?", inline = "Use?")
bool use_sym8 = input.bool(true, "Use Symbol 8?", inline = "Use?")
bool use_sym9 = input.bool(true, "Use Symbol 9?", inline = "Use?")
bool use_sym10= input.bool(true, "Use Symbol 10?", inline = "Use?")

sym1 =     input.symbol("    INDEX:BTCUSD    ", "    Symbol 1    ", inline = "Sym", group = "Symbol's")
sym2 =     input.symbol("    INDEX:ETHUSD    ", "    Symbol 2    ", inline = "Sym", group = "Symbol's")
sym3 =     input.symbol("    BINANCE:AAVEUSD    ", "    Symbol 3    ", inline = "Sym", group = "Symbol's")
sym4 =     input.symbol("    BINANCE:BNBUSD    ", "    Symbol 4    ", inline = "Sym", group = "Symbol's")
sym5 =     input.symbol("    BINANCE:SOLUSD    ", "    Symbol 5    ", inline = "Sym", group = "Symbol's")
sym6 =     input.symbol("    BINANCE:XRPUSD    ", "    Symbol 6    ", inline = "Sym", group = "Symbol's")
sym7 =     input.symbol("    BINANCE:ETCUSD    ", "    Symbol 7    ", inline = "Sym", group = "Symbol's")
sym8 =     input.symbol("    BINANCE:ADAUSD    ", "    Symbol 8    ", inline = "Sym", group = "Symbol's")
sym9 =     input.symbol("    BINANCE:AVAXUSD    ", "    Symbol 9    ", inline = "Sym", group = "Symbol's")
sym10 = input.symbol("    BINANCE:RUNEUSD    ", "    Symbol 10    ", inline = "Sym", group = "Symbol's")

//======================================================================================================================}
// - Declaration of Functions
//======================================================================================================================{
// Clean symbol from exchange name
clean_symbol(symbol)=>
    sym = str.split(symbol, ":")
    sym.get(1)

// Risk and Return Function
risk_return(src)=>
    var returns_array   = array.new_float(0)
    float root          = math.sqrt(365)

    if time >= data
        daily_return = 0.0
        //Daily Asset Returns 
        //formula -> (previous_value/current_value - 1)
        daily_return := src/src[1] - 1
        returns_array.push(daily_return)

    X = returns_array.stdev()*root
    Y = returns_array.avg()*root

    [X, Y]

// Scatter Plot
scatter(float xPosition, float yPosition, color  Color,
             int BarScale, string symbol, nodeChar = "⦿", nodeSize1 =  size.small, nodeSize =  size.normal) =>
    if barstate.islast
        float Y2 =     nz(yPosition)
        int   X2 = int(nz(xPosition) * BarScale)
        X2 := math.min(500, X2) + bar_index
   
        node = label.new(X2, Y2, nodeChar,  size=nodeSize,
                             color=#00000000, textcolor=Color,
                             style=label.style_label_center)

        nodeChar1 =  symbol
        label.new(X2+10, Y2, nodeChar1,  size=nodeSize1,
                         color=#00000000, textcolor=color.new(Color, 50),
                         style=label.style_label_center)

        label.new(bar_index, Y2, text= symbol + "  " + str.tostring(math.round(yPosition,3)), 
                 style = label.style_label_right, size = size.small, color = Color, textcolor = #000000)

        label.new(X2, 0, text= str.tostring(math.round(xPosition-1,2)), 
                 style = label.style_label_up, size = size.small, color = Color, textcolor = #000000)

//======================================================================================================================}
// - X & Y Values
//======================================================================================================================{
[    X1    ,    Y1    ] = request.security(    sym1    ,"", risk_return(close))
[    X2    ,    Y2    ] = request.security(    sym2    ,"", risk_return(close))
[    X3    ,    Y3    ] = request.security(    sym3    ,"", risk_return(close))
[    X4    ,    Y4    ] = request.security(    sym4    ,"", risk_return(close))
[    X5    ,    Y5    ] = request.security(    sym5    ,"", risk_return(close))
[    X6    ,    Y6    ] = request.security(    sym6    ,"", risk_return(close))
[    X7    ,    Y7    ] = request.security(    sym7    ,"", risk_return(close))
[    X8    ,    Y8    ] = request.security(    sym8    ,"", risk_return(close))
[    X9    ,    Y9    ] = request.security(    sym9    ,"", risk_return(close))
[    X10    ,    Y10    ] = request.security(    sym10    ,"", risk_return(close))

//======================================================================================================================}
// - Plots and AVG Portfolio ★
//======================================================================================================================{

var tbl = table.new(position.bottom_right, 15, 15, frame_color = #474747, frame_width = 2)

table.cell(tbl, 0, 0, "Symbol's", text_color = #f3f3f3)
table.cell(tbl, 0, 1, clean_symbol(    sym1    ), text_color = color.gray)
table.cell(tbl, 0, 2, clean_symbol(    sym2    ), text_color = color.gray)
table.cell(tbl, 0, 3, clean_symbol(    sym3    ), text_color = color.gray)
table.cell(tbl, 0, 4, clean_symbol(    sym4    ), text_color = color.gray)
table.cell(tbl, 0, 5, clean_symbol(    sym5    ), text_color = color.gray)
table.cell(tbl, 0, 6, clean_symbol(    sym6    ), text_color = color.gray)
table.cell(tbl, 0, 7, clean_symbol(    sym7    ), text_color = color.gray)
table.cell(tbl, 0, 8, clean_symbol(    sym8    ), text_color = color.gray)
table.cell(tbl, 0, 9, clean_symbol(    sym9    ), text_color = color.gray)
table.cell(tbl, 0, 10,clean_symbol(    sym10    ), text_color = color.gray)

table.cell(tbl, 1, 0, "Weight's", text_color = #f3f3f3)
table.cell(tbl, 1, 1, str.tostring(use_sym1 ? weight1*100 : 0, format.percent), text_color = color.gray)
table.cell(tbl, 1, 2, str.tostring(use_sym2 ?weight2*100 : 0, format.percent), text_color = color.gray)
table.cell(tbl, 1, 3, str.tostring(use_sym3 ?weight3*100 : 0, format.percent), text_color = color.gray)
table.cell(tbl, 1, 4, str.tostring(use_sym4 ?weight4*100 : 0, format.percent), text_color = color.gray)
table.cell(tbl, 1, 5, str.tostring(use_sym5 ?weight5*100 : 0, format.percent), text_color = color.gray)
table.cell(tbl, 1, 6, str.tostring(use_sym6 ?weight6*100 : 0, format.percent), text_color = color.gray)
table.cell(tbl, 1, 7, str.tostring(use_sym7 ?weight7*100 : 0, format.percent), text_color = color.gray)
table.cell(tbl, 1, 8, str.tostring(use_sym8 ?weight8*100 : 0, format.percent), text_color = color.gray)
table.cell(tbl, 1, 9, str.tostring(use_sym9 ?weight9*100 : 0, format.percent), text_color = color.gray)
table.cell(tbl, 1, 10,str.tostring(use_sym10 ?weight10*100 : 0, format.percent), text_color = color.gray)

table.cell(tbl, 0, 11, "Total", text_color = #f3f3f3)

sum = ((use_sym1 ? weight1 : 0)
     + (use_sym2 ? weight2 : 0)
     + (use_sym3 ? weight3 : 0)
     + (use_sym4 ? weight4 : 0)
     + (use_sym5 ? weight5 : 0)
     + (use_sym6 ? weight6 : 0)
     + (use_sym7 ? weight7 : 0)
     + (use_sym8 ? weight8 : 0)
     + (use_sym9 ? weight9 : 0)
     + (use_sym10 ? weight10 : 0)) * 100

table.cell(tbl, 1, 11, 
     str.tostring(sum, format.percent), 
     text_color = sum < 100 ? color.orange : sum == 100 ? color.green : color.red, 
     text_size = sum == 100 ? size.large : sum > 100 ? size.huge : size.large)
if sum < 100
    table.cell(tbl, 0, 12, "Cash", text_color = #f3f3f3)
    table.cell(tbl, 1, 12, str.tostring(100-sum, format.percent), text_color = #f3f3f3, text_size = size.large)

//Gradient colors of scatter plot marks
y_max = math.max(Y1, Y2, Y3,Y4,Y5,Y6,Y7,Y8,Y9,Y10)

y_min = math.min(Y1, Y2, Y3,Y4,Y5,Y6,Y7,Y8,Y9,Y10)

gradient(val)=>
    color_ = color.from_gradient(val, y_min,
                                 y_max, color.rgb(135, 223, 138), color.aqua)

color_1= gradient(    Y1    ), scatter(    X1    ,    Y1    ,    color_1    , BarScale, clean_symbol(    sym1    ))
color_2= gradient(    Y2    ), scatter(    X2    ,    Y2    ,    color_2    , BarScale, clean_symbol(    sym2    ))
color_3= gradient(    Y3    ), scatter(    X3    ,    Y3    ,    color_3    , BarScale, clean_symbol(    sym3    ))
color_4= gradient(    Y4    ), scatter(    X4    ,    Y4    ,    color_4    , BarScale, clean_symbol(    sym4    ))
color_5= gradient(    Y5    ), scatter(    X5    ,    Y5    ,    color_5    , BarScale, clean_symbol(    sym5    ))
color_6= gradient(    Y6    ), scatter(    X6    ,    Y6    ,    color_6    , BarScale, clean_symbol(    sym6    ))
color_7= gradient(    Y7    ), scatter(    X7    ,    Y7    ,    color_7    , BarScale, clean_symbol(    sym7    ))
color_8= gradient(    Y8    ), scatter(    X8    ,    Y8    ,    color_8    , BarScale, clean_symbol(    sym8    ))
color_9= gradient(    Y9    ), scatter(    X9    ,    Y9    ,    color_9    , BarScale, clean_symbol(    sym9    ))
color_10= gradient(    Y10    ), scatter(    X10    ,    Y10    ,    color_10, BarScale, clean_symbol(    sym10    ))

// Portfolio

// Risk and Return of Portfolio
x_portfolio = (use_sym1 ? X1*weight1 : 0)
 + (use_sym2 ? X2*weight2 : 0)
 + (use_sym3 ? X3*weight3 : 0)
 + (use_sym4 ? X4*weight4 : 0)
 + (use_sym5 ? X5*weight5 : 0)
 + (use_sym6 ? X6*weight6 : 0)
 + (use_sym7 ? X7*weight7 : 0)
 + (use_sym8 ? X8*weight8 : 0)
 + (use_sym9 ? X9*weight9 : 0)
 + (use_sym10 ? X10*weight10 : 0)

y_portfolio = (use_sym1 ? Y1*weight1 : 0)  
 + (use_sym2 ? Y2*weight2 : 0)
 + (use_sym3 ? Y3*weight3 : 0)
 + (use_sym4 ? Y4*weight4 : 0)
 + (use_sym5 ? Y5*weight5 : 0)
 + (use_sym6 ? Y6*weight6 : 0)
 + (use_sym7 ? Y7*weight7 : 0)
 + (use_sym8 ? Y8*weight8 : 0)
 + (use_sym9 ? Y9*weight9 : 0)
 + (use_sym10 ? Y10*weight10 : 0)


// Plot of Portfolio
scatter(x_portfolio, y_portfolio, color.red, BarScale, "Portfolio", "★", size.small, size.huge)

// Axis lines with labels
if barstate.islast
    x_max = math.round(math.max(X1,X2,X3,X4,X5,X6,X7,X8,X9,X10)*250)
    y_max := y_max+(y_max/10)

    X = bar_index
    X2 = int(nz(x_portfolio) * BarScale)
    X2 := math.min(500, X2) + bar_index

    // Background
    box.new(X+x_max, y_max, X, 0, #ffffff00, bgcolor = #ffffff07)

    // Return Axis
    label.new(X, y_max, "RETURN (Mean Daily Returns)", textcolor = #d6d6d6, style = label.style_none)
    line.new(X, 0.0, X, y_max, color=color.gray,
                     style=line.style_solid,  width=1)
 
    // Risk Axis
    label.new(X+x_max, 0, "RISK (Standart Deviation)", textcolor = #d6d6d6, style = label.style_none)
    line.new(X+x_max, 0.0, X, 0.0, color=color.gray,
                     style=line.style_solid,  width=1)

    // Legend
    y_max := y_max - (y_max/10)
    x_max := x_max + 50
    label.new(X+x_max, y_max, "  ★ - Manually Optimized Portfolio", textcolor = color.red, style = label.style_none)
    label.new(X+x_max, y_max-y_max/35, "⦿ - Coins (X - Risk, Y - Return)", textcolor = color.rgb(82, 255, 232), style = label.style_none)
    label.new(X+x_max, y_max-y_max/15, "Portfolio from " + str.tostring(strt_year), textcolor = #6b6b6b, style = label.style_none)

    // Capital allocation line (CAL)
    line.new(X2, 0.0, X2, y_portfolio, color=#474747,
                     style=line.style_dotted,  width=1)
                     
    line.new(X2, y_portfolio, X, y_portfolio, color=#474747,
                     style=line.style_dotted,  width=1)
    //(CAL) and risk-free rate
    line.new(X2, y_portfolio, X, 0+rfr, color=#474747,
                     style=line.style_dashed,  width=1)

    label.new(X+15, 0+rfr, "Risk-Free Rate \n" + str.tostring(rfr*100, format.percent), textcolor = #d6d6d6, style = label.style_none)

    
//★

Overview

The script supports ten symbols including Bitcoin, Ethereum, Aave, Binance, Solana, XRP, Ethereum Classic, Cardano, Avalanche and Rune. Inputs control symbol inclusion, weight values, portfolio start year and bar scale. Results appear in a separate pane. The indicator provides no built in exit signals or position management.

Details

Published by

regalouisei on GitHub

Code credit

VanHe1sing, as written in the script's header

Type

Indicator, Pine Script v5

Draws

Separate pane below the chart

Category

Volatility & bands, Support & resistance

Markets

Crypto

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
Bar Scaleint150BarScale
Risk-Free Ratefloat0.048rfr
Portfolio Start Yearint2010strt_year
Weight 1float10weight1
Weight 2float22weight2
Weight 3float5weight3
Weight 4float27weight4
Weight 5float10weight5
Weight 6float5weight6
Weight 7float3weight7
Weight 8float3weight8
Weight 9float5weight9
Show all 33 settings
SettingTypeDefaultVariable
Weight 10float10weight10
Use Symbol 1?booltrueuse_sym1
Use Symbol 2?booltrueuse_sym2
Use Symbol 3?booltrueuse_sym3
Use Symbol 4?booltrueuse_sym4
Use Symbol 5?booltrueuse_sym5
Use Symbol 6?booltrueuse_sym6
Use Symbol 7?booltrueuse_sym7
Use Symbol 8?booltrueuse_sym8
Use Symbol 9?booltrueuse_sym9
Use Symbol 10?booltrueuse_sym10
Symbol 1 symbol INDEX:BTCUSD sym1
Symbol 2 symbol INDEX:ETHUSD sym2
Symbol 3 symbol BINANCE:AAVEUSD sym3
Symbol 4 symbol BINANCE:BNBUSD sym4
Symbol 5 symbol BINANCE:SOLUSD sym5
Symbol 6 symbol BINANCE:XRPUSD sym6
Symbol 7 symbol BINANCE:ETCUSD sym7
Symbol 8 symbol BINANCE:ADAUSD sym8
Symbol 9 symbol BINANCE:AVAXUSD sym9
Symbol 10 symbol BINANCE:RUNEUSD sym10
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

CCI DC

by be-thomas
65 stars

This indicator plots the Commodity Channel Index in a separate pane and surrounds it with a Donchian channel whose lookback length changes with volatility. It measures volatility as a ratio of short and long ATR values, then ranks that ratio and shortens or le

Indicator v5 Oscillators & momentumVolatility & bands
Updated 4 days ago · GPL-3.0 GitHub repository of CCI DC

Trailing Drawdown

by palitojendthen
20 stars

The Trailing Drawdown indicator calculates and visualizes the percentage drop of a price series from its peak over a user-specified lookback period. It tracks maximum price levels within a rolling window and measures the current deviation from those peaks. Tra

Indicator v5 Volatility & bandsSupport & resistance
Updated 1 year ago · None stated GitHub repository of Trailing Drawdown

Market Pressure Scale plots two oscillator curves in a separate pane. Setup pressure combines ATR and Bollinger compression, support and resistance proximity, and drying volume. Impulse pressure combines range expansion, volume, and candle body size. A range-c

Indicator v6 Oscillators & momentumVolatility & bands
Updated yesterday · MIT GitHub repository of Market Pressure Scale [WavesUnchained]

QFA Pro Elite Refined

by JasonTeixeira
9 stars

This indicator plots a smoothed fear index in a separate pane. It measures how far the current low sits below the highest close over a lookback, scales that reading by an ATR percentile, and smooths it with a zero-lag EMA. A volatility regime raises or lowers

Indicator v6 Oscillators & momentumVolatility & bands
Updated 12 days ago · Custom licence GitHub repository of QFA Pro Elite Refined
7 stars

The Per Bak Self-Organized Criticality indicator calculates a composite fragility score by blending weights for tail risk from the SKEW index, volatility regimes from VIX terms, credit stress from HYG and LQD, and positioning from short volume data. It plots t

Indicator v5 Volatility & bandsSupport & resistance
Updated 3 months ago · None stated GitHub repository of Per Bak Self-Organized Criticality

MACD Breakout

by jerryinyang
5 stars

This indicator plots a higher-timeframe MACD breakout setup on the price chart and combines it with linear regression channels. It marks support and resistance zones from MACD histogram sensitivity, then filters them with the MACD zero line, a linear regressio

Indicator v4 Oscillators & momentumBreakout
Updated 2 years ago · None stated GitHub repository of MACD Breakout

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