Free indicator · TradingView · open source

Prop Firm Guard: your limits, live on the chart

A free, read-only TradingView indicator that keeps the two numbers that end most challenges in front of you: how much room is left before the daily and max loss limits, and how many full stops you can still take at your current risk.

What it shows

Today's loss against the daily limit · room to the daily and max loss limits (static or trailing) · full stops left before a breach, with an OK / CAUTION / STOP status · position size for the chart symbol (lots for forex and gold, contracts for futures), using the symbol's own point value and currency conversion · optional dashed lines at the price where an open trade would break each rule.

Add to TradingViewCopy the codeAll free tools
PROP FIRM GUARDOK
Today-850.00 (17% of daily limit)
Room to daily limit4,150.00
Room to max loss9,150.00
Risk per trade500.00 USD
Full stops before a breach8
Size for this stop on EURUSD1.00 lots

Install it in one minute

  1. Open the Prop Firm Guard page on TradingView, click Use on chart and add it to favorites. Or, if you prefer, copy the code below into the Pine Editor, click Save and then Add to chart.
  2. In the settings type your starting balance, today's starting balance, current equity and your prop's daily and max loss rules.
  3. Check "Full stops before a breach" before every trade.

The code

Pine Script v6, open source. It never places, modifies or closes orders.

//@version=6
// Prop Firm Guard - free, open source. Read-only: it never places, changes or closes orders.
indicator("Prop Firm Guard - Daily Loss, Drawdown and Position Size", shorttitle = "Prop Firm Guard", overlay = true)

// ------------- Account and prop rules
gA = "Account and prop rules"
startBal  = input.float(100000, "Starting balance", minval = 1, group = gA)
dayStart  = input.float(100000, "Balance at start of today", minval = 0, group = gA, tooltip = "Closed balance when your prop's trading day started (usually server midnight).")
equity    = input.float(100000, "Current equity", minval = 0, group = gA, tooltip = "Balance plus open profit or loss, as shown in your platform right now.")
dailyPct  = input.float(5.0, "Daily loss limit (%)", minval = 0, step = 0.5, group = gA)
dailyBase = input.string("Starting balance", "Daily limit calculated on", options = ["Starting balance", "Balance at start of day", "Lower of the two"], group = gA, tooltip = "Props differ: some use the initial balance, others the balance at the start of each day. If unsure, pick the lower of the two.")
maxPct    = input.float(10.0, "Max loss limit (%)", minval = 0, step = 0.5, group = gA)
ddType    = input.string("Static", "Max loss type", options = ["Static", "Trailing"], group = gA, tooltip = "Static: fixed floor below the starting balance. Trailing: the floor follows your highest balance.")
peakBal   = input.float(100000, "Highest balance reached (trailing only)", minval = 0, group = gA)
trailLock = input.bool(true, "Trailing floor stops at the starting balance", group = gA)

// ------------- Risk per trade and position size
gR = "Risk per trade and position size"
riskMode = input.string("% of starting balance", "Risk per trade", options = ["% of starting balance", "Fixed amount"], group = gR)
riskPct  = input.float(0.5, "Risk per trade (%)", minval = 0, step = 0.1, group = gR)
riskAmt  = input.float(500, "Risk per trade (amount)", minval = 0, group = gR)
acctCcy  = input.string("USD", "Account currency", options = ["USD", "EUR", "GBP", "CHF", "JPY", "AUD", "CAD"], group = gR)
stopMode = input.string("ATR multiple", "Stop distance", options = ["ATR multiple", "Price distance"], group = gR)
atrLen   = input.int(14, "ATR length", minval = 1, group = gR)
atrMult  = input.float(1.5, "ATR multiple", minval = 0.1, step = 0.1, group = gR)
stopDist = input.float(0.0050, "Price distance", minval = 0, group = gR, tooltip = "Distance between entry and stop in price units (e.g. 0.0050 = 50 pips on EURUSD, 25 = 25 points on NQ).")
fxLot    = input.float(100000, "Units per lot (forex)", minval = 1, group = gR)
metalLot = input.float(100, "Units per lot (gold; silver uses 5000)", minval = 1, group = gR)

