Files
trading_bot_v4/workflows/trading/moneyline_v11_2_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

207 lines
9.3 KiB
Plaintext

//@version=6
strategy("Money Line v11.2 STRATEGY", shorttitle="ML v11.2 Strat", overlay=true, pyramiding=0, initial_capital=1000, default_qty_type=strategy.percent_of_equity, default_qty_value=100)
// V11.2 STRATEGY VERSION (Dec 25, 2025):
// Same logic as indicator but with entries/exits for TradingView Strategy Report
// === CORE PARAMETERS ===
atrPeriod = input.int(12, "ATR Period", minval=1, group="Core")
multiplier = input.float(3.8, "Multiplier", minval=0.1, step=0.1, group="Core")
// === SIGNAL TIMING ===
confirmBars = input.int(1, "Bars to confirm after flip", minval=0, maxval=3, group="Timing")
flipThreshold = input.float(0.20, "Flip threshold %", minval=0.0, maxval=2.0, step=0.05, group="Timing")
// === ENTRY FILTERS ===
useEntryBuffer = input.bool(true, "Require entry buffer (ATR)", group="Filters")
entryBufferATR = input.float(-0.10, "Buffer size (in ATR, negative=early)", minval=-1.0, step=0.05, group="Filters")
useAdx = input.bool(true, "Use ADX filter", group="Filters")
adxLen = input.int(16, "ADX Length", minval=1, group="Filters")
adxMin = input.int(12, "ADX minimum", minval=0, maxval=100, group="Filters")
// === RSI FILTER ===
useRsiFilter = input.bool(true, "Use RSI filter", group="RSI")
rsiLongMin = input.float(56, "RSI Long Min", minval=0, maxval=100, group="RSI")
rsiLongMax = input.float(69, "RSI Long Max", minval=0, maxval=100, group="RSI")
rsiShortMin = input.float(30, "RSI Short Min", minval=0, maxval=100, group="RSI")
rsiShortMax = input.float(70, "RSI Short Max", minval=0, maxval=100, group="RSI")
// === POSITION FILTER ===
usePricePosition = input.bool(true, "Use price position filter", group="Position")
longPosMax = input.float(85, "Long max position %", minval=0, maxval=100, group="Position")
shortPosMin = input.float(5, "Short min position %", minval=0, maxval=100, group="Position")
// === VOLUME FILTER ===
useVolumeFilter = input.bool(true, "Use volume filter", group="Volume")
volMin = input.float(0.1, "Volume min ratio", minval=0.0, step=0.1, group="Volume")
volMax = input.float(3.5, "Volume max ratio", minval=0.5, step=0.5, group="Volume")
// === EXITS ===
tpPct = input.float(1.0, "TP %", minval=0.1, maxval=10, step=0.1, group="Exits")
slPct = input.float(0.8, "SL %", minval=0.1, maxval=10, step=0.1, group="Exits")
// =============================================================================
// MONEY LINE CALCULATION
// =============================================================================
calcH = high
calcL = low
calcC = close
// ATR
tr = math.max(calcH - calcL, math.max(math.abs(calcH - calcC[1]), math.abs(calcL - calcC[1])))
atr = ta.rma(tr, atrPeriod)
src = (calcH + calcL) / 2
up = src - (multiplier * atr)
dn = src + (multiplier * atr)
var float up1 = na
var float dn1 = na
up1 := nz(up1[1], up)
dn1 := nz(dn1[1], dn)
up1 := calcC[1] > up1 ? math.max(up, up1) : up
dn1 := calcC[1] < dn1 ? math.min(dn, dn1) : dn
var int trend = 1
var float tsl = na
tsl := nz(tsl[1], up1)
// Flip threshold
thresholdAmount = tsl * (flipThreshold / 100)
// Track consecutive bars for flip confirmation
var int bullMomentumBars = 0
var int bearMomentumBars = 0
if trend == 1
tsl := math.max(up1, tsl)
if calcC < (tsl - thresholdAmount)
bearMomentumBars := bearMomentumBars + 1
bullMomentumBars := 0
else
bearMomentumBars := 0
trend := bearMomentumBars >= (confirmBars + 1) ? -1 : 1
else
tsl := math.min(dn1, tsl)
if calcC > (tsl + thresholdAmount)
bullMomentumBars := bullMomentumBars + 1
bearMomentumBars := 0
else
bullMomentumBars := 0
trend := bullMomentumBars >= (confirmBars + 1) ? 1 : -1
supertrend = tsl
// =============================================================================
// INDICATORS
// =============================================================================
// ADX
upMove = calcH - calcH[1]
downMove = calcL[1] - calcL
plusDM = (upMove > downMove and upMove > 0) ? upMove : 0.0
minusDM = (downMove > upMove and downMove > 0) ? downMove : 0.0
trADX = math.max(calcH - calcL, math.max(math.abs(calcH - calcC[1]), math.abs(calcL - calcC[1])))
atrADX = ta.rma(trADX, adxLen)
plusDMSmooth = ta.rma(plusDM, adxLen)
minusDMSmooth = ta.rma(minusDM, adxLen)
plusDI = atrADX == 0.0 ? 0.0 : 100.0 * plusDMSmooth / atrADX
minusDI = atrADX == 0.0 ? 0.0 : 100.0 * minusDMSmooth / atrADX
dx = (plusDI + minusDI == 0.0) ? 0.0 : 100.0 * math.abs(plusDI - minusDI) / (plusDI + minusDI)
adxVal = ta.rma(dx, adxLen)
// RSI
rsi14 = ta.rsi(calcC, 14)
// Volume ratio
volMA20 = ta.sma(volume, 20)
volumeRatio = volume / volMA20
// Price position in 100-bar range
highest100 = ta.highest(calcH, 100)
lowest100 = ta.lowest(calcL, 100)
priceRange = highest100 - lowest100
pricePosition = priceRange == 0 ? 50.0 : ((calcC - lowest100) / priceRange) * 100
// =============================================================================
// FILTER CHECKS
// =============================================================================
adxOk = not useAdx or (adxVal >= adxMin)
longBufferOk = not useEntryBuffer or (calcC > supertrend + entryBufferATR * atr)
shortBufferOk = not useEntryBuffer or (calcC < supertrend - entryBufferATR * atr)
rsiLongOk = not useRsiFilter or (rsi14 >= rsiLongMin and rsi14 <= rsiLongMax)
rsiShortOk = not useRsiFilter or (rsi14 >= rsiShortMin and rsi14 <= rsiShortMax)
longPositionOk = not usePricePosition or (pricePosition < longPosMax)
shortPositionOk = not usePricePosition or (pricePosition > shortPosMin)
volumeOk = not useVolumeFilter or (volumeRatio >= volMin and volumeRatio <= volMax)
// =============================================================================
// SIGNALS
// =============================================================================
buyFlip = trend == 1 and trend[1] == -1
sellFlip = trend == -1 and trend[1] == 1
buyReady = ta.barssince(buyFlip) == confirmBars
sellReady = ta.barssince(sellFlip) == confirmBars
// FINAL SIGNALS (all filters applied!)
finalLongSignal = buyReady and adxOk and longBufferOk and rsiLongOk and longPositionOk and volumeOk
finalShortSignal = sellReady and adxOk and shortBufferOk and rsiShortOk and shortPositionOk and volumeOk
// =============================================================================
// STRATEGY ENTRIES & EXITS
// =============================================================================
if finalLongSignal
strategy.entry("Long", strategy.long)
if finalShortSignal
strategy.entry("Short", strategy.short)
// Exits with TP/SL
if strategy.position_size > 0
strategy.exit("L Exit", "Long", stop=strategy.position_avg_price * (1 - slPct/100), limit=strategy.position_avg_price * (1 + tpPct/100))
if strategy.position_size < 0
strategy.exit("S Exit", "Short", stop=strategy.position_avg_price * (1 + slPct/100), limit=strategy.position_avg_price * (1 - tpPct/100))
// =============================================================================
// PLOTS
// =============================================================================
upTrend = trend == 1 ? supertrend : na
downTrend = trend == -1 ? supertrend : na
plot(upTrend, "Up Trend", color=color.new(color.green, 0), style=plot.style_linebr, linewidth=2)
plot(downTrend, "Down Trend", color=color.new(color.red, 0), style=plot.style_linebr, linewidth=2)
plotshape(finalLongSignal, title="Buy", location=location.belowbar, color=color.lime, style=shape.triangleup, size=size.small)
plotshape(finalShortSignal, title="Sell", location=location.abovebar, color=color.red, style=shape.triangledown, size=size.small)
// =============================================================================
// DEBUG TABLE
// =============================================================================
var table dbg = table.new(position.top_right, 2, 8, bgcolor=color.new(color.black, 80))
if barstate.islast
table.cell(dbg, 0, 0, "Trend", text_color=color.white)
table.cell(dbg, 1, 0, trend == 1 ? "LONG ✓" : "SHORT ✓", text_color=trend == 1 ? color.lime : color.red)
table.cell(dbg, 0, 1, "ADX", text_color=color.white)
table.cell(dbg, 1, 1, str.tostring(adxVal, "#.#") + (adxOk ? " ✓" : " ✗"), text_color=adxOk ? color.lime : color.red)
table.cell(dbg, 0, 2, "RSI", text_color=color.white)
table.cell(dbg, 1, 2, str.tostring(rsi14, "#.#") + (rsiLongOk or rsiShortOk ? " ✓" : " ✗"), text_color=rsiLongOk or rsiShortOk ? color.lime : color.orange)
table.cell(dbg, 0, 3, "Price Pos", text_color=color.white)
table.cell(dbg, 1, 3, str.tostring(pricePosition, "#.#") + "%" + (longPositionOk and shortPositionOk ? " ✓" : ""), text_color=color.white)
table.cell(dbg, 0, 4, "Volume", text_color=color.white)
table.cell(dbg, 1, 4, str.tostring(volumeRatio, "#.##") + "x" + (volumeOk ? " ✓" : " ✗"), text_color=volumeOk ? color.lime : color.orange)
table.cell(dbg, 0, 5, "Entry Buffer", text_color=color.white)
table.cell(dbg, 1, 5, longBufferOk or shortBufferOk ? "OK ✓" : "—", text_color=longBufferOk or shortBufferOk ? color.lime : color.gray)
table.cell(dbg, 0, 6, "Signal", text_color=color.white)
table.cell(dbg, 1, 6, finalLongSignal ? "BUY!" : finalShortSignal ? "SELL!" : "—", text_color=finalLongSignal ? color.lime : finalShortSignal ? color.red : color.gray)
table.cell(dbg, 0, 7, "TP/SL", text_color=color.white)
table.cell(dbg, 1, 7, "+" + str.tostring(tpPct, "#.#") + "% / -" + str.tostring(slPct, "#.#") + "%", text_color=color.yellow)