PINE LIBRARY
PessimisticSim

Library "PessimisticSim"
Broker-agnostic pessimistic fill simulator. Runs a shadow account
alongside any indicator or strategy, filling every signal at the
worst plausible price (half-spread + slippage + a fraction of the
adverse bar excursion) so you can see whether an edge survives
real-world friction.
Instrument-agnostic: set pointValue for futures, pick a commission
model, pick a sizing model, pick a fill model. Defaults reproduce
spot crypto / stock behaviour (multiplier 1, percent commission,
risk-based sizing, next-bar-open fills).
newState(cfg)
Creates a fresh shadow account seeded from `cfg`.
Parameters:
cfg (SimConfig): Configuration object.
Returns: A SimState ready to pass to step().
commissionFor(cfg, price, qty)
Commission for one fill under the configured model.
Parameters:
cfg (SimConfig): Configuration object.
price (float): Fill price.
qty (float): Units filled.
Returns: Commission in account currency. Zero when qty <= 0.
qtyFor(cfg, equity, stopDist, price, openRisk)
Position size under the configured sizing model, with an optional
leverage cap and quantity-step rounding. Host scripts should call
this for their live orders too, so both engines size identically.
Parameters:
cfg (SimConfig): Configuration object.
equity (float): Account equity to size against.
stopDist (float): Distance from entry to stop, in PRICE units. Only used by
SizeMode.riskStop; pass 0 in the other modes.
price (float): Fill price, used by the leverage cap and equityPct sizing.
openRisk (float): Risk already committed by open positions, in CURRENCY
(i.e. qty * stopDist * pointValue). riskStop only.
Returns: Units to trade, rounded down to qtyStep. Zero when unsizable.
buyFillPrice(cfg, refPrice, advHigh)
Worst-case buy fill: reference price + half-spread + slippage +
a slice of the adverse upward excursion.
Parameters:
cfg (SimConfig): Configuration object.
refPrice (float): Reference price (bar open, or close under signalClose).
advHigh (float): Adverse extreme to price against. Pass the bar high under
nextOpen; pass refPrice under signalClose to disable it.
Returns: The pessimistic buy price.
sellFillPrice(cfg, refPrice, advLow)
Worst-case sell fill: reference price - half-spread - slippage -
a slice of the adverse downward excursion.
Parameters:
cfg (SimConfig): Configuration object.
refPrice (float): Reference price (bar open, or close under signalClose).
advLow (float): Adverse extreme to price against. Pass the bar low under
nextOpen; pass refPrice under signalClose to disable it.
Returns: The pessimistic sell price.
method closeAt(s, cfg, exitPrice)
Flattens the position at `exitPrice`, books PnL and updates stats.
No-op when flat.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
exitPrice (float): Fill price for the exit.
Returns: Void.
method openAt(s, cfg, dir, fillPrice, stopDist)
Opens a position, or adds a unit while `units < cfg.maxUnits`.
Add-ons blend into a volume-weighted average entry and are sized
against the risk already committed.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
dir (int): 1 to go long, -1 to go short.
fillPrice (float): Pessimistic fill price.
stopDist (float): Distance from entry to stop, in price units. Pass 0 under
fixedUnits / equityPct sizing.
Returns: Void.
method mark(s, cfg, price)
Marks the account to market and updates equity peak and drawdown.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
price (float): Current mark price, normally close.
Returns: Void.
method stepAt(s, cfg, longIn, longOut, shortIn, shortOut, buyPrice, sellPrice, markPrice, stopDistLong, stopDistShort)
General escape hatch: processes one bar against explicit fill prices.
Use when your execution model is neither FillMode case — limit fills,
stop fills, VWAP, session opens, anything.
Order is exits, then reversals, then entries, then mark-to-market.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
longIn (bool): Long entry signal.
longOut (bool): Long exit signal.
shortIn (bool): Short entry signal.
shortOut (bool): Short exit signal.
buyPrice (float): Price paid when buying.
sellPrice (float): Price received when selling.
markPrice (float): Price for the mark-to-market update.
stopDistLong (float): Stop distance for longs, price units.
stopDistShort (float): Stop distance for shorts, price units.
Returns: Void.
method step(s, cfg, longIn, longOut, shortIn, shortOut, o, h, l, c, stopDistLong, stopDistShort)
Processes one bar using the configured FillMode. Call once per
confirmed bar.
FillMode.nextOpen — pass PRIOR-bar signals (sig[1]); this bar's
open is the fill, its high/low the excursion.
FillMode.signalClose — pass CURRENT-bar signals; the close is the
fill and advFrac is inert.
Getting this pairing wrong produces plausible-but-wrong results, so
check it first when a comparison looks strange.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
longIn (bool): Long entry signal, shifted per FillMode.
longOut (bool): Long exit signal, shifted per FillMode.
shortIn (bool): Short entry signal, shifted per FillMode.
shortOut (bool): Short exit signal, shifted per FillMode.
o (float): Bar open.
h (float): Bar high.
l (float): Bar low.
c (float): Bar close.
stopDistLong (float): Stop distance for longs, price units.
stopDistShort (float): Stop distance for shorts, price units.
Returns: Void.
pf(gp, gl)
Profit factor with safe handling of an empty loss column.
Parameters:
gp (float): Gross profit.
gl (float): Gross loss, as a positive number.
Returns: gp/gl, 999 when there are no losses, 0 when there is nothing.
method netProfit(s, cfg)
Net profit of the shadow account, in currency.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
Returns: equity - initCap.
method profitFactor(s)
Profit factor of the shadow account.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
Returns: Profit factor.
method winRatePct(s)
Win rate of the shadow account, percent.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
Returns: Percentage of closed trades that were profitable.
method avgTradePct(s)
Average per-trade return, percent of equity-before-trade.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
Returns: Mean trade return, percent.
method rtCostPct(cfg)
Round-trip friction as a percent of notional. Only meaningful under
CommMode.pct — flat commissions do not scale with notional, so this
returns na under the other models. Compare against avgTradePct():
if the average trade does not clear this, the edge is smaller than
the cost of trading it.
Namespace types: SimConfig
Parameters:
cfg (SimConfig): Configuration object.
Returns: Round-trip cost, percent, or na under flat commission models.
rowLabels()
Row labels matching the order of rowValues(). Lay out an audit table
in the host script from these, so strategy.* calls stay in the host.
Returns: Array of ten label strings.
method rowValues(s, cfg)
Preformatted metric strings for the shadow account, aligned to rowLabels().
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
Returns: Array of ten value strings.
method verdict(s, cfg, strategyPF, strategyNet)
Compares a host strategy's headline numbers against the shadow
account and returns a verdict string.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
strategyPF (float): Host strategy profit factor.
strategyNet (float): Host strategy net profit.
Returns: "DIVERGED — investigate", "SURVIVES", or "NO EDGE".
SimConfig
Instrument, cost, sizing and execution assumptions for the shadow account.
Fields:
spreadPct (series float): Assumed FULL spread, percent. Half is charged per side.
slipPct (series float): Extra slippage percent per side.
advFrac (series float): Fraction of the fill bar's adverse excursion added to the fill.
commMode (series CommMode): Commission model.
commPct (series float): Commission percent of notional, per side.
commPerUnit (series float): Flat commission per contract or share, per side.
commMin (series float): Minimum commission per fill. Applied only when qty > 0.
pointValue (series float): Currency value of one full point of price movement, per unit.
qtyStep (series float): Rounds size DOWN to this increment. 0 = no rounding.
initCap (series float): Starting equity of the shadow account.
sizeMode (series SizeMode): Position sizing model.
riskPct (series float): Risk per trade, percent of equity. SizeMode.riskStop only.
maxTotalPct (series float): Ceiling on total open risk, percent of equity. riskStop only.
fixedUnits (series float): Units per entry. SizeMode.fixedUnits only.
equityPct (series float): Notional as percent of equity. SizeMode.equityPct only.
useLevCap (series bool): Apply the leverage cap on top of the chosen sizing model.
maxLeverage (series float): Max notional / equity.
maxUnits (series int): Max entries per position. MUST equal the host strategy's
fillMode (series FillMode): Execution assumption. Determines which signals step() wants.
SimState
Mutable state of the shadow account. Create with newState().
Fields:
equity (series float): Realised equity, commissions already deducted.
dir (series int): 1 long, -1 short, 0 flat.
entry (series float): Volume-weighted average entry price.
qty (series float): Total units held.
units (series int): Number of fills making up the current position.
trades (series int): Closed trades.
wins (series int): Closed trades with net > 0.
grossP (series float): Sum of winning net PnL.
grossL (series float): Sum of absolute losing net PnL.
commPaid (series float): Total commission paid, both sides.
peak (series float): Mark-to-market equity high water mark.
maxDD (series float): Worst mark-to-market drawdown, as a negative fraction.
consecL (series int): Current consecutive-loss run.
maxConsL (series int): Longest consecutive-loss run.
sumTrPct (series float): Sum of per-trade returns, percent of equity-before-trade.
skipped (series int): Entry signals dropped because sizing returned zero units.
Broker-agnostic pessimistic fill simulator. Runs a shadow account
alongside any indicator or strategy, filling every signal at the
worst plausible price (half-spread + slippage + a fraction of the
adverse bar excursion) so you can see whether an edge survives
real-world friction.
Instrument-agnostic: set pointValue for futures, pick a commission
model, pick a sizing model, pick a fill model. Defaults reproduce
spot crypto / stock behaviour (multiplier 1, percent commission,
risk-based sizing, next-bar-open fills).
newState(cfg)
Creates a fresh shadow account seeded from `cfg`.
Parameters:
cfg (SimConfig): Configuration object.
Returns: A SimState ready to pass to step().
commissionFor(cfg, price, qty)
Commission for one fill under the configured model.
Parameters:
cfg (SimConfig): Configuration object.
price (float): Fill price.
qty (float): Units filled.
Returns: Commission in account currency. Zero when qty <= 0.
qtyFor(cfg, equity, stopDist, price, openRisk)
Position size under the configured sizing model, with an optional
leverage cap and quantity-step rounding. Host scripts should call
this for their live orders too, so both engines size identically.
Parameters:
cfg (SimConfig): Configuration object.
equity (float): Account equity to size against.
stopDist (float): Distance from entry to stop, in PRICE units. Only used by
SizeMode.riskStop; pass 0 in the other modes.
price (float): Fill price, used by the leverage cap and equityPct sizing.
openRisk (float): Risk already committed by open positions, in CURRENCY
(i.e. qty * stopDist * pointValue). riskStop only.
Returns: Units to trade, rounded down to qtyStep. Zero when unsizable.
buyFillPrice(cfg, refPrice, advHigh)
Worst-case buy fill: reference price + half-spread + slippage +
a slice of the adverse upward excursion.
Parameters:
cfg (SimConfig): Configuration object.
refPrice (float): Reference price (bar open, or close under signalClose).
advHigh (float): Adverse extreme to price against. Pass the bar high under
nextOpen; pass refPrice under signalClose to disable it.
Returns: The pessimistic buy price.
sellFillPrice(cfg, refPrice, advLow)
Worst-case sell fill: reference price - half-spread - slippage -
a slice of the adverse downward excursion.
Parameters:
cfg (SimConfig): Configuration object.
refPrice (float): Reference price (bar open, or close under signalClose).
advLow (float): Adverse extreme to price against. Pass the bar low under
nextOpen; pass refPrice under signalClose to disable it.
Returns: The pessimistic sell price.
method closeAt(s, cfg, exitPrice)
Flattens the position at `exitPrice`, books PnL and updates stats.
No-op when flat.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
exitPrice (float): Fill price for the exit.
Returns: Void.
method openAt(s, cfg, dir, fillPrice, stopDist)
Opens a position, or adds a unit while `units < cfg.maxUnits`.
Add-ons blend into a volume-weighted average entry and are sized
against the risk already committed.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
dir (int): 1 to go long, -1 to go short.
fillPrice (float): Pessimistic fill price.
stopDist (float): Distance from entry to stop, in price units. Pass 0 under
fixedUnits / equityPct sizing.
Returns: Void.
method mark(s, cfg, price)
Marks the account to market and updates equity peak and drawdown.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
price (float): Current mark price, normally close.
Returns: Void.
method stepAt(s, cfg, longIn, longOut, shortIn, shortOut, buyPrice, sellPrice, markPrice, stopDistLong, stopDistShort)
General escape hatch: processes one bar against explicit fill prices.
Use when your execution model is neither FillMode case — limit fills,
stop fills, VWAP, session opens, anything.
Order is exits, then reversals, then entries, then mark-to-market.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
longIn (bool): Long entry signal.
longOut (bool): Long exit signal.
shortIn (bool): Short entry signal.
shortOut (bool): Short exit signal.
buyPrice (float): Price paid when buying.
sellPrice (float): Price received when selling.
markPrice (float): Price for the mark-to-market update.
stopDistLong (float): Stop distance for longs, price units.
stopDistShort (float): Stop distance for shorts, price units.
Returns: Void.
method step(s, cfg, longIn, longOut, shortIn, shortOut, o, h, l, c, stopDistLong, stopDistShort)
Processes one bar using the configured FillMode. Call once per
confirmed bar.
FillMode.nextOpen — pass PRIOR-bar signals (sig[1]); this bar's
open is the fill, its high/low the excursion.
FillMode.signalClose — pass CURRENT-bar signals; the close is the
fill and advFrac is inert.
Getting this pairing wrong produces plausible-but-wrong results, so
check it first when a comparison looks strange.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
longIn (bool): Long entry signal, shifted per FillMode.
longOut (bool): Long exit signal, shifted per FillMode.
shortIn (bool): Short entry signal, shifted per FillMode.
shortOut (bool): Short exit signal, shifted per FillMode.
o (float): Bar open.
h (float): Bar high.
l (float): Bar low.
c (float): Bar close.
stopDistLong (float): Stop distance for longs, price units.
stopDistShort (float): Stop distance for shorts, price units.
Returns: Void.
pf(gp, gl)
Profit factor with safe handling of an empty loss column.
Parameters:
gp (float): Gross profit.
gl (float): Gross loss, as a positive number.
Returns: gp/gl, 999 when there are no losses, 0 when there is nothing.
method netProfit(s, cfg)
Net profit of the shadow account, in currency.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
Returns: equity - initCap.
method profitFactor(s)
Profit factor of the shadow account.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
Returns: Profit factor.
method winRatePct(s)
Win rate of the shadow account, percent.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
Returns: Percentage of closed trades that were profitable.
method avgTradePct(s)
Average per-trade return, percent of equity-before-trade.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
Returns: Mean trade return, percent.
method rtCostPct(cfg)
Round-trip friction as a percent of notional. Only meaningful under
CommMode.pct — flat commissions do not scale with notional, so this
returns na under the other models. Compare against avgTradePct():
if the average trade does not clear this, the edge is smaller than
the cost of trading it.
Namespace types: SimConfig
Parameters:
cfg (SimConfig): Configuration object.
Returns: Round-trip cost, percent, or na under flat commission models.
rowLabels()
Row labels matching the order of rowValues(). Lay out an audit table
in the host script from these, so strategy.* calls stay in the host.
Returns: Array of ten label strings.
method rowValues(s, cfg)
Preformatted metric strings for the shadow account, aligned to rowLabels().
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
Returns: Array of ten value strings.
method verdict(s, cfg, strategyPF, strategyNet)
Compares a host strategy's headline numbers against the shadow
account and returns a verdict string.
Namespace types: SimState
Parameters:
s (SimState): Shadow account state.
cfg (SimConfig): Configuration object.
strategyPF (float): Host strategy profit factor.
strategyNet (float): Host strategy net profit.
Returns: "DIVERGED — investigate", "SURVIVES", or "NO EDGE".
SimConfig
Instrument, cost, sizing and execution assumptions for the shadow account.
Fields:
spreadPct (series float): Assumed FULL spread, percent. Half is charged per side.
slipPct (series float): Extra slippage percent per side.
advFrac (series float): Fraction of the fill bar's adverse excursion added to the fill.
commMode (series CommMode): Commission model.
commPct (series float): Commission percent of notional, per side.
commPerUnit (series float): Flat commission per contract or share, per side.
commMin (series float): Minimum commission per fill. Applied only when qty > 0.
pointValue (series float): Currency value of one full point of price movement, per unit.
qtyStep (series float): Rounds size DOWN to this increment. 0 = no rounding.
initCap (series float): Starting equity of the shadow account.
sizeMode (series SizeMode): Position sizing model.
riskPct (series float): Risk per trade, percent of equity. SizeMode.riskStop only.
maxTotalPct (series float): Ceiling on total open risk, percent of equity. riskStop only.
fixedUnits (series float): Units per entry. SizeMode.fixedUnits only.
equityPct (series float): Notional as percent of equity. SizeMode.equityPct only.
useLevCap (series bool): Apply the leverage cap on top of the chosen sizing model.
maxLeverage (series float): Max notional / equity.
maxUnits (series int): Max entries per position. MUST equal the host strategy's
fillMode (series FillMode): Execution assumption. Determines which signals step() wants.
SimState
Mutable state of the shadow account. Create with newState().
Fields:
equity (series float): Realised equity, commissions already deducted.
dir (series int): 1 long, -1 short, 0 flat.
entry (series float): Volume-weighted average entry price.
qty (series float): Total units held.
units (series int): Number of fills making up the current position.
trades (series int): Closed trades.
wins (series int): Closed trades with net > 0.
grossP (series float): Sum of winning net PnL.
grossL (series float): Sum of absolute losing net PnL.
commPaid (series float): Total commission paid, both sides.
peak (series float): Mark-to-market equity high water mark.
maxDD (series float): Worst mark-to-market drawdown, as a negative fraction.
consecL (series int): Current consecutive-loss run.
maxConsL (series int): Longest consecutive-loss run.
sumTrPct (series float): Sum of per-trade returns, percent of equity-before-trade.
skipped (series int): Entry signals dropped because sizing returned zero units.
Pine library
In true PulseWire spirit, the author has published this Pine code as an open-source library so that other Pine programmers from our community can reuse it. Cheers to the author! You may use this library privately or in other open-source publications, but reuse of this code in publications is governed by House Rules.
Disclaimer
The information and publications are not meant to be, and do not constitute, financial, investment, trading, or other types of advice or recommendations supplied or endorsed by PulseWire. Read more in the Terms of Use.
Pine library
In true PulseWire spirit, the author has published this Pine code as an open-source library so that other Pine programmers from our community can reuse it. Cheers to the author! You may use this library privately or in other open-source publications, but reuse of this code in publications is governed by House Rules.
Disclaimer
The information and publications are not meant to be, and do not constitute, financial, investment, trading, or other types of advice or recommendations supplied or endorsed by PulseWire. Read more in the Terms of Use.