// ------------- Open position (optional)
gP = "Open position (optional): where would it break the rules?"
showPos = input.bool(false, "Show breach levels for an open position", group = gP, tooltip = "Enter 'Current equity' WITHOUT this position (balance plus other open trades), then the entry and size of this position.")
side    = input.string("Long", "Side", options = ["Long", "Short"], group = gP)
entryPx = input.float(0.0, "Entry price", minval = 0, group = gP)
posSize = input.float(1.0, "Size (lots for forex/metals, contracts for futures, units otherwise)", minval = 0, group = gP)

// ------------- Display
gD = "Display"
panelPos = input.string("Top right", "Panel position", options = ["Top right", "Top left", "Bottom right", "Bottom left"], group = gD)
txtSize  = input.string("Small", "Text size", options = ["Tiny", "Small", "Normal"], group = gD)

// ------------- Prop limits
baseDay  = dailyBase == "Starting balance" ? startBal : dailyBase == "Balance at start of day" ? dayStart : math.min(startBal, dayStart)
dayLimit = baseDay * dailyPct / 100.0
dayLoss  = dayStart - equity
roomDay  = dayLimit - dayLoss
maxAmt   = startBal * maxPct / 100.0
floorBal = ddType == "Static" ? startBal - maxAmt : trailLock ? math.min(peakBal - maxAmt, startBal) : peakBal - maxAmt
roomMax  = equity - floorBal
roomAll  = math.min(roomDay, roomMax)
risk     = riskMode == "% of starting balance" ? startBal * riskPct / 100.0 : riskAmt
lossesLeft = risk > 0 ? math.max(math.ceil(roomAll / risk - 0.000000001) - 1, 0) : na   // touching the floor is already a breach

// ------------- Position size for the chart symbol
fxRate   = request.currency_rate(syminfo.currency, acctCcy, ignore_invalid_currency = true)
atrVal   = ta.atr(atrLen)
dist     = stopMode == "ATR multiple" ? atrVal * atrMult : stopDist
perUnit  = nz(fxRate, 1.0) * syminfo.pointvalue           // account money per 1.0 price move, per unit
tkr      = str.upper(syminfo.ticker)
isFut    = syminfo.type == "futures"
isSpot   = not isFut and syminfo.type != "stock" and syminfo.type != "fund"
isGold   = isSpot and (syminfo.basecurrency == "XAU" or str.startswith(tkr, "XAU") or str.startswith(tkr, "GOLD"))
isSilver = isSpot and (syminfo.basecurrency == "XAG" or str.startswith(tkr, "XAG") or str.startswith(tkr, "SILVER"))
isMetal  = isGold or isSilver
isFx     = syminfo.type == "forex" and not isMetal
lotUnits = isFx ? fxLot : isSilver ? 5000.0 : isGold ? metalLot : 1.0
units    = dist > 0 and perUnit > 0 ? risk / (dist * perUnit) : na
sizeTxt  = na(units) ? "n/a" : isFut ? str.tostring(math.floor(units)) + " contracts" : (isFx or isMetal) ? str.tostring(math.floor(units / lotUnits * 100) / 100, "0.00") + " lots" : str.tostring(units, "#.####") + " units"

// ------------- Breach levels of an open position
qty        = (isFx or isMetal) ? posSize * lotUnits : posSize
perPrice   = qty * perUnit
dir        = side == "Long" ? -1.0 : 1.0
dayBreach  = showPos and entryPx > 0 and perPrice > 0 ? entryPx + dir * roomDay / perPrice : na
maxBreach  = showPos and entryPx > 0 and perPrice > 0 ? entryPx + dir * roomMax / perPrice : na

