Indicator · Pine Script v5 · On the chart

Markov-HMM Regime v5

by flaviodesalvo54-del

Markov-HMM Regime v5 classifies each bar as Bull, Bear or Sideways using a hidden Markov model whose parameters were trained offline in Python and pasted into the script.

View on GitHub

GitHub stars1on the repository
Pine versionv5runs as is
Lines of code630
Settings26inputs
LicenceMIT
Last update4 months ago9 Jun 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

630 lines of Pine Script v5 from indicator/markov_hmm_v5_auto.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

markov_hmm_v5_auto.pine
//@version=5
// ═══════════════════════════════════════════════════════════════════
//  Markov-HMM Regime Detector v5 (Auto-Timeframe)
//  ────────────────────────────────────────────────
//  A Hidden Markov Model regime classifier for NQ futures.
//
//  Architecture:
//    Python (Baum-Welch / EM) trains the model offline → parameters
//    are hardcoded here → PineScript runs the Forward algorithm in
//    real time to decode the most probable regime each bar.
//
//  States: Bull (1) · Bear (2) · Sideways (0)
//  Features: z-scored return · volatility ratio · volume ratio
//
//  The indicator auto-detects the chart timeframe and loads the
//  matching parameter set (5m / 15m / 1h). Retrain periodically
//  with: python3 trainer/nq_hmm_trainer.py
//
//  Author: Flavio De Salvo
//  Source: https://github.com/[your-username]/nq-hmm-regime-detector
// ═══════════════════════════════════════════════════════════════════

indicator("Markov-HMM Regime v5", overlay=true, max_labels_count=500, max_bars_back=5000)

// ─── Inputs ─────────────────────────────────────────────────────

grp_logic    = "Regime logic"
grp_advanced = "Advanced"
grp_display  = "Display"
grp_position = "Table positions"

fast_lookback      = input.int(4, "Fast lookback", minval=2, maxval=60, group=grp_logic)
tf_minutes         = timeframe.in_seconds() / 60
auto_fast_th       = tf_minutes >= 1440 ? 2.5 : tf_minutes >= 240 ? 0.75 : tf_minutes >= 60 ? 0.3 : tf_minutes >= 15 ? 0.12 : tf_minutes >= 5 ? 0.07 : 0.05
fast_threshold_pct = input.float(0.12, "Fast threshold (%)", minval=0.01, maxval=50.0, step=0.01, group=grp_logic)
use_auto_fast      = input.bool(true, "Auto-detect fast threshold", group=grp_logic)
stationary_power   = input.int(50, "Stationary power", minval=10, maxval=200, group=grp_logic)
min_samples        = input.int(8, "Min samples", minval=3, maxval=50, group=grp_logic)

decay_factor       = input.float(0.997, "Transition decay", minval=0.95, maxval=0.9999, step=0.001, group=grp_advanced)
vol_confirm_mult   = input.float(1.0, "Volume confirm mult", minval=0.5, maxval=3.0, step=0.1, group=grp_advanced)
vol_sma_len        = input.int(20, "Volume SMA for confirm", minval=5, maxval=100, group=grp_advanced)

show_ribbon     = input.bool(true, "Regime ribbon", group=grp_display)
show_banner     = input.bool(true, "Banner", group=grp_display)
show_macro      = input.bool(true, "Macro matrix + stats", group=grp_display)
show_fast       = input.bool(true, "Fast matrix", group=grp_display)
show_stationary = input.bool(true, "Stationary dist", group=grp_display)
show_duration   = input.bool(true, "Duration memory", group=grp_display)
show_2nd_order  = input.bool(true, "2nd-order memory", group=grp_display)
show_candle     = input.bool(true, "Candle streak memory", group=grp_display)
show_labels     = input.bool(true, "Labels", group=grp_display)
txt_sz          = input.string("normal", "Text size", options=["small","normal","large","huge"], group=grp_display)
min_hold        = input.int(2, "Min hold for label", minval=1, maxval=50, group=grp_display)

pos_banner   = input.string("top_center", "Banner", options=["top_left","top_center","top_right","middle_left","middle_center","middle_right","bottom_left","bottom_center","bottom_right"], group=grp_position)
pos_macro    = input.string("top_right", "Macro + stats", options=["top_left","top_center","top_right","middle_left","middle_center","middle_right","bottom_left","bottom_center","bottom_right"], group=grp_position)
pos_fast     = input.string("middle_right", "Fast", options=["top_left","top_center","top_right","middle_left","middle_center","middle_right","bottom_left","bottom_center","bottom_right"], group=grp_position)
pos_stat     = input.string("bottom_center", "Stationary", options=["top_left","top_center","top_right","middle_left","middle_center","middle_right","bottom_left","bottom_center","bottom_right"], group=grp_position)
pos_dur      = input.string("bottom_left", "Duration", options=["top_left","top_center","top_right","middle_left","middle_center","middle_right","bottom_left","bottom_center","bottom_right"], group=grp_position)
pos_2nd      = input.string("middle_left", "2nd-order", options=["top_left","top_center","top_right","middle_left","middle_center","middle_right","bottom_left","bottom_center","bottom_right"], group=grp_position)
pos_candle   = input.string("bottom_right", "Candle memory", options=["top_left","top_center","top_right","middle_left","middle_center","middle_right","bottom_left","bottom_center","bottom_right"], group=grp_position)

// ─── Helpers ────────────────────────────────────────────────────

pos_f(s) =>
    switch s
        "top_left" => position.top_left
        "top_center" => position.top_center
        "top_right" => position.top_right
        "middle_left" => position.middle_left
        "middle_center" => position.middle_center
        "middle_right" => position.middle_right
        "bottom_left" => position.bottom_left
        "bottom_center" => position.bottom_center
        "bottom_right" => position.bottom_right
        => position.top_right
