Leverage Strategy Shell [by Oberlunar] by oberlunar_tr
By oberlunar_tr
Performance Metrics
- Author: oberlunar_tr
- Symbol: CAPITALCOM:GOLD
- Timeframe: 15 minutes
- Net P&L: +5,369.56 USD (+53.70%)
- Win Rate: 30.0%
- Profit Factor: 1.537
- Max Drawdown: 1,398.87 USD (10.47%)
- Total Trades: 190
Description
Leverage Strategy Shell ★ by OberlunarA quantitative strategy shell that executes no logic of its own. It receives entry and exit signals from any external indicator through a standardised protocol, then sizes each position through a full risk engine, executes against TradingView's broker emulator, and produces a post-trade analytics suite that no native strategy tester can match. Think of it as a risk manager that sits between your signal generator and the market.The core idea is the separation of concerns. Your indicator decides when to trade. This shell decides how much to trade, enforces margin constraints, tracks every position through intrabar LTF data, and decomposes your equity curve into R-multiple and time distributions so you can see exactly where your edge lives — and where it doesn't.Signal ProtocolThe shell reads three values from your indicator via Pine Script®input.source(). Your indicator must plot them with Pine Script®display=display.none so they don't clutter the chart.Pine Script®// In your indicator:signal = longCondition ? 1 : shortCondition ? -1 : exitLong ? 2 : exitShort ? -2 : 0stopPx = longCondition ? yourStopPrice : shortCondition ? yourStopPrice : 0tpPx = longCondition ? yourTPPrice : shortCondition ? yourTPPrice : 0plot(signal, "Signal", display=display.none)plot(stopPx, "Stop", display=display.none)plot(tpPx, "TP", display=display.none)Expand 3 linesSignal is the only mandatory source. It accepts five discrete values: +1 opens a long, -1 opens a short, +2 closes an open long, -2 closes an open short, and 0 means no action. The shell is edge-triggered — it fires only on the transition from one value to another, so holding +1 for multiple bars will not produce multiple entries.Stop is optional. When your indicator provides a stop price, the shell uses it for the exit order and for computing the risk distance that drives position sizing. When the source reads zero or equals the bar's close, the shell falls back to its internal stop model (ATR × multiplier or percentage). This means any indicator, from a simple moving-average crossover to a complex volatility regime detector, can plug in and immediately inherit disciplined risk management.TP is also optional. If provided, the shell places a limit exit at that price alongside the stop. If not, the position lives until a +2 or -2 signal closes it.To connect: apply your signal indicator to the chart first, then apply the shell to the same chart. In the shell's settings, map each Pine Script®input.source() to the corresponding plot from your indicator. That is the entire integration. No code changes to your indicator beyond adding the three plot lines above.Risk EnginePosition sizing is not a percentage of equity and not a fixed lot. It is derived from the actual risk per trade, defined as the monetary distance from entry to stop expressed in account currency.The engine computes tick value as Pine Script®tickSize × pointValue, converts to account currency through Pine Script®request.currency_rate(), then calculates the cost of the stop being hit: Pine Script®stopTicks × tickValue × ccyRate. To this it adds a friction estimate (spread + entry slippage + exit slippage, all in ticks) and the round-trip commission cost. The budget available for the trade is Pine Script®capital × riskPercent / 100. Raw quantity is Pine Script®budget / totalCostPerUnit, floored to the instrument's minimum contract step and capped by the margin constraint Pine Script®capital / (notionalPerUnit × marginPercent). The final quantity is the smaller of the risk-sized and margin-capped values.The commission model supports three modes. Per Order subtracts a fixed dollar amount per order from the risk budget before dividing by the per-unit cost — this is the simplest to configure and is the default. Per Contract scales linearly with quantity. Percent scales with notional value. In all three cases, the engine accounts for the full round-trip cost before committing capital.The strategy declaration at the top of the script uses compile-time constants for Pine Script®initial_capital, Pine Script®commission_type, Pine Script®commission_value, Pine Script®slippage, and Pine Script®margin_long/short. These are the values TradingView's broker emulator actually uses during the backtest. The input panel exposes the same parameters for the dashboard and sizing engine. If the two diverge, a red DECL MISMATCH warning appears at the bottom of the table — edit the constants in the source (marked with ✏️) to match your inputs.Trade TrackingOnce a position is filled, the shell begins monitoring it through lower-timeframe intrabar data via Pine Script®request.security_lower_tf(). For each open trade it checks, bar by bar, whether the price path has reached -1R, +1R, +2R, +3R, or +5R from the entry, using the stop distance frozen at signal time as the unit of R. It records whether each level was touched and how many minutes it took to get there.Trade close detection uses Pine Script®strategy.closedtrades as an event counter, not Pine Script®strategy.position_size == 0. This is critical for robustness: when the strategy reverses from long to short in a single bar, Pine Script®position_size never passes through zero, but Pine Script®closedtrades still increments. The entry price for R-multiple calculation comes from Pine Script®strategy.position_avg_price — the real fill, not the close of the signal bar.The stop distance follows a three-stage chain. At signal time, stop and distance are frozen into pending variables. At fill time, when the position actually exists, they transfer into active tracking variables. At trade close, when Pine Script®closedtrades increments, the active stop distance is pushed into a closed-trade array that stays perfectly aligned with TradingView's internal trade index. This eliminates the fragile parallel-array problem that plagues most custom trade trackers.Analytics DashboardThe dashboard is a single unified table rendered on the last bar. It has three main blocks.Left panel shows live strategy metrics computed from all closed trades: Profit Factor, Win Rate, Expectancy in account currency, average R-multiple at exit, average Win R and Loss R with trade counts, maximum drawdown, annualised Sortino ratio and Calmar ratio, and average trade duration. These are not approximations — they iterate over every Pine Script®strategy.closedtrades.profit(i), Pine Script®entry_price(i), Pine Script®exit_price(i), and Pine Script®entry_time(i)/exit_time(i) on the final bar.Right panel, upper block — R Distribution. Every closed trade is binned by its R-multiple at exit into eight buckets: below -1R, -1R to -0.5R, -0.5R to zero, zero to +0.5R, +0.5R to +1R, +1R to +2R, +2R to +3R, and above +3R. Each row shows the count of trades in that bin, the average P&L, the average duration, and the win percentage. The background colour shifts from red to green with the bin's average R. This is the distribution that tells you whether your strategy is a small-loss / big-win machine or something else entirely.Right panel, lower block — Time Distribution. The same closed trades are binned by duration into six buckets: under 6 hours, 6–24 hours, 1–3 days, 3–7 days, 1–4 weeks, and over 4 weeks. Each row shows count, average R, average P&L, and win rate. This reveals the time structure of your edge. A well-functioning trend-following signal will show negative average R in the short buckets (quick stops) and strongly positive R in the long buckets (runners). If the pattern is flat or inverted, the signal has no time edge.Below the distributions, a Trade R-Level HIT% row shows what fraction of completed trades touched each R-level during their lifetime, with the average time to reach it. This is a different question from exit R: a trade can touch +3R intrabar and then close at +1.5R. The HIT% row captures the maximum favourable excursion in R-space, which is useful for evaluating whether wider take-profit targets are viable.A Multi-Risk Sizing table at the bottom shows what the position size, notional, margin, and risk dollar would be at seven different risk percentages from 0.25% to 10%, so you can instantly see the scaling profile of your current instrument without changing any input.Setup checklistAdd three Pine Script®plot() lines to your indicator (Signal, Stop (optional), TP(optional)) following the protocol above.Apply your indicator to the chart.Apply this strategy shell to the same chart.In the shell's settings, map the three Pine Script®input.source() fields to the corresponding plots from your indicator.Set Account Capital, Leverage, Commission Type and Value to match your broker. Then edit the ✏️ constants at the top of the script to the same values — these are compile-time and cannot follow input changes.Verify no DECL MISMATCH warning appears in the table.Run the backtest. Read the R and Time distributions. Decide whether your signal has edge.The strategy declaration uses Pine Script®process_orders_on_close=false and Pine Script®use_bar_magnifier=true by default. Orders fill on the next bar's open, which is realistic for market orders. Bar magnifier uses lower-timeframe data to improve fill simulation on higher timeframes. Both settings can be changed in the source if your workflow requires it.The very simple tested strategy is the following:Pine Script®// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/// © Oberlunar ★//@version=6indicator("EMA Trend Signal ★ Oberlunar", overlay=true)// ═══════════════════════════════════════════════════════════════════════════════// INPUTS// ═══════════════════════════════════════════════════════════════════════════════i_emaFast = input.int(19, "EMA Fast", minval=2, group="EMA")i_emaMid = input.int(49, "EMA Mid", minval=2, group="EMA")i_emaSlow = input.int(154, "EMA Slow", minval=2, group="EMA")i_slopeBars = input.int(5, "Slope Lookback", minval=1, maxval=50, group="EMA", tooltip="Bars to measure EMA slope.\nPositive = ema now > ema N bars ago.")i_atrMult = input.float(1.5,"Stop ATR Mult", minval=0.1, step=0.1, group="Stop & TP")i_rrRatio = input.float(2.0,"R:R Ratio for TP", minval=0.5, step=0.5, group="Stop & TP", tooltip="0 = no TP, exit by signal only.")i_showEMA = input.bool(true,"Show EMAs", group="Visual")// ═══════════════════════════════════════════════════════════════════════════════// EMAs + SLOPE// ═══════════════════════════════════════════════════════════════════════════════float ema21 = ta.ema(close, i_emaFast)float ema50 = ta.ema(close, i_emaMid)float ema200 = ta.ema(close, i_emaSlow)float atr = ta.atr(14)// Slope: positive if EMA21 is rising over N barsfloat slope21 = ema21 - ema21[i_slopeBars]bool slopeUp = slope21 > 0bool slopeDn = slope21 50 > 200 under price)// Slope is positivebool bullishAlignment = ema50 0 ? close + longRisk * i_rrRatio : 0.0float shortTP = shortEntry and i_rrRatio > 0 ? close - shortRisk * i_rrRatio : 0.0// ═══════════════════════════════════════════════════════════════════════════════// SIGNAL PROTOCOL — Output for Leverage Strategy Shell// ═══════════════════════════════════════════════════════════════════════════════// +1 = Long entry -1 = Short entry// +2 = Close long -2 = Close short// 0 = No signalfloat signal = longEntry ? 1 : shortEntry ? -1 : exitLong ? 2 : exitShort ? -2 : 0float stopPx = longEntry ? longStop : shortEntry ? shortStop : 0float tpPx = longEntry ? longTP : shortEntry ? shortTP : 0// Plots: connect these to Strategy Shell's input.source()plot(signal, "Signal", color=color.new(color.yellow, 100), display=display.none)plot(stopPx, "Stop", color=color.new(color.red, 100), display=display.none)plot(tpPx, "TP", color=color.new(color.green, 100), display=display.none)// ═══════════════════════════════════════════════════════════════════════════════// VISUAL — EMAs + Entry markers + Stop/TP levels// ═══════════════════════════════════════════════════════════════════════════════plot(i_showEMA ? ema21 : na, "EMA 21", color=color.new(#FFD700, 0), linewidth=2)plot(i_showEMA ? ema50 : na, "EMA 50", color=color.new(#5b7dff, 0), linewidth=1)plot(i_showEMA ? ema200 : na, "EMA 200", color=color.new(#c792ea, 0), linewidth=1)// Entry arrowsplotshape(longEntry, "Long", shape.triangleup, location.belowbar, color.new(#22d47a, 0), size=size.small)plotshape(shortEntry, "Short", shape.triangledown, location.abovebar, color.new(#f05050, 0), size=size.small)// Exit crossesplotshape(exitLong, "Exit L", shape.xcross, location.abovebar, color.new(#22d47a, 60), size=size.tiny)plotshape(exitShort, "Exit S", shape.xcross, location.belowbar, color.new(#f05050, 60), size=size.tiny)// Stop & TP lines on signal barsplot(longEntry ? longStop : na, "L Stop", color=color.new(#f05050, 0), style=plot.style_circles, linewidth=3)plot(longEntry ? longTP : na, "L TP", color=color.new(#22d47a, 0), style=plot.style_circles, linewidth=3)plot(shortEntry ? shortStop : na, "S Stop", color=color.new(#f05050, 0), style=plot.style_circles, linewidth=3)plot(shortEntry ? shortTP : na, "S TP", color=color.new(#22d47a, 0), style=plot.style_circles, linewidth=3)// ═══════════════════════════════════════════════════════════════════════════════// INFO LABEL — slope + alignment status// ═══════════════════════════════════════════════════════════════════════════════var label infoLbl = naif barstate.islast string status = bullishAlignment ? "BULL ▲" : bearishAlignment ? "BEAR ▼" : "NEUTRAL —" string slopeTxt = "Slope 21: " + (slopeUp ? "+" : "") + str.tostring(slope21, "#.##") string txt = "EMA Signal ★\n" + status + "\n" + slopeTxt color bg = bullishAlignment ? color.new(#22d47a, 80) : bearishAlignment ? color.new(#f05050, 80) : color.new(#888888, 80) if not na(infoLbl) label.delete(infoLbl) infoLbl := label.new(bar_index, high, txt, style=label.style_label_down, color=bg, textcolor=color.white, size=size.small)Expand 111 linesCreditsConcept, architecture, and code: Oberlunar.Built in Pine Script™ v6.Release NotesSmall color fix.Release NotesSmall color fix.