//@version=6 strategy("Hercules GUS-V5 ยท Risk Backtest", shorttitle="GUS-V5 Strategy", overlay=true, pyramiding=0, initial_capital=10000, currency=currency.USD, default_qty_type=strategy.fixed, default_qty_value=1, commission_type=strategy.commission.percent, commission_value=0.08, slippage=2, calc_on_every_tick=false, calc_on_order_fills=false, process_orders_on_close=false, fill_orders_on_standard_ohlc=true, margin_long=100, margin_short=100) // This strategy is deliberately conservative: signals are evaluated on confirmed // standard candles and orders are filled by TradingView's broker emulator on the // next available tick. Commission is charged on entry and exit; slippage is in ticks. groupSignal = "Signal" wtChannel = input.int(9, "WaveTrend channel", minval=2, group=groupSignal) wtAverage = input.int(12, "WaveTrend average", minval=2, group=groupSignal) wtSmooth = input.int(3, "WaveTrend signal", minval=1, group=groupSignal) wtOversold = input.float(-53.0, "WaveTrend oversold", group=groupSignal) wtOverbought = input.float(53.0, "WaveTrend overbought", group=groupSignal) useTrendFilter = input.bool(true, "Require EMA + Supertrend alignment", group=groupSignal) groupRisk = "Risk model" riskPercent = input.float(1.0, "Equity risk per trade (%)", minval=0.1, maxval=5.0, step=0.1, group=groupRisk) atrLength = input.int(14, "ATR length", minval=2, group=groupRisk) stopAtr = input.float(1.5, "Stop distance (ATR)", minval=0.1, step=0.1, group=groupRisk) targetR = input.float(2.0, "Target (R)", minval=0.5, step=0.1, group=groupRisk) feeForSizingPercent = input.float(0.08, "Fee per side used in sizing (%)", minval=0.0, step=0.01, group=groupRisk, tooltip="Keep this aligned with Strategy Properties > Commission.") quantityStep = input.float(0.001, "Quantity step approximation", minval=0.000001, step=0.001, group=groupRisk) maximumExposurePercent = input.float(100.0, "Maximum notional / equity (%)", minval=1.0, maxval=1000.0, step=5.0, group=groupRisk) exitOnOpposite = input.bool(true, "Exit on confirmed opposite signal", group=groupRisk) standardTicker = ticker.standard(syminfo.tickerid) [rawOpen, rawHigh, rawLow, rawClose, rawVolume] = request.security(standardTicker, timeframe.period, [open, high, low, close, volume], gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_off) f_waveTrend(float source, int channelLength, int averageLength, int signalLength) => float esa = ta.ema(source, channelLength) float deviation = ta.ema(math.abs(source - esa), channelLength) float ci = deviation == 0.0 ? 0.0 : (source - esa) / (0.015 * deviation) float wave = ta.ema(ci, averageLength) float signal = ta.sma(wave, signalLength) [wave, signal] [wt1, wt2] = f_waveTrend((rawHigh + rawLow + rawClose) / 3.0, wtChannel, wtAverage, wtSmooth) ema20 = ta.ema(rawClose, 20) ema50 = ta.ema(rawClose, 50) [supertrend, supertrendDirection] = request.security(standardTicker, timeframe.period, ta.supertrend(3.0, 10), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_off) rawTrueRange = math.max(rawHigh - rawLow, math.max(math.abs(rawHigh - rawClose[1]), math.abs(rawLow - rawClose[1]))) atr = ta.rma(rawTrueRange, atrLength) trendBull = ema20 > ema50 and rawClose > ema20 and supertrendDirection < 0 trendBear = ema20 < ema50 and rawClose < ema20 and supertrendDirection > 0 longSignal = barstate.isconfirmed and ta.crossover(wt1, wt2) and wt2 <= wtOversold and (not useTrendFilter or trendBull) shortSignal = barstate.isconfirmed and ta.crossunder(wt1, wt2) and wt2 >= wtOverbought and (not useTrendFilter or trendBear) f_roundDown(float value, float step) => step <= 0.0 ? value : math.floor(value / step) * step feeRate = feeForSizingPercent / 100.0 riskBudget = strategy.equity * riskPercent / 100.0 stopDistance = atr * stopAtr riskPerUnit = stopDistance * syminfo.pointvalue + rawClose * syminfo.pointvalue * feeRate * 2.0 riskQuantity = riskPerUnit > 0.0 ? riskBudget / riskPerUnit : 0.0 exposureQuantity = rawClose * syminfo.pointvalue > 0.0 ? strategy.equity * maximumExposurePercent / 100.0 / (rawClose * syminfo.pointvalue) : 0.0 plannedQuantity = f_roundDown(math.min(riskQuantity, exposureQuantity), quantityStep) var float plannedStop = na var float plannedTarget = na var float plannedRisk = na var string activeSide = "FLAT" f_payload(string eventName, string side, float stopPrice, float targetPrice, float qty) => '{"schema":"hercules.signal.v1","event":"' + eventName + '","strategy":"GUS-V5","version":"1.0.0","side":"' + side + '","symbol":"' + standardTicker + '","timeframe":"' + timeframe.period + '","signalPrice":' + str.tostring(rawClose) + ',"stop":' + str.tostring(stopPrice) + ',"target":' + str.tostring(targetPrice) + ',"quantityApprox":' + str.tostring(qty) + ',"riskPct":' + str.tostring(riskPercent) + ',"barTime":' + str.tostring(time_close) + ',"environment":"PAPER"}' canEnter = strategy.position_size == 0 and plannedQuantity > 0.0 if canEnter and longSignal plannedStop := rawClose - stopDistance plannedRisk := rawClose - plannedStop plannedTarget := rawClose + plannedRisk * targetR activeSide := "LONG" strategy.entry("Long", strategy.long, qty=plannedQuantity, alert_message=f_payload("ENTRY", "LONG", plannedStop, plannedTarget, plannedQuantity)) else if canEnter and shortSignal plannedStop := rawClose + stopDistance plannedRisk := plannedStop - rawClose plannedTarget := rawClose - plannedRisk * targetR activeSide := "SHORT" strategy.entry("Short", strategy.short, qty=plannedQuantity, alert_message=f_payload("ENTRY", "SHORT", plannedStop, plannedTarget, plannedQuantity)) if strategy.position_size > 0 strategy.exit("Long bracket", from_entry="Long", stop=plannedStop, limit=plannedTarget, alert_message=f_payload("EXIT", "LONG", plannedStop, plannedTarget, math.abs(strategy.position_size))) if exitOnOpposite and shortSignal strategy.close("Long", comment="Confirmed opposite", alert_message=f_payload("OPPOSITE_EXIT", "LONG", plannedStop, plannedTarget, math.abs(strategy.position_size))) else if strategy.position_size < 0 strategy.exit("Short bracket", from_entry="Short", stop=plannedStop, limit=plannedTarget, alert_message=f_payload("EXIT", "SHORT", plannedStop, plannedTarget, math.abs(strategy.position_size))) if exitOnOpposite and longSignal strategy.close("Short", comment="Confirmed opposite", alert_message=f_payload("OPPOSITE_EXIT", "SHORT", plannedStop, plannedTarget, math.abs(strategy.position_size))) if strategy.position_size == 0 and strategy.position_size[1] != 0 activeSide := "FLAT" plannedStop := na plannedTarget := na plannedRisk := na plot(ema20, "EMA 20", color=#36c2d7) plot(ema50, "EMA 50", color=#efc45c, linewidth=2) plot(supertrend, "Supertrend", color=supertrendDirection < 0 ? color.new(#20c98b, 20) : color.new(#f06575, 20), style=plot.style_linebr) plot(strategy.position_size != 0 ? strategy.position_avg_price : na, "Entry", color=#36c2d7, style=plot.style_linebr) plot(strategy.position_size != 0 ? plannedStop : na, "Stop", color=#f06575, style=plot.style_linebr, linewidth=2) plot(strategy.position_size != 0 ? plannedTarget : na, "Target", color=#20c98b, style=plot.style_linebr, linewidth=2) plotshape(longSignal, title="Confirmed LONG", style=shape.triangleup, location=location.belowbar, color=#20c98b, text="L", textcolor=color.white, size=size.tiny) plotshape(shortSignal, title="Confirmed SHORT", style=shape.triangledown, location=location.abovebar, color=#f06575, text="S", textcolor=color.white, size=size.tiny) var table riskTable = table.new(position.bottom_right, 2, 5, bgcolor=color.new(#101923, 10), frame_color=color.new(#526173, 35), border_width=1) if barstate.islast table.cell(riskTable, 0, 0, "State", text_color=color.silver) table.cell(riskTable, 1, 0, activeSide, text_color=activeSide == "LONG" ? #20c98b : activeSide == "SHORT" ? #f06575 : #efc45c) table.cell(riskTable, 0, 1, "Risk budget", text_color=color.silver) table.cell(riskTable, 1, 1, str.tostring(riskBudget, "#.##"), text_color=color.white) table.cell(riskTable, 0, 2, "Qty approx", text_color=color.silver) table.cell(riskTable, 1, 2, str.tostring(plannedQuantity, "#.######"), text_color=color.white) table.cell(riskTable, 0, 3, "Stop ATR", text_color=color.silver) table.cell(riskTable, 1, 3, str.tostring(stopAtr, "#.##") + "x", text_color=color.white) table.cell(riskTable, 0, 4, "Target", text_color=color.silver) table.cell(riskTable, 1, 4, str.tostring(targetR, "#.##") + "R", text_color=color.white) if longSignal alert(f_payload("SIGNAL", "LONG", rawClose - stopDistance, rawClose + stopDistance * targetR, plannedQuantity), alert.freq_once_per_bar_close) else if shortSignal alert(f_payload("SIGNAL", "SHORT", rawClose + stopDistance, rawClose - stopDistance * targetR, plannedQuantity), alert.freq_once_per_bar_close)