, the architecture needs to move away from basic script logic and adopt a modular, object-oriented structure.
A "pro" implementation in Pine Script v5 utilizes custom types to isolate state, dynamic risk sizing based on precise stop-loss distances rather than arbitrary percentages, and most importantly, an on-chart "Fill Log" table to audit the execution latency and simulated slippage in real-time.
This version introduces a strict separation of concerns: Event Parsing (News Window), Signal Generation, Risk Sizing, and Trade State Auditing.
Code: Select all
//@version=5
strategy("Institutional Execution Reality & Audit", overlay=true, calc_on_every_tick=true, max_labels_count=50, max_lines_count=50, slippage=40, commission_type=strategy.commission.cash_per_contract, commission_value=2.0)
// =========================================================================
// 1. DESK PARAMETERS & CONFIGURATION
// =========================================================================
var string G_RISK = "Risk & Sizing (Desk Rules)"
var string G_EXEC = "Execution & Time Constraints"
riskPerTradePct = input.float(1.0, "Standard Risk (%)", step=0.1, group=G_RISK)
newsRiskModifier = input.float(0.5, "News Risk Multiplier (e.g., 0.5 = Half Size)", step=0.1, group=G_RISK)
minStopDistance = input.float(10.0, "Minimum Stop Distance (Ticks)", group=G_RISK)
newsSession = input.session("0830-0835", "High-Impact Release Window (EST)", group=G_EXEC)
hardTimeStop = input.int(3, "Hard Time Stop (Minutes/Bars)", group=G_EXEC)
maxSpreadTolerated = input.float(5.0, "Max Acceptable Spread Blowout (Ticks)", group=G_EXEC)
// =========================================================================
// 2. TYPES & STATE MANAGEMENT
// =========================================================================
// Custom type to manage the lifecycle and audit of a single trade
type ExecutionState
float intendedEntry
float actualFill
float stopLoss
int entryBarIndex
float spreadAtClick
bool isActive
var ExecutionState currentTrade = ExecutionState.new(na, na, na, na, na, false)
// =========================================================================
// 3. CORE LOGIC & MICROSTRUCTURE SIZING
// =========================================================================
// Timezone conversion to catch the exact print regardless of local exchange time
inNewsWindow = not na(time(timeframe.period, newsSession, "America/New_York"))
// Dynamic Risk Sizing Method
method calculatePositionSize(float riskPct, float stopDistanceTicks) =>
adjustedRisk = inNewsWindow ? (riskPct * newsRiskModifier) : riskPct
riskAmount = (strategy.equity * (adjustedRisk / 100))
tickValue = syminfo.mintick * syminfo.pointvalue
qty = riskAmount / math.max(stopDistanceTicks * tickValue, 1e-5)
math.floor(qty)
// Simulated Spread Blowout (Since Pine lacks historical Bid/Ask)
// We use a fraction of the M1 ATR to simulate the spread widening exactly at 08:30:00
simulatedSpread = ta.atr(5) * 0.2
spreadViolated = (simulatedSpread / syminfo.mintick) > maxSpreadTolerated
// =========================================================================
// 4. SIGNAL GENERATION (REPLACE WITH RAW PRICE ACTION LOGIC)
// =========================================================================
// Mock setup: 1-minute liquidity sweep and structural shift
bullishSweep = low < ta.lowest(low[1], 10) and close > open
bearishSweep = high > ta.highest(high[1], 10) and close < open
longTrigger = inNewsWindow and bullishSweep and not spreadViolated
shortTrigger = inNewsWindow and bearishSweep and not spreadViolated
// =========================================================================
// 5. EXECUTION & AUDITING
// =========================================================================
float slDistanceTicks = math.max(ta.atr(14) / syminfo.mintick, minStopDistance)
if longTrigger and not currentTrade.isActive
currentTrade.intendedEntry := close
currentTrade.spreadAtClick := simulatedSpread / syminfo.mintick
currentTrade.stopLoss := close - (slDistanceTicks * syminfo.mintick)
currentTrade.entryBarIndex := bar_index
currentTrade.isActive := true
qty = calculatePositionSize(riskPerTradePct, slDistanceTicks)
strategy.entry("Desk_Long", strategy.long, qty=qty)
if shortTrigger and not currentTrade.isActive
currentTrade.intendedEntry := close
currentTrade.spreadAtClick := simulatedSpread / syminfo.mintick
currentTrade.stopLoss := close + (slDistanceTicks * syminfo.mintick)
currentTrade.entryBarIndex := bar_index
currentTrade.isActive := true
qty = calculatePositionSize(riskPerTradePct, slDistanceTicks)
strategy.entry("Desk_Short", strategy.short, qty=qty)
// Update actual fill price on the next tick/bar
if currentTrade.isActive and currentTrade.entryBarIndex == bar_index[1]
currentTrade.actualFill := strategy.opentrades.entry_price(strategy.opentrades - 1)
// =========================================================================
// 6. HARD EXITS & TIME STOPS
// =========================================================================
barsHeld = currentTrade.isActive ? (bar_index - currentTrade.entryBarIndex) : 0
if currentTrade.isActive
// Time Stop
if barsHeld >= hardTimeStop
strategy.close_all(comment="Time Stop")
currentTrade.isActive := false
// Hard Stop Loss
else
strategy.exit("Risk_Out", from_entry="Desk_Long", stop=currentTrade.stopLoss)
strategy.exit("Risk_Out", from_entry="Desk_Short", stop=currentTrade.stopLoss)
if strategy.position_size == 0
currentTrade.isActive := false
// =========================================================================
// 7. DASHBOARD: THE FILL LOG
// =========================================================================
var table auditTable = table.new(position.bottom_right, 2, 5, bgcolor=color.new(color.black, 20), border_width=1, border_color=color.gray)
if barstate.islast
table.cell(auditTable, 0, 0, "LAST AUDIT", text_color=color.white, text_halign=text.align_left)
table.cell(auditTable, 1, 0, "VALUE", text_color=color.white, text_halign=text.align_right)
table.cell(auditTable, 0, 1, "Intended Entry", text_color=color.gray, text_halign=text.align_left)
table.cell(auditTable, 1, 1, str.tostring(currentTrade.intendedEntry, format.mintick), text_color=color.white, text_halign=text.align_right)
table.cell(auditTable, 0, 2, "Actual Fill (w/ Slip)", text_color=color.gray, text_halign=text.align_left)
table.cell(auditTable, 1, 2, str.tostring(currentTrade.actualFill, format.mintick), text_color=color.white, text_halign=text.align_right)
slipTicks = math.abs(currentTrade.intendedEntry - currentTrade.actualFill) / syminfo.mintick
slipColor = slipTicks > 10 ? color.red : color.green
table.cell(auditTable, 0, 3, "Slippage INCURRED", text_color=color.gray, text_halign=text.align_left)
table.cell(auditTable, 1, 3, str.tostring(slipTicks) + " Ticks", text_color=slipColor, text_halign=text.align_right)
table.cell(auditTable, 0, 4, "Spread at Click", text_color=color.gray, text_halign=text.align_left)
table.cell(auditTable, 1, 4, str.tostring(currentTrade.spreadAtClick, "#.#") + " Ticks", text_color=color.white, text_halign=text.align_right)