sz_f(s) =>
    switch s
        "small" => size.small
        "normal" => size.normal
        "large" => size.large
        "huge" => size.huge
        => size.normal
notch(s) =>
    switch s
        "huge" => "large"
        "large" => "normal"
        "normal" => "small"
        => "small"

val_size = sz_f(txt_sz)
hdr_size = sz_f(notch(txt_sz))
active_fast_th = use_auto_fast ? auto_fast_th : fast_threshold_pct

// Colour palette
c_bull=color.rgb(132,187,161,30), c_bear=color.rgb(197,127,134,30), c_side=color.rgb(164,171,183,30)
c_bg=color.new(color.black,30), c_txt=color.white
c_card=color.new(#0B0F0D,8), c_frame=color.new(#3FDE7E,78), c_acc=#3FDE7E
c_dbg=color.new(#3FDE7E,80), c_dtxt=#6BF0A6
c_off=color.new(color.white,55), c_hdr=color.new(color.white,35), c_foot=color.new(color.white,60)
c_algn=color.new(#FFD700,30), c_algnt=#FFD700
c_warn=#FF6B6B, c_strong=#4DEEAA, c_green=#26A69A, c_red=#EF5350

rsol(r) => r==1?c_bull:r==2?c_bear:c_side
rnam(r) => r==1?"Bull":r==2?"Bear":"Sideways"
rabr(r) => r==1?"BUL":r==2?"BER":"SID"
fpct(p) => str.tostring(math.round(p*100))+"%"
colp(p) => p>=0?(p>=0.80?c_strong:p<0.60?c_warn:c_off):c_foot

// Basis-point formatter with 1 decimal precision
// e.g. 0.000003 → "0.3bp" instead of "0bp"
fbps(p) =>
    val = math.round(p * 100000) / 10.0
    str.tostring(val) + "bp"

// ═══════════════════════════════════════════════════════════════
//  HMM PARAMETERS — Auto-loaded by chart timeframe
//
//  States:  0 = Sideways, 1 = Bull, 2 = Bear
//  Layout:  row-major 3×3 flattened into 9-element arrays
//
//  Transition matrix:
//    [SID→SID, SID→BUL, SID→BER,
//     BUL→SID, BUL→BUL, BUL→BER,
//     BER→SID, BER→BUL, BER→BER]
//
//  Emission means & variances (Gaussian, diagonal covariance):
//    [SID_zret, SID_vr, SID_ur,
//     BUL_zret, BUL_vr, BUL_ur,
//     BER_zret, BER_vr, BER_ur]
//
//  Retrain: python3 trainer/nq_hmm_trainer.py
// ═══════════════════════════════════════════════════════════════

tf_sec = timeframe.in_seconds()

var trans = array.new_float(9, 0.0)
var means = array.new_float(9, 0.0)
var vars  = array.new_float(9, 0.0)
var string tf_label = ""

if barstate.isfirst
    if tf_sec <= 300
        // ── 5-MINUTE parameters (13217 bars, Sharpe 0.78) ──
        trans := array.from(0.943962, 0.000000, 0.056038, 0.031706, 0.914668, 0.053626, 0.110024, 0.210058, 0.679918)
        means := array.from(0.035288, 0.732710, 0.848629, 0.037343, 1.220802, 0.826263, -0.038083, 1.088494, 2.240876)
        vars  := array.from(0.803964, 0.029437, 0.152834, 0.584471, 0.034105, 0.123098, 2.993140, 0.079303, 0.994303)
        tf_label := "5m"
    else if tf_sec <= 900
        // ── 15-MINUTE parameters (4385 bars, Sharpe 0.03) ──
        trans := array.from(0.806295, 0.172045, 0.021660, 0.048030, 0.914230, 0.037740, 0.036078, 0.001975, 0.961947)
        means := array.from(0.084324, 1.177627, 2.583429, 0.058740, 1.245920, 0.719822, 0.026857, 0.621834, 0.837466)
        vars  := array.from(2.805351, 0.065580, 2.245801, 0.457494, 0.049356, 0.133854, 0.917522, 0.031809, 0.272145)
        tf_label := "15m"
    else if tf_sec <= 3600
        // ── 1-HOUR parameters (13634 bars, Sharpe 0.60) ──
        trans := array.from(0.871258, 0.001575, 0.127166, 0.000000, 0.845431, 0.154569, 0.176314, 0.231883, 0.591803)
        means := array.from(0.038400, 0.929981, 2.778223, 0.045697, 1.002387, 0.144128, -0.009978, 0.945287, 0.396098)
        vars  := array.from(2.275991, 0.076837, 1.880630, 0.176232, 0.078041, 0.004992, 0.803508, 0.070128, 0.041729)
        tf_label := "1h"
    else
        // ── 1-DAY parameters (6435 bars, 25.7 years, Sharpe 0.49, DD reduction 50%) ──
        trans := array.from(0.927545, 0.035307, 0.037148, 0.044334, 0.954892, 0.000774, 0.041405, 0.001427, 0.957168)
        means := array.from(0.032079, 0.958639, 1.004624, 0.126549, 0.704361, 1.034901, 0.015084, 1.235448, 1.008822)
        vars  := array.from(0.967460, 0.005203, 0.126411, 1.072657, 0.015507, 0.135864, 1.043655, 0.017889, 0.140397)
        tf_label := "1D"

// ═══════════════════════════════════════════════════════════════
//  FEATURE COMPUTATION + HMM FORWARD ALGORITHM
//
//  The forward algorithm computes:
//    α_t(s) ∝ [ Σ_s' α_{t-1}(s') · A(s'→s) ] · B(s, x_t)
//
//  where A is the transition matrix and B is the Gaussian emission
//  density. We work in log-space with a max-shift trick for
//  numerical stability.
// ═══════════════════════════════════════════════════════════════

// Feature computation (matching Python trainer exactly)
bar_log_ret = math.log(close/close[1])
hmm_rvol = ta.stdev(bar_log_ret, 20)    // fast vol (20-bar)
hmm_lvol = ta.stdev(bar_log_ret, 60)    // slow vol (60-bar)
hmm_vsma = ta.sma(volume, 20)           // volume MA (20-bar)

z_return = not na(hmm_rvol) and hmm_rvol>0 ? bar_log_ret/hmm_rvol : 0.0
vol_ratio = not na(hmm_rvol) and not na(hmm_lvol) and hmm_lvol>0 ? hmm_rvol/hmm_lvol : 1.0
volume_ratio = not na(hmm_vsma) and hmm_vsma>0 ? volume/hmm_vsma : 1.0

// Clip to match training bounds
zr = math.max(-5.0, math.min(5.0, z_return))
vr = math.max(0.0, math.min(5.0, vol_ratio))
ur = math.max(0.0, math.min(15.0, volume_ratio))

// Gaussian log-density (single feature, diagonal covariance)
log_g(x, mu, v) => -0.5*((x-mu)*(x-mu)/v + math.log(2.0*math.pi*v))

// Total log-emission for a state (sum of 3 independent Gaussians)
le_state(state_idx) =>
    base = state_idx * 3
    log_g(zr, array.get(means, base), array.get(vars, base)) + log_g(vr, array.get(means, base+1), array.get(vars, base+1)) + log_g(ur, array.get(means, base+2), array.get(vars, base+2))

// Forward variables (posterior probabilities)
var float a_sid=1.0/3.0, var float a_bul=1.0/3.0, var float a_ber=1.0/3.0
var int macro_regime = 0

if barstate.isconfirmed and not na(zr) and not na(vr) and not na(ur)
    // Prediction step: α_pred(s) = Σ_{s'} α_{t-1}(s') · A(s'→s)
    ps = a_sid*array.get(trans,0) + a_bul*array.get(trans,3) + a_ber*array.get(trans,6)
    pb = a_sid*array.get(trans,1) + a_bul*array.get(trans,4) + a_ber*array.get(trans,7)
    pr = a_sid*array.get(trans,2) + a_bul*array.get(trans,5) + a_ber*array.get(trans,8)
    // Update step: α_t(s) ∝ α_pred(s) · B(s, x_t)
    ls = le_state(0), lb = le_state(1), lr = le_state(2)
    mx = math.max(ls, math.max(lb, lr))
    es = math.exp(ls-mx), eb = math.exp(lb-mx), er = math.exp(lr-mx)
    raw_s=ps*es, raw_b=pb*eb, raw_r=pr*er
    tot=raw_s+raw_b+raw_r
    if tot>0
        a_sid:=raw_s/tot, a_bul:=raw_b/tot, a_ber:=raw_r/tot
    else
        a_sid:=1.0/3.0, a_bul:=1.0/3.0, a_ber:=1.0/3.0
    macro_regime := a_bul>=a_sid and a_bul>=a_ber ? 1 : a_ber>=a_sid and a_ber>=a_bul ? 2 : 0

confidence = macro_regime==1?a_bul:macro_regime==2?a_ber:a_sid

// ═══════════════════════════════════════════════════════════════
//  FAST Z-SCORE REGIME + ALIGNMENT
//
//  A short-lookback z-score regime provides a "trigger" signal.
//  When both HMM (macro) and fast agree on direction → aligned.
//  Alignment = high-conviction entry signal.
// ═══════════════════════════════════════════════════════════════

log_ret_fast = math.log(close/close[fast_lookback])
rvol_f = ta.stdev(bar_log_ret, math.max(fast_lookback*2, 10))
float z_fast = na
if not na(rvol_f) and rvol_f>0
    z_fast := log_ret_fast/(rvol_f*math.sqrt(fast_lookback))
int fast_regime = na
if not na(log_ret_fast)
    if not na(z_fast)
        fast_regime := z_fast>0.4?1:z_fast<-0.4?2:0
    else
        fast_regime := log_ret_fast>active_fast_th/100.0?1:log_ret_fast<-active_fast_th/100.0?2:0

aligned = not na(macro_regime) and not na(fast_regime) and macro_regime==fast_regime and macro_regime!=0
vol_sma_c = ta.sma(volume, vol_sma_len)
vol_confirmed = not na(vol_sma_c) and volume>=vol_sma_c*vol_confirm_mult
bar_return = close/close[1]-1.0

// Background ribbon
rib = fast_regime==1?color.rgb(132,187,161,90):fast_regime==2?color.rgb(197,127,134,90):color.rgb(164,171,183,90)
arib = aligned and fast_regime==1?color.rgb(132,187,161,75):aligned and fast_regime==2?color.rgb(197,127,134,75):rib
bgcolor(show_ribbon?arib:na, title="Regime ribbon")

// ═══════════════════════════════════════════════════════════════
//  LIVE STATISTICS — EMA-decayed transition counts, 2nd-order
//  memory, duration persistence, candle streaks, regime P&L
// ═══════════════════════════════════════════════════════════════

var mc=array.new_int(9,0), var fc=array.new_int(9,0)
var em=array.new_float(9,0.0), var ef_a=array.new_float(9,0.0)
prev_m=macro_regime[1], prev_f=fast_regime[1]

if barstate.isconfirmed
    for i=0 to 8
        array.set(em,i,array.get(em,i)*decay_factor)
        array.set(ef_a,i,array.get(ef_a,i)*decay_factor)
    if not na(prev_m) and not na(macro_regime)
        mi=prev_m*3+macro_regime
        array.set(mc,mi,array.get(mc,mi)+1)
        array.set(em,mi,array.get(em,mi)+1.0)
    if not na(prev_f) and not na(fast_regime)
        fi=prev_f*3+fast_regime
        array.set(fc,fi,array.get(fc,fi)+1)
        array.set(ef_a,fi,array.get(ef_a,fi)+1.0)

// 2nd-order transitions (trigram memory)
var so=array.new_int(81,0)
ppm=macro_regime[2]
if barstate.isconfirmed and not na(ppm) and not na(prev_m) and not na(macro_regime)
    fp=ppm*3+prev_m, tp=prev_m*3+macro_regime
    array.set(so,fp*9+tp,array.get(so,fp*9+tp)+1)

// Duration tracking
var int mac_dur=1, var int prev_dur=1
if barstate.isconfirmed and not na(macro_regime)
    prev_dur:=mac_dur
    if not na(prev_m) and macro_regime==prev_m
        mac_dur+=1
    else
        mac_dur:=1

var ds=array.new_int(9,0), var dm_a=array.new_int(9,0), var dl_a=array.new_int(9,0)
if barstate.isconfirmed and not na(prev_m) and not na(macro_regime)
    di=prev_m*3+macro_regime
    if prev_dur<=3
        array.set(ds,di,array.get(ds,di)+1)
    else if prev_dur<=8
        array.set(dm_a,di,array.get(dm_a,di)+1)
    else
        array.set(dl_a,di,array.get(dl_a,di)+1)

// Per-regime return statistics (for E[r] and Kelly)
var float srb=0.0,var float sr2b=0.0,var int nb=0
var float srr=0.0,var float sr2r=0.0,var int nr=0
var float srs=0.0,var float sr2s=0.0,var int ns=0
if barstate.isconfirmed and not na(macro_regime) and not na(bar_return)
    if macro_regime==1
        srb+=bar_return, sr2b+=bar_return*bar_return, nb+=1
    else if macro_regime==2
        srr+=bar_return, sr2r+=bar_return*bar_return, nr+=1
    else
        srs+=bar_return, sr2s+=bar_return*bar_return, ns+=1

// Alignment hit rate
var int sig_ok=0, var int sig_n=0
if barstate.isconfirmed and not na(aligned[1]) and aligned[1]
    sig_n+=1
    if fast_regime[1]==1 and close>close[1]
        sig_ok+=1
    else if fast_regime[1]==2 and close<close[1]
        sig_ok+=1

// Candle streak memory
var int gstrk=0, var int rstrk=0
var gc_a=array.new_int(6,0), var rc_a=array.new_int(6,0)
if barstate.isconfirmed
    pgs=gstrk, prs=rstrk
    cg=close>open
    if cg
        gstrk+=1, rstrk:=0
    else
        rstrk+=1, gstrk:=0
    if pgs>=1
        bk=pgs>=3?4:pgs==2?2:0
        if cg
            array.set(gc_a,bk,array.get(gc_a,bk)+1)
        else
            array.set(gc_a,bk+1,array.get(gc_a,bk+1)+1)
    if prs>=1
        bk=prs>=3?4:prs==2?2:0
        if cg
            array.set(rc_a,bk,array.get(rc_a,bk)+1)
        else
            array.set(rc_a,bk+1,array.get(rc_a,bk+1)+1)

// Model fit (average log-likelihood)
cur_ll = le_state(0)*a_sid + le_state(1)*a_bul + le_state(2)*a_ber
avg_ll = ta.sma(cur_ll, 100)

// ═══════════════════════════════════════════════════════════════
//  TRANSITION LABELS (on-chart)
// ═══════════════════════════════════════════════════════════════

var int last_lbl=na
lh=not na(fast_regime)
for k=0 to min_hold-1
    lh:=lh and not na(fast_regime[k]) and fast_regime[k]==fast_regime
if barstate.isconfirmed and not na(fast_regime) and lh and fast_regime!=last_lbl
    if show_labels and not na(last_lbl)
        fo=min_hold-1
        label.new(bar_index-fo,high[fo],rabr(last_lbl)+"->"+rabr(fast_regime),yloc=yloc.abovebar,style=label.style_label_down,color=rsol(fast_regime),textcolor=color.white,size=size.small)
    last_lbl:=fast_regime

// ═══════════════════════════════════════════════════════════════
//  MATRIX HELPERS
// ═══════════════════════════════════════════════════════════════

matmul(A,B) =>
    a00=array.get(A,0),a01=array.get(A,1),a02=array.get(A,2),a10=array.get(A,3),a11=array.get(A,4),a12=array.get(A,5),a20=array.get(A,6),a21=array.get(A,7),a22=array.get(A,8)
    b00=array.get(B,0),b01=array.get(B,1),b02=array.get(B,2),b10=array.get(B,3),b11=array.get(B,4),b12=array.get(B,5),b20=array.get(B,6),b21=array.get(B,7),b22=array.get(B,8)
    C=array.new_float(9,0.0)
    array.set(C,0,a00*b00+a01*b10+a02*b20),array.set(C,1,a00*b01+a01*b11+a02*b21),array.set(C,2,a00*b02+a01*b12+a02*b22)
    array.set(C,3,a10*b00+a11*b10+a12*b20),array.set(C,4,a10*b01+a11*b11+a12*b21),array.set(C,5,a10*b02+a11*b12+a12*b22)
    array.set(C,6,a20*b00+a21*b10+a22*b20),array.set(C,7,a20*b01+a21*b11+a22*b21),array.set(C,8,a20*b02+a21*b12+a22*b22)
    C

bld_m(rc) =>
    P=array.new_float(9,0.0)
    for r=0 to 2
        rsum=array.get(rc,r*3)+array.get(rc,r*3+1)+array.get(rc,r*3+2)
        for c=0 to 2
            array.set(P,r*3+c,rsum>0?array.get(rc,r*3+c)/rsum:1.0/3.0)
    P
bld_f(rc) =>
    P=array.new_float(9,0.0)
    for r=0 to 2
        rsum=array.get(rc,r*3)+array.get(rc,r*3+1)+array.get(rc,r*3+2)
        for c=0 to 2
            array.set(P,r*3+c,rsum>0?array.get(rc,r*3+c)/rsum:1.0/3.0)
    P

row_n(ca,si) => array.get(ca,si*3)+array.get(ca,si*3+1)+array.get(ca,si*3+2)
safe_p(ca,si,mn) =>
    rs=row_n(ca,si)
    rs>=mn?array.get(ca,si*3+si)/rs:-1.0

draw_fast(tbl, P, cnt, tt, ft) =>
    table.cell(tbl,0,0,tt,text_color=c_acc,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_left)
    table.cell(tbl,1,0,"",bgcolor=c_card),table.cell(tbl,2,0,"",bgcolor=c_card),table.cell(tbl,3,0,"",bgcolor=c_card)
    table.cell(tbl,4,0,"3x3",text_color=c_foot,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_right)
    table.cell(tbl,0,1,"",bgcolor=c_card)
    table.cell(tbl,1,1,"BUL",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
    table.cell(tbl,2,1,"BER",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
    table.cell(tbl,3,1,"SID",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
    table.cell(tbl,4,1,"n",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
    for r=0 to 2
        rn=r==0?"BUL":r==1?"BER":"SID"
        rnt=row_n(cnt,r)
        table.cell(tbl,0,r+2,rn,text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        for c=0 to 2
            p=array.get(P,r*3+c)
            id=(r==c)
            table.cell(tbl,c+1,r+2,fpct(p),text_color=id?c_dtxt:c_off,bgcolor=id?c_dbg:c_card,text_size=id?val_size:hdr_size,text_halign=text.align_center)
        table.cell(tbl,4,r+2,str.tostring(rnt),text_color=c_foot,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
    table.cell(tbl,0,5,ft,text_color=c_foot,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_left)
    table.cell(tbl,1,5,"",bgcolor=c_card),table.cell(tbl,2,5,"",bgcolor=c_card),table.cell(tbl,3,5,"",bgcolor=c_card)
    table.cell(tbl,4,5,"live",text_color=c_acc,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_right)

// ═══════════════════════════════════════════════════════════════
//  DASHBOARD TABLES
// ═══════════════════════════════════════════════════════════════

var table tb = table.new(pos_f(pos_banner), 1, 7, bgcolor=c_bg, border_width=0)
var table tm = table.new(pos_f(pos_macro), 5, 8, bgcolor=c_card, border_width=1, border_color=c_card, frame_color=c_frame, frame_width=1)
var table tf_t = table.new(pos_f(pos_fast), 5, 6, bgcolor=c_card, border_width=1, border_color=c_card, frame_color=c_frame, frame_width=1)
var table ts = table.new(pos_f(pos_stat), 3, 4, bgcolor=c_card, border_width=1, border_color=c_card, frame_color=c_frame, frame_width=1)
var table td = table.new(pos_f(pos_dur), 4, 5, bgcolor=c_card, border_width=1, border_color=c_card, frame_color=c_frame, frame_width=1)
var table t2 = table.new(pos_f(pos_2nd), 3, 5, bgcolor=c_card, border_width=1, border_color=c_card, frame_color=c_frame, frame_width=1)
var table tc = table.new(pos_f(pos_candle), 4, 8, bgcolor=c_card, border_width=1, border_color=c_card, frame_color=c_frame, frame_width=1)

if barstate.islast
    Pm = bld_m(mc)
    Pf = bld_m(fc)
    Pe = bld_f(em)
    M = array.copy(Pm)
    for _i=1 to stationary_power-1
        M:=matmul(M,Pm)

    // ─── BANNER ───
    if show_banner
        cs=" ("+str.tostring(math.round(confidence*100))+"%)"
        table.cell(tb,0,0,"HMM "+tf_label+": "+rnam(macro_regime)+cs,text_color=c_txt,bgcolor=rsol(macro_regime),text_size=val_size,text_halign=text.align_center)
        zfs=not na(z_fast)?" z:"+str.tostring(math.round(z_fast*10)/10.0):""
        table.cell(tb,0,1,"FAST: "+rnam(fast_regime)+zfs,text_color=c_txt,bgcolor=rsol(fast_regime),text_size=hdr_size,text_halign=text.align_center)
        if aligned
            table.cell(tb,0,2,fast_regime==1?"ALIGNED LONG":"ALIGNED SHORT",text_color=c_algnt,bgcolor=c_algn,text_size=val_size,text_halign=text.align_center)
        else
            table.cell(tb,0,2,"NO ALIGNMENT",text_color=c_foot,bgcolor=c_bg,text_size=hdr_size,text_halign=text.align_center)
        db=mac_dur<=3?"early":mac_dur<=8?"mid":"mature"
        vt=vol_confirmed?" | VOL+":""
        table.cell(tb,0,3,str.tostring(mac_dur)+" bars ("+db+")"+vt,text_color=c_foot,bgcolor=c_bg,text_size=size.small,text_halign=text.align_center)

        // Edge & Kelly criterion
        float te=na, float kf=0.0
        if not na(macro_regime)
            if macro_regime==1 and nb>min_samples
                mu=srb/nb, va=math.max(sr2b/nb-mu*mu,0.000001)
                te:=mu, kf:=aligned?math.max(0.0,math.min(mu/va,1.0)):0.0
            else if macro_regime==2 and nr>min_samples
                mu=srr/nr, va=math.max(sr2r/nr-mu*mu,0.000001)
                te:=-mu, kf:=aligned?math.max(0.0,math.min(-mu/va,1.0)):0.0
            else if macro_regime==0 and ns>min_samples
                te:=srs/ns, kf:=0.0
        et=aligned and not na(te)?"E[r]: "+fbps(te)+" | Kelly: "+fpct(kf):not na(te)?"E[r]: "+fbps(te)+" (no trade)":"Gathering..."
        ec=not na(te)?(te>0 and aligned?c_strong:te>0?c_off:c_warn):c_foot
        table.cell(tb,0,4,et,text_color=ec,bgcolor=c_bg,text_size=size.small,text_halign=text.align_center)

        // P(stay) + recent trend
        string rt=""
        color rtc=c_foot
        if not na(macro_regime)
            ph=array.get(Pm,macro_regime*3+macro_regime)
            prr=array.get(Pe,macro_regime*3+macro_regime)
            dlt=prr-ph
            ew=math.round(1.0/(1.0-decay_factor))
            ar=math.abs(dlt)>=0.03?(dlt>0?" +":"")+str.tostring(math.round(dlt*100))+"%":""
            rt:="P(stay): "+fpct(ph)+" | rcnt("+str.tostring(ew)+"b): "+fpct(prr)+ar
            rtc:=math.abs(dlt)>=0.05?(dlt>0?c_strong:c_warn):c_foot
        table.cell(tb,0,5,rt,text_color=rtc,bgcolor=c_bg,text_size=size.small,text_halign=text.align_center)

        // Hit rate + log-likelihood
        hp=sig_n>=20?sig_ok*1.0/sig_n:na
        ht=not na(hp)?"Hit: "+fpct(hp)+" (n="+str.tostring(sig_n)+")":"Hit: gathering..."
        hc=not na(hp)?(hp>=0.55?c_strong:hp<0.45?c_warn:c_off):c_foot
        llt=not na(avg_ll)?" | LL: "+str.tostring(math.round(avg_ll*10)/10.0):""
        table.cell(tb,0,6,ht+llt,text_color=hc,bgcolor=c_bg,text_size=size.small,text_halign=text.align_center)

    // ─── MACRO + STATS ───
    if show_macro
        table.cell(tm,0,0,"MACRO (HMM "+tf_label+")",text_color=c_acc,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_left)
        table.cell(tm,1,0,"",bgcolor=c_card),table.cell(tm,2,0,"",bgcolor=c_card),table.cell(tm,3,0,"",bgcolor=c_card)
        table.cell(tm,4,0,"3x3",text_color=c_foot,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_right)
        table.cell(tm,0,1,"",bgcolor=c_card)
        table.cell(tm,1,1,"BUL",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,2,1,"BER",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,3,1,"SID",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,4,1,"n",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        for r=0 to 2
            rn=r==0?"BUL":r==1?"BER":"SID"
            rnt=row_n(mc,r)
            table.cell(tm,0,r+2,rn,text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
            for c=0 to 2
                p=array.get(Pm,r*3+c)
                id=(r==c)
                table.cell(tm,c+1,r+2,fpct(p),text_color=id?c_dtxt:c_off,bgcolor=id?c_dbg:c_card,text_size=id?val_size:hdr_size,text_halign=text.align_center)
            table.cell(tm,4,r+2,str.tostring(rnt),text_color=c_foot,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        // E[r] row
        mub=nb>5?srb/nb:na, mur=nr>5?srr/nr:na, mus=ns>5?srs/ns:na
        table.cell(tm,0,5,"E[r]",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,1,5,not na(mub)?fbps(mub):"—",text_color=not na(mub) and mub>0?c_strong:c_warn,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,2,5,not na(mur)?fbps(mur):"—",text_color=not na(mur) and mur>0?c_strong:c_warn,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,3,5,not na(mus)?fbps(mus):"—",text_color=not na(mus) and mus>0?c_strong:c_warn,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,4,5,"",bgcolor=c_card)
        // Vol row
        vb=nb>5?math.sqrt(math.max(sr2b/nb-math.pow(mub,2),0)):na
        vrr2=nr>5?math.sqrt(math.max(sr2r/nr-math.pow(mur,2),0)):na
        vs=ns>5?math.sqrt(math.max(sr2s/ns-math.pow(mus,2),0)):na
        table.cell(tm,0,6,"vol",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,1,6,not na(vb)?fbps(vb):"—",text_color=c_off,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,2,6,not na(vrr2)?fbps(vrr2):"—",text_color=c_off,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,3,6,not na(vs)?fbps(vs):"—",text_color=c_off,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tm,4,6,"",bgcolor=c_card)
        table.cell(tm,0,7,"trend P",text_color=c_foot,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_left)
        table.cell(tm,1,7,"",bgcolor=c_card),table.cell(tm,2,7,"",bgcolor=c_card),table.cell(tm,3,7,"",bgcolor=c_card)
        table.cell(tm,4,7,"live",text_color=c_acc,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_right)

    // ─── FAST ───
    if show_fast
        draw_fast(tf_t, Pf, fc, "FAST", "trade P")

    // ─── STATIONARY ───
    if show_stationary
        table.cell(ts,0,0,"LONG-RUN",text_color=c_acc,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_left)
        table.cell(ts,1,0,"",bgcolor=c_card),table.cell(ts,2,0,"",bgcolor=c_card)
        table.cell(ts,0,1,"BUL",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(ts,1,1,"BER",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(ts,2,1,"SID",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(ts,0,2,fpct(array.get(M,0)),text_color=c_dtxt,bgcolor=c_dbg,text_size=val_size,text_halign=text.align_center)
        table.cell(ts,1,2,fpct(array.get(M,1)),text_color=c_dtxt,bgcolor=c_dbg,text_size=val_size,text_halign=text.align_center)
        table.cell(ts,2,2,fpct(array.get(M,2)),text_color=c_dtxt,bgcolor=c_dbg,text_size=val_size,text_halign=text.align_center)
        table.cell(ts,0,3,"steady state",text_color=c_foot,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_left)
        table.cell(ts,1,3,"",bgcolor=c_card)
        table.cell(ts,2,3,"live",text_color=c_acc,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_right)

    // ─── DURATION ───
    if show_duration
        table.cell(td,0,0,"DURATION",text_color=c_acc,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_left)
        table.cell(td,1,0,"",bgcolor=c_card),table.cell(td,2,0,"",bgcolor=c_card),table.cell(td,3,0,"",bgcolor=c_card)
        table.cell(td,0,1,"",bgcolor=c_card)
        table.cell(td,1,1,"1-3",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(td,2,1,"4-8",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(td,3,1,"9+",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        for s=0 to 2
            sn=s==0?"BUL":s==1?"BER":"SID"
            pss=safe_p(ds,s,min_samples),pmm=safe_p(dm_a,s,min_samples),pll=safe_p(dl_a,s,min_samples)
            nns=row_n(ds,s),nnm=row_n(dm_a,s),nnl=row_n(dl_a,s)
            ic=not na(macro_regime) and s==macro_regime
            table.cell(td,0,s+2,sn,text_color=ic?c_dtxt:c_hdr,bgcolor=ic?c_dbg:c_card,text_size=hdr_size,text_halign=text.align_center)
            table.cell(td,1,s+2,pss>=0?fpct(pss)+"("+str.tostring(math.round(nns))+")":"—",text_color=colp(pss),bgcolor=ic and mac_dur<=3?c_dbg:c_card,text_size=hdr_size,text_halign=text.align_center)
            table.cell(td,2,s+2,pmm>=0?fpct(pmm)+"("+str.tostring(math.round(nnm))+")":"—",text_color=colp(pmm),bgcolor=ic and mac_dur>=4 and mac_dur<=8?c_dbg:c_card,text_size=hdr_size,text_halign=text.align_center)
            table.cell(td,3,s+2,pll>=0?fpct(pll)+"("+str.tostring(math.round(nnl))+")":"—",text_color=colp(pll),bgcolor=ic and mac_dur>=9?c_dbg:c_card,text_size=hdr_size,text_halign=text.align_center)

    // ─── 2ND ORDER ───
    if show_2nd_order
        table.cell(t2,0,0,"2ND ORDER",text_color=c_acc,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_left)
        table.cell(t2,1,0,"",bgcolor=c_card),table.cell(t2,2,0,"",bgcolor=c_card)
        table.cell(t2,0,1,"",bgcolor=c_card)
        table.cell(t2,1,1,"SUST",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(t2,2,1,"FRESH",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        for s=0 to 2
            sn=s==0?"BUL":s==1?"BER":"SID"
            sf=s*3+s, srs2=0
            for c2=0 to 8
                srs2+=array.get(so,sf*9+c2)
            sp=srs2>=min_samples?array.get(so,sf*9+sf)/srs2:-1.0
            ft2=0,fs2=0
            for f=0 to 2
                if f!=s
                    ff=f*3+s, frs=0
                    for c2=0 to 8
                        frs+=array.get(so,ff*9+c2)
                    ft2+=frs, fs2+=array.get(so,ff*9+sf)
            fp2=ft2>=min_samples?fs2/ft2:-1.0
            ic=not na(macro_regime) and s==macro_regime
            table.cell(t2,0,s+2,sn,text_color=ic?c_dtxt:c_hdr,bgcolor=ic?c_dbg:c_card,text_size=hdr_size,text_halign=text.align_center)
            table.cell(t2,1,s+2,sp>=0?fpct(sp)+"("+str.tostring(math.round(srs2))+")":"—",text_color=colp(sp),bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
            table.cell(t2,2,s+2,fp2>=0?fpct(fp2)+"("+str.tostring(math.round(ft2))+")":"—",text_color=colp(fp2),bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)

    // ─── CANDLE MEMORY ───
    if show_candle
        table.cell(tc,0,0,"CANDLE MEMORY",text_color=c_acc,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_left)
        table.cell(tc,1,0,"",bgcolor=c_card),table.cell(tc,2,0,"",bgcolor=c_card),table.cell(tc,3,0,"",bgcolor=c_card)
        table.cell(tc,0,1,"AFTER",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tc,1,1,"P(GRN)",text_color=c_green,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tc,2,1,"P(RED)",text_color=c_red,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        table.cell(tc,3,1,"(n)",text_color=c_hdr,bgcolor=c_card,text_size=hdr_size,text_halign=text.align_center)
        gl=array.from("1 grn","2 grn","3+ grn")
        for i=0 to 2
            ix=i*2, ng=array.get(gc_a,ix), nrr=array.get(gc_a,ix+1), tot=ng+nrr
            pg=tot>=min_samples?ng*1.0/tot:-1.0, prr=tot>=min_samples?nrr*1.0/tot:-1.0
            ic=(gstrk==i+1 and i<2) or (gstrk>=3 and i==2)
            rb=ic?c_dbg:c_card
            table.cell(tc,0,i+2,array.get(gl,i),text_color=ic?c_green:c_hdr,bgcolor=rb,text_size=hdr_size,text_halign=text.align_center)
            table.cell(tc,1,i+2,pg>=0?fpct(pg):"—",text_color=pg>=0?(pg>=0.55?c_strong:pg<0.45?c_warn:c_off):c_foot,bgcolor=rb,text_size=val_size,text_halign=text.align_center)
            table.cell(tc,2,i+2,prr>=0?fpct(prr):"—",text_color=prr>=0?(prr>=0.55?c_warn:prr<0.45?c_strong:c_off):c_foot,bgcolor=rb,text_size=val_size,text_halign=text.align_center)
            table.cell(tc,3,i+2,str.tostring(math.round(tot)),text_color=c_foot,bgcolor=rb,text_size=hdr_size,text_halign=text.align_center)
        rl=array.from("1 red","2 red","3+ red")
        for i=0 to 2
            ix=i*2, ng=array.get(rc_a,ix), nrr=array.get(rc_a,ix+1), tot=ng+nrr
            pg=tot>=min_samples?ng*1.0/tot:-1.0, prr=tot>=min_samples?nrr*1.0/tot:-1.0
            ic=(rstrk==i+1 and i<2) or (rstrk>=3 and i==2)
            rb=ic?c_dbg:c_card
            table.cell(tc,0,i+5,array.get(rl,i),text_color=ic?c_red:c_hdr,bgcolor=rb,text_size=hdr_size,text_halign=text.align_center)
            table.cell(tc,1,i+5,pg>=0?fpct(pg):"—",text_color=pg>=0?(pg>=0.55?c_strong:pg<0.45?c_warn:c_off):c_foot,bgcolor=rb,text_size=val_size,text_halign=text.align_center)
            table.cell(tc,2,i+5,prr>=0?fpct(prr):"—",text_color=prr>=0?(prr>=0.55?c_warn:prr<0.45?c_strong:c_off):c_foot,bgcolor=rb,text_size=val_size,text_halign=text.align_center)
            table.cell(tc,3,i+5,str.tostring(math.round(tot)),text_color=c_foot,bgcolor=rb,text_size=hdr_size,text_halign=text.align_center)

// ═══════════════════════════════════════════════════════════════
//  ALERTS
// ═══════════════════════════════════════════════════════════════

alertcondition(macro_regime!=macro_regime[1] and not na(macro_regime[1]), title="HMM Regime Change", message="HMM regime changed")
alertcondition(aligned and not aligned[1], title="New Alignment", message="HMM+Fast alignment established")
alertcondition(not aligned and aligned[1], title="Lost Alignment", message="HMM+Fast alignment lost")
alertcondition(confidence<0.4, title="Low Confidence", message="HMM confidence below 40%")

plot(macro_regime, title="HMM Regime", display=display.none)
plot(confidence, title="Confidence", display=display.none)

Overview

Markov-HMM Regime v5 runs the forward algorithm on price and volume features and loads a parameter set matching the chart timeframe. A regime ribbon, banner, labels and several tables show transition, stationary, duration and candle-streak statistics. It is built for Nasdaq futures and draws on the price chart.

Details

Published by

flaviodesalvo54-del on GitHub

Type

Indicator, Pine Script v5

Draws

On the price chart

Category

Trend, AI / machine learning, Screeners & dashboards

Markets

Indices

Timeframes

M15-H1

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
Fast lookbackint4fast_lookback
Fast threshold (%)float0.12fast_threshold_pct
Auto-detect fast thresholdbooltrueuse_auto_fast
Stationary powerint50stationary_power
Min samplesint8min_samples
Transition decayfloat0.997decay_factor
Volume confirm multfloat1.0vol_confirm_mult
Volume SMA for confirmint20vol_sma_len
Regime ribbonbooltrueshow_ribbon
Bannerbooltrueshow_banner
Macro matrix + statsbooltrueshow_macro
Fast matrixbooltrueshow_fast
Show all 26 settings
SettingTypeDefaultVariable
Stationary distbooltrueshow_stationary
Duration memorybooltrueshow_duration
2nd-order memorybooltrueshow_2nd_order
Candle streak memorybooltrueshow_candle
Labelsbooltrueshow_labels
Text sizestringnormaltxt_sz
Min hold for labelint2min_hold
Bannerstringtop_centerpos_banner
Macro + statsstringtop_rightpos_macro
Faststringmiddle_rightpos_fast
Stationarystringbottom_centerpos_stat
Durationstringbottom_leftpos_dur
2nd-orderstringmiddle_leftpos_2nd
Candle memorystringbottom_rightpos_candle
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

46 stars

This indicator uses a Hidden Markov Model (HMM) framework combined with an adaptive dual moving average engine and Directional Movement Index (DMI) features to identify market regimes (bullish, bearish, or ranging/chop). It calculates regime probabilities on e

Indicator v5 AI / machine learningTrend
Updated 7 weeks ago · None stated GitHub repository of HMM Enhanced: Regime Probability

This overlay indicator plots a SuperTrend-style band on the price chart using ATR length, a selectable source, and a base factor. Optional settings enable an adaptive factor driven by rate of change, a volatility filter, K-means clustering, and an LSTM layer w

Indicator v6 TrendVolatility & bands
Updated 4 months ago · MIT GitHub repository of ML SuperTrend (Ultimate) - Auto-Optimized AI with LSTM

Markov State Engine classifies market conditions into six distinct regimes, including strong trends, weak trends, compression, and expansion. It evaluates historical state transitions using an exit-conditional Markov model with Laplace smoothing to estimate re

Indicator v6 AI / machine learningTrend
Updated 2 days ago · MIT GitHub repository of Markov State Engine [WavesUnchained]
257 stars

This indicator uses a Lorentzian distance k-nearest neighbors classification algorithm to predict market direction based on up to five technical features, such as RSI, WaveTrend, CCI, and ADX. It compares current feature values against historical bar patterns

Indicator v5 AI / machine learningOscillators & momentum
Updated 6 weeks ago · None stated GitHub repository of Donald - Machine Learning Options

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