Files
trading_bot_v4/workflows/trading/breaker_v1_strategy.pinescript
mindesbunister ba1fe4433e feat: Indicator score bypass - v11.2 sends SCORE:100 to bypass bot quality scoring
Changes:
- moneyline_v11_2_indicator.pinescript: Alert format now includes SCORE:100
- parse_signal_enhanced.json: Added indicatorScore parsing (SCORE:X regex)
- execute/route.ts: Added hasIndicatorScore bypass (score >= 90 bypasses quality check)
- Money_Machine.json: Both Execute Trade nodes now pass indicatorScore to API

Rationale: v11.2 indicator filters already optimized (2.544 PF, +51.80% return).
Bot-side quality scoring was blocking proven profitable signals (e.g., quality 75).
Now indicator passes SCORE:100, bot respects it and executes immediately.

This completes the signal chain:
Indicator (SCORE:100) → n8n parser (indicatorScore) → workflow → bot endpoint (bypass)
2025-12-26 11:40:12 +01:00

138 lines
6.6 KiB
Plaintext

//@version=6
strategy("Bullmania Breaker v1 Strategy", overlay=true, pyramiding=0, initial_capital=1000, default_qty_type=strategy.percent_of_equity, default_qty_value=100)
// Reuse the breaker_v1 signal logic for backtesting.
// Trend filters
emaFastLen = input.int(50, "EMA Fast", minval=1, group="Trend")
emaSlowLen = input.int(200, "EMA Slow", minval=1, group="Trend")
trendBuffer = input.float(0.10, "Trend buffer %", minval=0.0, step=0.05, group="Trend")
// Squeeze and release
bbLen = input.int(20, "BB Length", minval=1, group="Squeeze")
bbMult = input.float(2.0, "BB Multiplier", minval=0.1, step=0.1, group="Squeeze")
squeezeLookback = input.int(120, "Squeeze lookback", minval=10, group="Squeeze")
squeezeThreshold = input.float(0.25, "Squeeze threshold (0-1)", minval=0.0, maxval=1.0, step=0.05, group="Squeeze")
releaseThreshold = input.float(0.35, "Release threshold (0-1)", minval=0.0, maxval=1.0, step=0.05, group="Squeeze")
releaseWindow = input.int(30, "Release window bars", minval=1, group="Squeeze")
// Confirmation and filters
confirmPct = input.float(0.25, "Confirm move % (next bar)", minval=0.0, maxval=3.0, step=0.05, group="Confirmation")
volLookback = input.int(20, "Volume MA length", minval=1, group="Confirmation")
volSpike = input.float(1.2, "Volume spike x", minval=0.5, step=0.05, group="Confirmation")
adxLen = input.int(14, "ADX length", minval=1, group="Confirmation")
adxMin = input.int(18, "ADX minimum", minval=0, maxval=100, group="Confirmation")
rsiLen = input.int(14, "RSI length", minval=2, group="Confirmation")
rsiLongMin = input.float(48, "RSI long min", minval=0, maxval=100, group="Confirmation")
rsiLongMax = input.float(68, "RSI long max", minval=0, maxval=100, group="Confirmation")
rsiShortMin = input.float(32, "RSI short min", minval=0, maxval=100, group="Confirmation")
rsiShortMax = input.float(60, "RSI short max", minval=0, maxval=100, group="Confirmation")
priceRangeLookback = input.int(100, "Price position lookback", minval=10, group="Confirmation")
longPosMax = input.float(88, "Long max position %", minval=0, maxval=100, group="Confirmation")
shortPosMin = input.float(12, "Short min position %", minval=0, maxval=100, group="Confirmation")
cooldownBars = input.int(5, "Bars between signals", minval=0, maxval=100, group="Confirmation")
// Exits for backtest
useTP = input.bool(true, "Use take profit", group="Backtest Exits")
useSL = input.bool(true, "Use stop loss", group="Backtest Exits")
tpPct = input.float(1.2, "TP %", minval=0.1, maxval=10, step=0.1, group="Backtest Exits")
slPct = input.float(0.6, "SL %", minval=0.1, maxval=10, step=0.1, group="Backtest Exits")
// Core series
emaFast = ta.ema(close, emaFastLen)
emaSlow = ta.ema(close, emaSlowLen)
trendGap = (emaSlow == 0.0) ? 0.0 : math.abs((emaFast - emaSlow) / emaSlow) * 100
trendLong = emaFast > emaSlow and trendGap >= trendBuffer
trendShort = emaFast < emaSlow and trendGap >= trendBuffer
basis = ta.sma(close, bbLen)
dev = bbMult * ta.stdev(close, bbLen)
upper = basis + dev
lower = basis - dev
widthPct = basis == 0.0 ? 0.0 : (upper - lower) / basis * 100.0
lowW = ta.lowest(widthPct, squeezeLookback)
highW = ta.highest(widthPct, squeezeLookback)
denom = math.max(highW - lowW, 0.0001)
normWidth = (widthPct - lowW) / denom
squeeze = normWidth < squeezeThreshold
barsSinceSqueeze = ta.barssince(squeeze)
recentSqueeze = not na(barsSinceSqueeze) and barsSinceSqueeze <= releaseWindow
release = normWidth > releaseThreshold and recentSqueeze
volMA = ta.sma(volume, volLookback)
volumeRatio = volMA == 0.0 ? 0.0 : volume / volMA
rsi = ta.rsi(close, rsiLen)
[_, _, adxVal] = ta.dmi(adxLen, adxLen)
highestRange = ta.highest(high, priceRangeLookback)
lowestRange = ta.lowest(low, priceRangeLookback)
rangeSpan = math.max(highestRange - lowestRange, 0.0001)
pricePos = ((close - lowestRange) / rangeSpan) * 100
// Breakout candidates
breakoutLong = trendLong and release and close > upper and volumeRatio >= volSpike and adxVal >= adxMin and rsi >= rsiLongMin and rsi <= rsiLongMax and pricePos <= longPosMax
breakoutShort = trendShort and release and close < lower and volumeRatio >= volSpike and adxVal >= adxMin and rsi >= rsiShortMin and rsi <= rsiShortMax and pricePos >= shortPosMin
// Cooldown gate
var int lastSignalBar = na
cooldownOk = na(lastSignalBar) or (bar_index - lastSignalBar > cooldownBars)
// Two-stage confirmation
var int pendingDir = 0
var float pendingPrice = na
var int pendingBar = na
finalLong = false
finalShort = false
if cooldownOk
if breakoutLong
pendingDir := 1
pendingPrice := close
pendingBar := bar_index
if breakoutShort
pendingDir := -1
pendingPrice := close
pendingBar := bar_index
if pendingDir != 0 and bar_index == pendingBar + 1
longPass = pendingDir == 1 and (confirmPct <= 0 or close >= pendingPrice * (1 + confirmPct / 100.0))
shortPass = pendingDir == -1 and (confirmPct <= 0 or close <= pendingPrice * (1 - confirmPct / 100.0))
if longPass
finalLong := true
lastSignalBar := bar_index
if shortPass
finalShort := true
lastSignalBar := bar_index
pendingDir := 0
pendingPrice := na
pendingBar := na
// Entries
if finalLong
strategy.entry("Long", strategy.long)
if finalShort
strategy.entry("Short", strategy.short)
// Exits
if useTP or useSL
if strategy.position_size > 0
longStop = useSL ? strategy.position_avg_price * (1 - slPct / 100.0) : na
longLimit = useTP ? strategy.position_avg_price * (1 + tpPct / 100.0) : na
strategy.exit("L Exit", from_entry="Long", stop=longStop, limit=longLimit)
if strategy.position_size < 0
shortStop = useSL ? strategy.position_avg_price * (1 + slPct / 100.0) : na
shortLimit = useTP ? strategy.position_avg_price * (1 - tpPct / 100.0) : na
strategy.exit("S Exit", from_entry="Short", stop=shortStop, limit=shortLimit)
// Plots
plot(emaFast, "EMA Fast", color=color.new(color.green, 0), linewidth=1)
plot(emaSlow, "EMA Slow", color=color.new(color.red, 0), linewidth=1)
plot(basis, "BB Basis", color=color.new(color.gray, 50))
plot(upper, "BB Upper", color=color.new(color.teal, 40))
plot(lower, "BB Lower", color=color.new(color.teal, 40))
plotshape(squeeze, title="Squeeze", location=location.bottom, color=color.new(color.blue, 10), style=shape.square, size=size.tiny)
plotshape(release, title="Release", location=location.bottom, color=color.new(color.orange, 0), style=shape.triangleup, size=size.tiny)
plotshape(finalLong, title="Buy", location=location.belowbar, color=color.lime, style=shape.circle, size=size.small, text="B")
plotshape(finalShort, title="Sell", location=location.abovebar, color=color.red, style=shape.circle, size=size.small, text="S")