Code: Select all
//@version=5
strategy("Enterprise Position Sizing Unit", overlay=true, margin_long=100, margin_short=100, initial_capital=10000)
// =============================================================================
// 1. CONFIGURATION & UI PANELS
// =============================================================================
var G_RISK = "Risk Core"
i_riskMode = input.string("Equity %", "Risk Model", options=["Equity %", "Balance %", "Fixed Cash"], group=G_RISK)
i_riskValue = input.float(1.0, "Risk Value", step=0.1, group=G_RISK, tooltip="1.0 means 1% or $1 depending on model.")
var G_INSTR = "Instrument Specifications"
i_minQty = input.float(0.01, "Min Lot Size", group=G_INSTR)
i_maxQty = input.float(100.0, "Max Lot Size", group=G_INSTR)
i_qtyStep = input.float(0.01, "Lot Step", group=G_INSTR)
i_leverage = input.float(30.0, "Max Leverage", group=G_INSTR, tooltip="Used to simulate margin availability checks.")
var G_UI = "Telemetry"
i_showHUD = input.bool(true, "Show Risk Dashboard", group=G_UI)
// =============================================================================
// 2. ARCHITECTURE: USER-DEFINED TYPES & METHODS
// =============================================================================
// @type Encapsulates all risk settings into a single configuration object
type RiskManager
string mode
float value
float minLot
float maxLot
float step
float leverage
// @type Holds the result of a sizing calculation, including telemetry and error states
type SizeResult
float qty
float riskCash
float marginReq
string status
bool isValid
// @function Core calculation method attached to the RiskManager type
method calculate(RiskManager this, float entryPrice, float slPrice) =>
float cashRisk = 0.0
// 1. Resolve Risk Model
switch this.mode
"Equity %" => cashRisk := strategy.equity * (this.value / 100.0)
"Balance %" => cashRisk := strategy.closedtrades.profit > 0 ? strategy.initial_capital + strategy.netprofit : strategy.initial_capital // Approx balance
"Fixed Cash" => cashRisk := this.value
// 2. Division-by-Zero & Validity Check
float slDist = math.abs(entryPrice - slPrice)
if slDist == 0
SizeResult.new(0.0, cashRisk, 0.0, "ERR: SL = Entry", false)
else
// 3. Base Calculation
float rawQty = cashRisk / (slDist * syminfo.pointvalue)
// 4. Floor Rounding to Lot Step
float roundedQty = math.floor(rawQty / this.step) * this.step
// 5. Margin Constraint Check (Approximate Notional / Leverage)
// Note: Assumes base currency matches account currency for simplicity in this template
float notionalValue = roundedQty * entryPrice
float requiredMargin = notionalValue / this.leverage
bool marginExceeded = requiredMargin > strategy.equity
// 6. Hard Limit Validations
if marginExceeded
SizeResult.new(0.0, cashRisk, requiredMargin, "ERR: Margin Exceeded", false)
else if roundedQty < this.minLot
SizeResult.new(0.0, cashRisk, requiredMargin, "ERR: Below Min Lot", false)
else if roundedQty > this.maxLot
SizeResult.new(this.maxLot, cashRisk, requiredMargin, "WARN: Capped at Max", true)
else
SizeResult.new(roundedQty, cashRisk, requiredMargin, "OK", true)
// =============================================================================
// 3. INITIALIZATION
// =============================================================================
// Instantiate the Risk Manager object once (var)
var RiskManager rm = RiskManager.new(i_riskMode, i_riskValue, i_minQty, i_maxQty, i_qtyStep, i_leverage)
// =============================================================================
// 4. MOCK EXECUTION ENGINE
// =============================================================================
// Simple Mean Reversion entry for testing purposes
atr = ta.atr(14)
longTrigger = ta.crossover(close, ta.sma(close, 20))
shortTrigger = ta.crossunder(close, ta.sma(close, 20))
// Store the latest result for the UI Dashboard
var SizeResult lastCalc = SizeResult.new(0,0,0,"Waiting...", false)
if longTrigger
float sl = close - (atr * 2)
lastCalc := rm.calculate(close, sl) // Object-oriented method call
if lastCalc.isValid
strategy.entry("L", strategy.long, qty=lastCalc.qty)
strategy.exit("XL", "L", stop=sl)
if shortTrigger
float sl = close + (atr * 2)
lastCalc := rm.calculate(close, sl)
if lastCalc.isValid
strategy.entry("S", strategy.short, qty=lastCalc.qty)
strategy.exit("XS", "S", stop=sl)
// =============================================================================
// 5. TELEMETRY & DEBUG HUD (The "Pro" touch)
// =============================================================================
var table hud = table.new(position.bottom_right, 2, 5, border_width=1, border_color=color.gray, frame_width=1, frame_color=color.gray)
if barstate.islast and i_showHUD
// Header
table.cell(hud, 0, 0, "Risk Analytics", text_color=color.white, bgcolor=color.new(color.blue, 30), text_size=size.small, width=15)
table.cell(hud, 1, 0, "Value", text_color=color.white, bgcolor=color.new(color.blue, 30), text_size=size.small, width=15)
// Status
color statusColor = lastCalc.isValid ? color.new(color.green, 70) : color.new(color.red, 70)
table.cell(hud, 0, 1, "Last Status", text_color=color.gray, bgcolor=color.black, text_size=size.small)
table.cell(hud, 1, 1, lastCalc.status, text_color=color.white, bgcolor=statusColor, text_size=size.small)
// Target Risk Cash
table.cell(hud, 0, 2, "Target Risk ($)", text_color=color.gray, bgcolor=color.black, text_size=size.small)
table.cell(hud, 1, 2, str.tostring(lastCalc.riskCash, "#.##"), text_color=color.white, bgcolor=color.black, text_size=size.small)
// Calculated Qty
table.cell(hud, 0, 3, "Calculated Size", text_color=color.gray, bgcolor=color.black, text_size=size.small)
table.cell(hud, 1, 3, str.tostring(lastCalc.qty, "#.##"), text_color=color.white, bgcolor=color.black, text_size=size.small)
// Est. Margin
table.cell(hud, 0, 4, "Est. Margin Req.", text_color=color.gray, bgcolor=color.black, text_size=size.small)
table.cell(hud, 1, 4, str.tostring(lastCalc.marginReq, "#.##"), text_color=color.white, bgcolor=color.black, text_size=size.small)