// ------------- Panel
money(x) => str.tostring(x, "#,##0.00")
tPos = panelPos == "Top right" ? position.top_right : panelPos == "Top left" ? position.top_left : panelPos == "Bottom right" ? position.bottom_right : position.bottom_left
tSz  = txtSize == "Tiny" ? size.tiny : txtSize == "Normal" ? size.normal : size.small
var table panel = table.new(tPos, 2, 10, bgcolor = color.new(#0A0F1C, 5), border_color = color.new(color.gray, 70), border_width = 1)

cell(r, a, b, col) =>
    table.cell(panel, 0, r, a, text_color = color.new(color.silver, 0), text_halign = text.align_left, text_size = tSz)
    table.cell(panel, 1, r, b, text_color = col, text_halign = text.align_right, text_size = tSz)

if barstate.islast
    stTxt = roomAll <= 0 ? "LIMIT BREACHED" : lossesLeft < 1 ? "STOP: next full stop breaches" : lossesLeft == 1 ? "CAUTION: one full stop left" : "OK"
    stCol = roomAll <= 0 or lossesLeft < 1 ? color.red : lossesLeft == 1 ? color.orange : color.lime
    usedPct = dayLimit > 0 ? math.max(dayLoss, 0) / dayLimit * 100 : 0
    table.cell(panel, 0, 0, "PROP FIRM GUARD", text_color = color.new(#38BDF8, 0), text_halign = text.align_left, text_size = tSz)
    table.cell(panel, 1, 0, stTxt, text_color = stCol, text_halign = text.align_right, text_size = tSz)
    cell(1, "Today", (dayLoss > 0 ? "-" : "+") + money(math.abs(dayLoss)) + " (" + str.tostring(usedPct, "0") + "% of daily limit)", usedPct >= 70 ? color.orange : color.white)
    cell(2, "Room to daily limit", money(roomDay), roomDay <= 0 ? color.red : color.white)
    cell(3, "Room to max loss", money(roomMax) + "  (floor " + money(floorBal) + ")", roomMax <= 0 ? color.red : color.white)
    cell(4, "Risk per trade", money(risk) + " " + acctCcy, color.white)
    cell(5, "Full stops before a breach", na(lossesLeft) ? "n/a" : str.tostring(math.max(lossesLeft, 0)), stCol)
    cell(6, "Stop distance", str.tostring(dist, format.mintick) + (stopMode == "ATR multiple" ? "  (" + str.tostring(atrMult) + " x ATR" + str.tostring(atrLen) + ")" : ""), color.white)
    cell(7, "Size for this stop on " + syminfo.ticker, sizeTxt, color.white)
    if showPos and entryPx > 0
        cell(8, "Daily limit breached at", na(dayBreach) ? "n/a" : str.tostring(dayBreach, format.mintick), color.orange)
        cell(9, "Max loss breached at", na(maxBreach) ? "n/a" : str.tostring(maxBreach, format.mintick), color.red)

// ------------- Breach lines on the chart
var line  lnDay = na
var line  lnMax = na
var label lbDay = na
var label lbMax = na
if barstate.islast
    line.delete(lnDay)
    line.delete(lnMax)
    label.delete(lbDay)
    label.delete(lbMax)
    if not na(dayBreach) and dayBreach > 0
        lnDay := line.new(bar_index - 30, dayBreach, bar_index, dayBreach, extend = extend.right, color = color.orange, style = line.style_dashed, width = 1)
        lbDay := label.new(bar_index + 2, dayBreach, "Daily limit breach " + str.tostring(dayBreach, format.mintick), style = label.style_label_left, color = color.new(color.orange, 20), textcolor = color.black, size = size.small)
    if not na(maxBreach) and maxBreach > 0
        lnMax := line.new(bar_index - 30, maxBreach, bar_index, maxBreach, extend = extend.right, color = color.red, style = line.style_dashed, width = 1)
        lbMax := label.new(bar_index + 2, maxBreach, "Max loss breach " + str.tostring(maxBreach, format.mintick), style = label.style_label_left, color = color.new(color.red, 20), textcolor = color.white, size = size.small)

Limits

TradingView cannot read your broker account, so the account numbers are only as current as what you type. Prop firms calculate limits differently (balance or equity, reset time, trailing on closed or open balance): use the calculators for your firm's exact method and always trust your firm's dashboard. Commissions, swaps and slippage are not included in the size.

Want it automatic on MetaTrader 5?

RetryLab Copilot reads your account by itself on MT5, adds the rules from your check-up (max trades, stop after N losses, no re-entry after a loss) and warns you before you break them.

See the Copilot

Get an email when prop rules change

FTMO, The5ers, FundedNext, FundingPips: when a daily or max loss rule changes, we update the tools and tell you.

Want an indicator with your own rules?

Prop Firm Guard shows generic limits. If you want your entry rules, alerts or your own risk panel on the chart, we can build it for TradingView or MT5, source code included.

See the Indicator Builder