In Pine Script, you build this by capturing the account equity at the open of a new daily session (ta.change(time("D"))), calculating the percentage deviation on every subsequent bar, and switching a boolean flag to block all strategy.entry commands if the threshold is crossed.
Here is the fully integrated script with the Daily Drawdown Circuit Breaker and an updated live dashboard:
Code: Select all
//@version=5
strategy("Kelly + Cap + Circuit Breaker", overlay=true, initial_capital=10000, default_qty_type=strategy.cash)
// =========================================================================
// 1. BOOTSTRAP INPUTS
// =========================================================================
i_initialWinRate = input.float(50.0, title="Bootstrap Win Rate (%)", minval=1.0, maxval=99.0, group="Kelly Parameters") / 100.0
i_initialRR = input.float(2.0, title="Bootstrap Reward/Risk", minval=0.1, group="Kelly Parameters")
i_fraction = input.float(0.5, title="Kelly Fraction (0.5 = Half Kelly)", minval=0.1, maxval=1.0, step=0.1, group="Kelly Parameters")
i_lookback = input.int(100, title="Rolling Trade Window", minval=10, maxval=500, group="Kelly Parameters")
// =========================================================================
// 2. RISK MANAGEMENT INPUTS (Hard Cap & Circuit Breaker)
// =========================================================================
i_atrLen = input.int(14, title="ATR Length for Stop", group="Risk Management")
i_atrMult = input.float(1.5, title="ATR Multiplier for Stop Distance", group="Risk Management")
i_maxRisk = input.float(5.0, title="Absolute Max Risk Cap (%)", minval=0.1, maxval=20.0, step=0.5, group="Risk Management") / 100.0
i_maxDailyDD = input.float(3.0, title="Max Daily Drawdown (%)", minval=0.5, maxval=10.0, step=0.5, group="Risk Management") / 100.0
// =========================================================================
// 3. DAILY DRAWDOWN CIRCUIT BREAKER LOGIC
// =========================================================================
var float startOfDayEquity = strategy.initial_capital
var bool circuitBreakerTripped = false
// Detect the start of a new trading day (resets at 00:00 exchange time)
isNewDay = ta.change(time("D"))
if isNewDay
startOfDayEquity := strategy.equity
circuitBreakerTripped := false // Reset the breaker for the new day
// Calculate the current daily P&L percentage relative to the day's open
currentDailyDD = (strategy.equity - startOfDayEquity) / startOfDayEquity
// Trip the breaker if equity drops below the threshold
if currentDailyDD <= -i_maxDailyDD
circuitBreakerTripped := true
// =========================================================================
// 4. DYNAMIC EDGE CALCULATION
// =========================================================================
var float dynWinRate = i_initialWinRate
var float dynRR = i_initialRR
int totalTrades = strategy.closedtrades
if totalTrades > 0
int wins = 0
float grossProfit = 0.0
float grossLoss = 0.0
int winCount = 0
int lossCount = 0
int startIdx = math.max(0, totalTrades - i_lookback)
int tradesToAnalyze = totalTrades - startIdx
if tradesToAnalyze > 0
for i = startIdx to totalTrades - 1
float profit = strategy.closedtrades.profit(i)
if profit > 0
wins += 1
grossProfit += profit
winCount += 1
else if profit < 0
grossLoss += math.abs(profit)
lossCount += 1
dynWinRate := wins / tradesToAnalyze
float avgWin = winCount > 0 ? grossProfit / winCount : 0.0
float avgLoss = lossCount > 0 ? grossLoss / lossCount : 0.0
dynRR := avgLoss > 0 ? avgWin / avgLoss : i_initialRR
// =========================================================================
// 5. THE KELLY MATH + HARD CAP
// =========================================================================
fullKelly = dynWinRate - ((1.0 - dynWinRate) / dynRR)
fractionalKelly = math.max(0, fullKelly * i_fraction)
appliedRisk = math.min(i_maxRisk, fractionalKelly)
riskAmount = strategy.equity * appliedRisk
atr = ta.atr(i_atrLen)
stopDist = atr * i_atrMult
positionSize = stopDist > 0 ? (riskAmount / stopDist) : 0
// =========================================================================
// 6. DUMMY ENTRY LOGIC
// =========================================================================
fastSma = ta.sma(close, 10)
slowSma = ta.sma(close, 20)
longTrigger = ta.crossover(fastSma, slowSma)
shortTrigger = ta.crossunder(fastSma, slowSma)
// =========================================================================
// 7. EXECUTION (Blocked by Circuit Breaker)
// =========================================================================
// Added "and not circuitBreakerTripped" to the entry conditions
if longTrigger and strategy.opentrades == 0 and appliedRisk > 0 and not circuitBreakerTripped
strategy.entry("Long", strategy.long, qty=positionSize)
strategy.exit("Exit Long", "Long", stop=close - stopDist, limit=close + (stopDist * dynRR))
if shortTrigger and strategy.opentrades == 0 and appliedRisk > 0 and not circuitBreakerTripped
strategy.entry("Short", strategy.short, qty=positionSize)
strategy.exit("Exit Short", "Short", stop=close + stopDist, limit=close - (stopDist * dynRR))
// =========================================================================
// 8. DASHBOARD UI
// =========================================================================
var table infoTable = table.new(position.top_right, 2, 6, border_width=1, border_color=color.gray, frame_color=color.black, frame_width=1)
if barstate.islast
table.cell(infoTable, 0, 0, "Rolling Win Rate", text_color=color.white, bgcolor=color.new(color.purple, 20))
table.cell(infoTable, 1, 0, str.tostring(dynWinRate * 100, "#.##") + "%", text_color=color.white, bgcolor=color.new(color.purple, 20))
table.cell(infoTable, 0, 1, "Rolling R/R", text_color=color.white, bgcolor=color.new(color.purple, 20))
table.cell(infoTable, 1, 1, str.tostring(dynRR, "#.##"), text_color=color.white, bgcolor=color.new(color.purple, 20))
table.cell(infoTable, 0, 2, "Fractional Kelly %", text_color=color.white, bgcolor=color.new(color.blue, 20))
table.cell(infoTable, 1, 2, str.tostring(fractionalKelly * 100, "#.##") + "%", text_color=color.white, bgcolor=color.new(color.blue, 20))
isCapped = fractionalKelly > i_maxRisk
capColor = isCapped ? color.new(color.red, 0) : color.new(color.teal, 20)
table.cell(infoTable, 0, 3, isCapped ? "RISK CAPPED AT MAX" : "Applied Risk", text_color=color.white, bgcolor=capColor)
table.cell(infoTable, 1, 3, str.tostring(appliedRisk * 100, "#.##") + "%", text_color=color.white, bgcolor=capColor)
// Circuit Breaker UI Logic
ddColor = circuitBreakerTripped ? color.new(color.red, 0) : color.new(color.gray, 20)
table.cell(infoTable, 0, 4, circuitBreakerTripped ? "CIRCUIT BREAKER ACTIVE" : "Daily Drawdown", text_color=color.white, bgcolor=ddColor)
table.cell(infoTable, 1, 4, str.tostring(currentDailyDD * 100, "#.##") + "%", text_color=color.white, bgcolor=ddColor)
table.cell(infoTable, 0, 5, "Current Risk ($)", text_color=color.white, bgcolor=color.new(color.gray, 20))
table.cell(infoTable, 1, 5, "$" + str.tostring(riskAmount, "#.##"), text_color=color.white, bgcolor=color.new(color.gray, 20))