MEASUREMENT CODE
The Gold M15 research build
This is the code that produced the figures on the method page. We publish it in full so you can check them yourself rather than take our word for it.
How it differs from the published indicator
The signal engine is the same. At its default settings this build fires the same signals as the indicator published on TradingView, and adds only what measurement needs:
- An outcome tally that records each signal's result in R, shows the sample size, and warns explicitly when the count is under 200 trades.
- Exits in thirds: a third at TP1, a third at TP2, the rest at TP3 or the stop. It can be switched off to measure the trade as a single position.
- A breakeven switch, to measure the effect of moving the stop to entry after TP1, and of not moving it.
- Adjustable RSI bounds on the breakout path, defaulting to 70 and 30 — the values in the published version.
- A fixed start date, so different settings are measured over the same period.
How to reproduce the measurement
- Open TradingView, open the Pine editor below the chart, and create a new indicator.
- Copy the full code below, paste it over the default content, save, and click “Add to chart”.
- Open a gold chart (XAUUSD) on the 15-minute timeframe.
- In the indicator settings, set “Count signals from” to the start date you want. The result appears in the RESEARCH TALLY table on the chart.
Your figures may differ from ours: they depend on the start date, on how much history your TradingView plan loads, and on the price feed. The logic is identical, and all of it is in front of you.
The code
//@version=6 // Gold M15 Signal Engine — research build // © 2026 Consultya · consultya.co // Published for reading and verification. This is the code that produced // the measured figures on the Consultya website. It is not a trading system. // // Same engine as the published version, plus what is needed to measure it: // 1. Partial exits. A third books at TP1, a third at TP2, the rest at // TP3 or the stop - which is how the signals are actually traded. // Without this, a trade that reached TP1 and came back was recorded // as zero, when in reality a third of it was already banked. // 2. A switch for the breakeven rule, so the same bars can be // measured both ways. // 3. RSI guards on the breakout entry, adjustable rather than fixed. // 4. An R-multiple tally. Win rate alone cannot tell you whether a // system makes money - a 35% win rate can be excellent and a 60% // one can be ruinous. R is the distance to the original stop, so // every outcome is expressed in units of what was risked. // 5. A fixed start date and a timeframe override, so the sample can // be held still between two readings. indicator("Gold M15 — Research Build", overlay=true, max_labels_count=500, max_lines_count=100) // ══════════════════════════════════════════════════════════════ // INPUTS // ══════════════════════════════════════════════════════════════ i_e20 = input.int(20, "EMA 20", group="Moving averages") i_e50 = input.int(50, "EMA 50", group="Moving averages") i_e200 = input.int(200, "EMA 200", group="Moving averages") i_swing = input.int(7, "Swing Length", group="Support / resistance") i_maxS = input.int(2, "Max Supports", group="Support / resistance") i_maxR = input.int(2, "Max Resistances", group="Support / resistance") i_minD = input.float(0.4, "Min Gap %", group="Support / resistance") i_rsiP = input.int(14, "RSI Period", group="Signal engine") i_cool = input.int(5, "Cooldown Bars", group="Signal engine") i_brk = input.int(10, "Breakout Bars", group="Signal engine") i_exp = input.int(40, "Expire Bars (0=off)", group="Signal engine") // Breakout entries buy strength and sell weakness, which is exactly what // an exhausted move looks like right before it reverses. These two guards // block a breakout entry taken into an already-stretched RSI. i_brkHi = input.float(70.0, "Breakout: max RSI for a long", minval=50, maxval=100, group="Signal engine", tooltip="A long breakout is skipped when RSI is above this. 70 reproduces the published behaviour; try 60 to refuse buying into an overbought high.") i_brkLo = input.float(30.0, "Breakout: min RSI for a short", minval=0, maxval=50, group="Signal engine", tooltip="A short breakout is skipped when RSI is below this. 30 reproduces the published behaviour; try 40 to refuse selling into an oversold low.") i_atrP = input.int(14, "ATR Period", group="Targets and stop") i_tp1x = input.float(0.8, "TP1 × ATR", group="Targets and stop") i_tp2x = input.float(1.8, "TP2 × ATR fallback", group="Targets and stop") i_tp3x = input.float(3.0, "TP3 × ATR", group="Targets and stop") i_slPct = input.float(0.35,"Max SL %", group="Targets and stop") i_partial = input.bool(true, "Book a third at each target", group="Stop management", tooltip="Models how the signals are actually traded: a third closes at TP1, a third at TP2, the remainder at TP3 or the stop. Turn it off to treat every signal as one all-or-nothing position, which records a trade that reached TP1 and came back as exactly zero.") i_beTP1 = input.bool(true, "Move stop to breakeven at TP1", group="Stop management", tooltip="ON reproduces the published behaviour. OFF leaves the stop where it started until TP2.") i_start = input.time(1788220800000, "Count signals from", group="Research", tooltip="Signals before this moment are ignored entirely - not drawn and not counted. Pin it to a fixed date so two settings can be compared over exactly the same period.") i_stats = input.bool(true, "Show tally", group="Research", tooltip="Counts the outcomes the indicator drew inside the window. Not a backtest: no spread, no slippage, no sizing, and when a single bar touches both the stop and a target the stop is assumed to come first.") i_anyTF = input.bool(false, "Allow any timeframe (research only)", group="Research", tooltip="The engine was tuned on 15-minute bars. Turning this on gives a larger sample but measures a differently-behaving system.") i_h4 = input.bool(true, "H4 trend filter", group="Higher-timeframe filter", tooltip="Only allow signals that agree with the 4-hour trend.") // ══════════════════════════════════════════════════════════════ // CORE INDICATORS // ══════════════════════════════════════════════════════════════ ema20 = ta.ema(close, i_e20) ema50 = ta.ema(close, i_e50) ema200 = ta.ema(close, i_e200) rsiVal = ta.rsi(close, i_rsiP) atrVal = ta.atr(i_atrP) plot(ema20, "EMA 20", color=color.yellow, linewidth=1) plot(ema50, "EMA 50", color=#1E90FF, linewidth=2) plot(ema200, "EMA 200", color=color.red, linewidth=2) // ══════════════════════════════════════════════════════════════ // H4 TREND FILTER // ══════════════════════════════════════════════════════════════ h4_ema200 = request.security(syminfo.tickerid, "240", ta.ema(close, 200), lookahead=barmerge.lookahead_off) h4_close = request.security(syminfo.tickerid, "240", close, lookahead=barmerge.lookahead_off) h4_uptrend = h4_close > h4_ema200 h4_downtrend = h4_close < h4_ema200 h4_buy_ok = not i_h4 or h4_uptrend h4_sell_ok = not i_h4 or h4_downtrend plot(h4_ema200, "H4 EMA 200", color=color.new(color.purple, 50), linewidth=1, style=plot.style_stepline) // ══════════════════════════════════════════════════════════════ // TIMEFRAME GUARD // ══════════════════════════════════════════════════════════════ is_m15 = i_anyTF or timeframe.period == "15" if not is_m15 and barstate.islast label.new(bar_index, high, "Consultya: this script runs on the 15-minute timeframe only", color=color.yellow, textcolor=color.black, style=label.style_label_down, size=size.normal) // ══════════════════════════════════════════════════════════════ // SUPPORT / RESISTANCE DETECTION // ══════════════════════════════════════════════════════════════ var array<float> supArr = array.new_float(0) var array<float> resArr = array.new_float(0) var array<line> supLns = array.new_line(0) var array<line> resLns = array.new_line(0) f_near(arr, lvl, gap) => found = false n = array.size(arr) if n > 0 for j = 0 to n - 1 if math.abs(array.get(arr, j) - lvl) < gap found := true break found ph = ta.pivothigh(high, i_swing, i_swing) pl = ta.pivotlow(low, i_swing, i_swing) if not na(ph) and is_m15 gap = close * i_minD / 100.0 if ph > close and not f_near(resArr, ph, gap) if array.size(resArr) >= i_maxR line.delete(array.shift(resLns)) array.shift(resArr) array.push(resArr, ph) ln = line.new(bar_index - i_swing, ph, bar_index, ph, extend=extend.right, color=color.new(#FF4500, 20), style=line.style_dashed, width=1) array.push(resLns, ln) if not na(pl) and is_m15 gap = close * i_minD / 100.0 if pl < close and not f_near(supArr, pl, gap) if array.size(supArr) >= i_maxS line.delete(array.shift(supLns)) array.shift(supArr) array.push(supArr, pl) ln = line.new(bar_index - i_swing, pl, bar_index, pl, extend=extend.right, color=color.new(color.lime, 20), style=line.style_solid, width=1) array.push(supLns, ln) // ══════════════════════════════════════════════════════════════ // SIGNAL STATE // ══════════════════════════════════════════════════════════════ var int lastSig = -999 var bool sigOn = false var bool sigBuy = true var float sigEntry = na var float sigSL = na var float sigSL0 = na // the ORIGINAL stop - this is what defines 1R var float sigTP1 = na var float sigTP2 = na var float sigTP3 = na var int sigBar = -1 var bool sigDone = false var bool t1Hit = false var bool t2Hit = false // Partial-exit bookkeeping for the signal currently running. var float bookedR = 0.0 // R already banked from closed portions var float openFr = 1.0 // fraction of the position still open // ══════════════════════════════════════════════════════════════ // OUTCOME COUNTERS // ══════════════════════════════════════════════════════════════ var int cTotal = 0 var int cTP3 = 0 var int cTP2 = 0 var int cT1Stop = 0 var int cLoss = 0 var int cExp = 0 var int cWin = 0 // finished above breakeven var int cFlat = 0 // finished at breakeven var int cDown = 0 // finished below breakeven var float sumR = 0.0 var float bestR = 0.0 var float worstR = 0.0 var float sumPts = 0.0 // same results, in price points instead of R // Return a price expressed in R. Positive means it sits beyond the entry // in the trade's favour. Pine forbids a function writing to a global, so // this only computes - the totals are updated at the call site. f_R(float px) => risk = math.abs(sigEntry - sigSL0) risk > 0 and not na(px) ? (sigBuy ? (px - sigEntry) / risk : (sigEntry - px) / risk) : na // ══════════════════════════════════════════════════════════════ // INFO PANEL // ══════════════════════════════════════════════════════════════ var table pnl = table.new(position.top_right, 2, 10, bgcolor=#0D1117, border_width=1, border_color=#2A2D4A, frame_width=2, frame_color=#2A2D4A) // ══════════════════════════════════════════════════════════════ // HELPERS // ══════════════════════════════════════════════════════════════ f_nearestAbove(tp) => float best = na n = array.size(resArr) if n > 0 for j = 0 to n - 1 v = array.get(resArr, j) if v > tp and (na(best) or v < best) best := v best f_nearestBelow(tp) => float best = na n = array.size(supArr) if n > 0 for j = 0 to n - 1 v = array.get(supArr, j) if v < tp and (na(best) or v > best) best := v best f_tps(isBuy, price, base, rng, atr) => maxSL = price * (i_slPct / 100.0) sl_ = isBuy ? base - math.min(rng * 0.5, maxSL) : base + math.min(rng * 0.5, maxSL) tp1_ = isBuy ? price + atr * i_tp1x : price - atr * i_tp1x nr = f_nearestAbove(tp1_) ns = f_nearestBelow(tp1_) tp2_ = isBuy ? (not na(nr) and nr > tp1_ ? nr : price + atr * i_tp2x) : (not na(ns) and ns < tp1_ ? ns : price - atr * i_tp2x) tp3r = isBuy ? price + atr * i_tp3x : price - atr * i_tp3x tp3_ = isBuy ? (tp3r <= tp2_ ? tp2_ + (tp2_ - price) * 0.5 : tp3r) : (tp3r >= tp2_ ? tp2_ - (price - tp2_) * 0.5 : tp3r) [sl_, tp1_, tp2_, tp3_] // ══════════════════════════════════════════════════════════════ // MAIN LOGIC — evaluated on confirmed bars only (no repainting) // ══════════════════════════════════════════════════════════════ if is_m15 and barstate.isconfirmed rng = high - low third = 1.0 / 3.0 // Holds this bar's closed-trade result, if a signal closed on it. // At most one signal can close per bar, so one slot is enough. float barR = na // ── Track the outcome of the active signal ─────────────────────────── if sigOn and not sigDone and sigBar >= 0 and bar_index > sigBar if sigBuy if low <= sigSL label.new(bar_index, sigSL, "✕ SL", color=#C83C3C, textcolor=color.white, style=label.style_label_up, size=size.tiny) barR := bookedR + openFr * f_R(sigSL) sigOn := false sigDone := true if t2Hit cTP2 += 1 else if t1Hit cT1Stop += 1 else cLoss += 1 else if high >= sigTP3 if not t1Hit label.new(bar_index, sigTP1, ">> TP1", color=#00C8A0, textcolor=color.black, style=label.style_label_down, size=size.tiny) t1Hit := true if i_partial bookedR += third * f_R(sigTP1) openFr -= third if not t2Hit label.new(bar_index, sigTP2, ">> TP2", color=#00D0A8, textcolor=color.black, style=label.style_label_down, size=size.tiny) t2Hit := true if i_partial bookedR += third * f_R(sigTP2) openFr -= third label.new(bar_index, sigTP3, ">> TP3", color=#00EB78, textcolor=color.black, style=label.style_label_down, size=size.tiny) barR := bookedR + openFr * f_R(sigTP3) bookedR := barR openFr := 0.0 sigDone := true cTP3 += 1 else if not t2Hit and high >= sigTP2 if not t1Hit label.new(bar_index, sigTP1, ">> TP1", color=#00C8A0, textcolor=color.black, style=label.style_label_down, size=size.tiny) t1Hit := true if i_partial bookedR += third * f_R(sigTP1) openFr -= third label.new(bar_index, sigTP2, ">> TP2", color=#00D0A8, textcolor=color.black, style=label.style_label_down, size=size.tiny) t2Hit := true if i_partial bookedR += third * f_R(sigTP2) openFr -= third sigSL := sigTP1 else if not t1Hit and high >= sigTP1 label.new(bar_index, sigTP1, ">> TP1", color=#00C8A0, textcolor=color.black, style=label.style_label_down, size=size.tiny) t1Hit := true if i_partial bookedR += third * f_R(sigTP1) openFr -= third if i_beTP1 sigSL := sigEntry else // SELL if high >= sigSL label.new(bar_index, sigSL, "✕ SL", color=#C83C3C, textcolor=color.white, style=label.style_label_down, size=size.tiny) barR := bookedR + openFr * f_R(sigSL) sigOn := false sigDone := true if t2Hit cTP2 += 1 else if t1Hit cT1Stop += 1 else cLoss += 1 else if low <= sigTP3 if not t1Hit label.new(bar_index, sigTP1, ">> TP1", color=#00C8A0, textcolor=color.black, style=label.style_label_up, size=size.tiny) t1Hit := true if i_partial bookedR += third * f_R(sigTP1) openFr -= third if not t2Hit label.new(bar_index, sigTP2, ">> TP2", color=#00D0A8, textcolor=color.black, style=label.style_label_up, size=size.tiny) t2Hit := true if i_partial bookedR += third * f_R(sigTP2) openFr -= third label.new(bar_index, sigTP3, ">> TP3", color=#00EB78, textcolor=color.black, style=label.style_label_up, size=size.tiny) barR := bookedR + openFr * f_R(sigTP3) bookedR := barR openFr := 0.0 sigDone := true cTP3 += 1 else if not t2Hit and low <= sigTP2 if not t1Hit label.new(bar_index, sigTP1, ">> TP1", color=#00C8A0, textcolor=color.black, style=label.style_label_up, size=size.tiny) t1Hit := true if i_partial bookedR += third * f_R(sigTP1) openFr -= third label.new(bar_index, sigTP2, ">> TP2", color=#00D0A8, textcolor=color.black, style=label.style_label_up, size=size.tiny) t2Hit := true if i_partial bookedR += third * f_R(sigTP2) openFr -= third sigSL := sigTP1 else if not t1Hit and low <= sigTP1 label.new(bar_index, sigTP1, ">> TP1", color=#00C8A0, textcolor=color.black, style=label.style_label_up, size=size.tiny) t1Hit := true if i_partial bookedR += third * f_R(sigTP1) openFr -= third if i_beTP1 sigSL := sigEntry // ── Close the signal once it has run out of time ───────────────────── if sigOn and not sigDone and i_exp > 0 and bar_index - sigBar >= i_exp label.new(bar_index, sigBuy ? low : high, "⏳", color=#808080, textcolor=color.white, style=sigBuy ? label.style_label_up : label.style_label_down, size=size.tiny) barR := bookedR + openFr * f_R(close) sigOn := false sigDone := true if t2Hit cTP2 += 1 else if t1Hit cT1Stop += 1 else cExp += 1 // ── Fold this bar's result into the running totals ─────────────────── if not na(barR) sumR += barR sumPts += barR * math.abs(sigEntry - sigSL0) bestR := math.max(bestR, barR) worstR := math.min(worstR, barR) if barR > 0.001 cWin += 1 else if barR < -0.001 cDown += 1 else cFlat += 1 // ── Signal detection ───────────────────────────────────────────────── // Nothing fires before the research start date, so the tally always // covers the same window regardless of how many bars loaded. inWindow = time >= i_start canSig = inWindow and bar_index - lastSig >= i_cool actBuy = sigOn and not sigDone and sigBuy actSell = sigOn and not sigDone and not sigBuy fire_buy = false fire_sell = false buy_base = low sell_base = high buy_clr = color.lime sell_clr = color.orange if canSig upTrd = ema20 > ema50 dnTrd = ema20 < ema50 abv200 = close > ema200 blw200 = close < ema200 strongU = rng > 0 and (close - low) >= rng * 0.55 strongD = rng > 0 and (high - close) >= rng * 0.55 rsiOk = rsiVal >= 35.0 and rsiVal <= 65.0 // ── Signal 1: EMA Bounce ───────────────────────────────────────── if upTrd and abv200 and low <= ema20 and close > ema20 and strongU and rsiOk and h4_buy_ok and not actBuy fire_buy := true buy_base := low buy_clr := color.lime else if dnTrd and blw200 and high >= ema20 and close < ema20 and strongD and rsiOk and h4_sell_ok and not actSell fire_sell := true sell_base := high sell_clr := color.orange // ── Signal 2: SR Bounce ────────────────────────────────────────── if not fire_buy and not fire_sell tol = close * 0.0015 n_s = array.size(supArr) if n_s > 0 for j = 0 to n_s - 1 lvl = array.get(supArr, j) if lvl < close and low <= lvl + tol and low >= lvl - tol * 2.0 and (close - low) >= rng * 0.55 and rsiVal < 60.0 and h4_buy_ok and not actBuy fire_buy := true buy_base := lvl buy_clr := color.aqua break if not fire_buy n_r = array.size(resArr) if n_r > 0 for j = 0 to n_r - 1 lvl = array.get(resArr, j) if lvl > close and high >= lvl - tol and high <= lvl + tol * 2.0 and (high - close) >= rng * 0.55 and rsiVal > 40.0 and h4_sell_ok and not actSell fire_sell := true sell_base := lvl sell_clr := color.yellow break // ── Signal 3: N-Bar Breakout ───────────────────────────────────── if not fire_buy and not fire_sell and bar_index >= i_brk + 1 hh = ta.highest(high, i_brk)[1] ll = ta.lowest(low, i_brk)[1] if close > hh and rng > 0 and (close - low) >= rng * 0.5 and rsiVal < i_brkHi and h4_buy_ok and not actBuy fire_buy := true buy_base := low buy_clr := color.white else if close < ll and rng > 0 and (high - close) >= rng * 0.5 and rsiVal > i_brkLo and h4_sell_ok and not actSell fire_sell := true sell_base := high sell_clr := color.fuchsia // ── Emit the signal and store its levels ───────────────────────────── if fire_buy [sl_, tp1_, tp2_, tp3_] = f_tps(true, close, buy_base, rng, atrVal) off = close * 0.002 tip = "Entry: " + str.tostring(close, format.mintick) + " SL: " + str.tostring(sl_, format.mintick) + " TP1: " + str.tostring(tp1_, format.mintick) label.new(bar_index, buy_base - off, "", color=buy_clr, style=label.style_arrowup, size=size.small, tooltip=tip) sigOn := true sigBuy := true sigEntry := close sigSL := sl_ sigSL0 := sl_ sigTP1 := tp1_ sigTP2 := tp2_ sigTP3 := tp3_ sigBar := bar_index sigDone := false t1Hit := false t2Hit := false bookedR := 0.0 openFr := 1.0 lastSig := bar_index cTotal += 1 if fire_sell and not fire_buy [sl_, tp1_, tp2_, tp3_] = f_tps(false, close, sell_base, rng, atrVal) off = close * 0.002 tip = "Entry: " + str.tostring(close, format.mintick) + " SL: " + str.tostring(sl_, format.mintick) + " TP1: " + str.tostring(tp1_, format.mintick) label.new(bar_index, sell_base + off, "", color=sell_clr, style=label.style_arrowdown, size=size.small, tooltip=tip) sigOn := true sigBuy := false sigEntry := close sigSL := sl_ sigSL0 := sl_ sigTP1 := tp1_ sigTP2 := tp2_ sigTP3 := tp3_ sigBar := bar_index sigDone := false t1Hit := false t2Hit := false bookedR := 0.0 openFr := 1.0 lastSig := bar_index cTotal += 1 // ══════════════════════════════════════════════════════════════ // PANEL UPDATE — refreshes on the last bar // ══════════════════════════════════════════════════════════════ if is_m15 and barstate.islast and sigOn barsEl = bar_index - sigBar expired = not sigDone and i_exp > 0 and barsEl >= i_exp stat_txt = expired ? "-- Expired" : sigDone ? ">> TP3 hit — signal complete" : t2Hit ? ">> TP2 hit — SL moved to TP1" : t1Hit ? (i_beTP1 ? ">> TP1 hit — SL at entry" : ">> TP1 hit — stop unchanged") : "● Active" stat_clr = expired ? #808080 : sigDone ? #00EB78 : t2Hit ? #00D0A0 : t1Hit ? #00C0B0 : #50B450 dir_txt = sigBuy ? "BUY +" : "SELL -" dir_clr = sigBuy ? #00D25A : #DC3C3C sl_lbl = str.tostring(sigSL, format.mintick) + (t2Hit ? " [TP1]" : t1Hit and i_beTP1 ? " [BE]" : "") hdr_bg = #1A1E3A row_bg = #0D1117 dir_bg = sigBuy ? #003A18 : #3A0010 table.cell(pnl, 0, 0, "▣ CONSULTYA", text_color=#8AB4FF, text_size=size.normal, bgcolor=hdr_bg, text_halign=text.align_left) table.cell(pnl, 1, 0, "", text_color=#8AB4FF, text_size=size.normal, bgcolor=hdr_bg) table.cell(pnl, 0, 1, dir_txt, text_color=dir_clr, text_size=size.large, bgcolor=dir_bg, text_halign=text.align_center) table.cell(pnl, 1, 1, "", text_color=dir_clr, text_size=size.large, bgcolor=dir_bg) table.cell(pnl, 0, 2, "Entry", text_color=#BBBBBB, text_size=size.normal, bgcolor=row_bg, text_halign=text.align_left) table.cell(pnl, 1, 2, str.tostring(sigEntry, format.mintick), text_color=color.white, text_size=size.normal, bgcolor=row_bg, text_halign=text.align_right) table.cell(pnl, 0, 3, "SL", text_color=#FF6060, text_size=size.normal, bgcolor=row_bg, text_halign=text.align_left) table.cell(pnl, 1, 3, sl_lbl, text_color=#FF8080, text_size=size.normal, bgcolor=row_bg, text_halign=text.align_right) table.cell(pnl, 0, 4, "TP1", text_color=#BBBBBB, text_size=size.normal, bgcolor=row_bg, text_halign=text.align_left) table.cell(pnl, 1, 4, str.tostring(sigTP1, format.mintick), text_color=#64C8FF, text_size=size.normal, bgcolor=row_bg, text_halign=text.align_right) table.cell(pnl, 0, 5, "TP2", text_color=#BBBBBB, text_size=size.normal, bgcolor=row_bg, text_halign=text.align_left) table.cell(pnl, 1, 5, str.tostring(sigTP2, format.mintick), text_color=#00E5CC, text_size=size.normal, bgcolor=row_bg, text_halign=text.align_right) table.cell(pnl, 0, 6, "TP3", text_color=#BBBBBB, text_size=size.normal, bgcolor=row_bg, text_halign=text.align_left) table.cell(pnl, 1, 6, str.tostring(sigTP3, format.mintick), text_color=#00FF88, text_size=size.normal, bgcolor=row_bg, text_halign=text.align_right) table.cell(pnl, 0, 7, "Booked", text_color=#555577, text_size=size.small, bgcolor=row_bg, text_halign=text.align_left) table.cell(pnl, 1, 7, str.tostring(bookedR, "#.##") + "R · " + str.tostring(openFr * 100, "#") + "% open", text_color=#8899AA, text_size=size.small, bgcolor=row_bg, text_halign=text.align_right) table.cell(pnl, 0, 8, stat_txt, text_color=stat_clr, text_size=size.normal, bgcolor=hdr_bg, text_halign=text.align_left) table.cell(pnl, 1, 8, "", text_color=stat_clr, text_size=size.normal, bgcolor=hdr_bg) h4_lbl = not i_h4 ? "H4 OFF" : h4_uptrend ? "H4 uptrend" : "H4 downtrend" h4_clr = not i_h4 ? #555555 : h4_uptrend ? #00C853 : #FF1744 table.cell(pnl, 0, 9, h4_lbl, text_color=h4_clr, text_size=size.small, bgcolor=#0A0D15, text_halign=text.align_left) table.cell(pnl, 1, 9, "", text_color=h4_clr, text_size=size.small, bgcolor=#0A0D15) // ══════════════════════════════════════════════════════════════ // RESEARCH TALLY // ══════════════════════════════════════════════════════════════ var table stats = table.new(position.bottom_right, 2, 12, bgcolor=#0D1117, border_width=1, border_color=#2A2D4A, frame_width=2, frame_color=#2A2D4A) if is_m15 and barstate.islast and i_stats closed = cWin + cFlat + cDown avgR = closed > 0 ? sumR / closed : 0.0 // With partial exits on, "profitable" means the trade finished with // money banked - which is what a subscriber actually experiences. wrTxt = closed > 0 ? str.tostring(cWin * 100.0 / closed, "#.#") + "%" : "-" t1Txt = closed > 0 ? str.tostring((cTP3 + cTP2 + cT1Stop) * 100.0 / closed, "#.#") + "%" : "-" rClr = sumR > 0 ? #00E58A : sumR < 0 ? #FF6060 : #9AAABB modeTxt = (i_partial ? "thirds" : "all-or-nothing") + (i_beTP1 ? " · BE at TP1" : " · stop to TP2") // Under roughly 200 closed trades the numbers are mostly noise, so // the sample size is flagged rather than left for the eye to judge. nTxt = closed >= 300 ? "" : closed >= 200 ? " (thin)" : " (TOO FEW)" nClr = closed >= 300 ? #8899AA : closed >= 200 ? #F0C040 : #FF6060 sHdr = #1A1E3A sRow = #0D1117 table.cell(stats, 0, 0, "RESEARCH TALLY", text_color=#8AB4FF, text_size=size.normal, bgcolor=sHdr, text_halign=text.align_left) table.cell(stats, 1, 0, str.tostring(cTotal) + " fired", text_color=color.white, text_size=size.normal, bgcolor=sHdr, text_halign=text.align_right) table.cell(stats, 0, 1, "Total R", text_color=#BBBBBB, text_size=size.large, bgcolor=sRow, text_halign=text.align_left) table.cell(stats, 1, 1, str.tostring(sumR, "#.#") + "R", text_color=rClr, text_size=size.large, bgcolor=sRow, text_halign=text.align_right) table.cell(stats, 0, 2, "Avg R per trade", text_color=#BBBBBB, text_size=size.normal, bgcolor=sRow, text_halign=text.align_left) table.cell(stats, 1, 2, str.tostring(avgR, "#.###") + "R", text_color=rClr, text_size=size.normal, bgcolor=sRow, text_halign=text.align_right) table.cell(stats, 0, 3, "Closed in profit", text_color=#BBBBBB, text_size=size.normal, bgcolor=sRow, text_halign=text.align_left) table.cell(stats, 1, 3, wrTxt, text_color=#00E58A, text_size=size.normal, bgcolor=sRow, text_halign=text.align_right) table.cell(stats, 0, 4, "Reached TP1", text_color=#BBBBBB, text_size=size.small, bgcolor=sRow, text_halign=text.align_left) table.cell(stats, 1, 4, t1Txt, text_color=#9AAABB, text_size=size.small, bgcolor=sRow, text_halign=text.align_right) table.cell(stats, 0, 5, "Best / worst", text_color=#BBBBBB, text_size=size.small, bgcolor=sRow, text_halign=text.align_left) table.cell(stats, 1, 5, str.tostring(bestR, "#.#") + "R / " + str.tostring(worstR, "#.#") + "R", text_color=#9AAABB, text_size=size.small, bgcolor=sRow, text_halign=text.align_right) table.cell(stats, 0, 6, "Profit / flat / loss", text_color=#BBBBBB, text_size=size.small, bgcolor=sRow, text_halign=text.align_left) table.cell(stats, 1, 6, str.tostring(cWin) + " / " + str.tostring(cFlat) + " / " + str.tostring(cDown), text_color=#9AAABB, text_size=size.small, bgcolor=sRow, text_halign=text.align_right) table.cell(stats, 0, 7, "TP3 reached", text_color=#BBBBBB, text_size=size.normal, bgcolor=sRow, text_halign=text.align_left) table.cell(stats, 1, 7, str.tostring(cTP3), text_color=#00FF88, text_size=size.normal, bgcolor=sRow, text_halign=text.align_right) table.cell(stats, 0, 8, "TP2, closed at TP1", text_color=#BBBBBB, text_size=size.normal, bgcolor=sRow, text_halign=text.align_left) table.cell(stats, 1, 8, str.tostring(cTP2), text_color=#00E5CC, text_size=size.normal, bgcolor=sRow, text_halign=text.align_right) table.cell(stats, 0, 9, "TP1, then stopped", text_color=#BBBBBB, text_size=size.normal, bgcolor=sRow, text_halign=text.align_left) table.cell(stats, 1, 9, str.tostring(cT1Stop), text_color=#64C8FF, text_size=size.normal, bgcolor=sRow, text_halign=text.align_right) table.cell(stats, 0, 10, modeTxt, text_color=#8899AA, text_size=size.small, bgcolor=#0A0D15, text_halign=text.align_left) table.cell(stats, 1, 10, str.tostring(closed) + " closed" + nTxt, text_color=nClr, text_size=size.small, bgcolor=#0A0D15, text_halign=text.align_right) table.cell(stats, 0, 11, "Total points", text_color=#BBBBBB, text_size=size.normal, bgcolor=sRow, text_halign=text.align_left) table.cell(stats, 1, 11, str.tostring(sumPts, "#.#"), text_color=rClr, text_size=size.normal, bgcolor=sRow, text_halign=text.align_right)
A measurement tool, not a trading system. It does not account for spread, slippage or commission.
© 2026 Consultya — published for reading and verification.