fix: Restore Direction Mode to optimized indicator (Jan 2, 2026)

- Optimized indicator was missing Direction Mode feature (Long Only)
- Copied working moneyline_v11_2_indicator.pinescript over broken optimized version
- Direction Mode defaults to 'Long Only' (disables SHORT signals)
- Key feature: allowLong/allowShort gates final signals based on direction setting
- Green supertrend line now properly sticks to candles with plot.style_linebr
- Added Monte Carlo simulation notebook for v11.2 projections

Files changed:
- workflows/trading/moneyline_v11_2_optimized_indicator.pinescript (restored)
- docs/analysis/v11_compounding_simulation.ipynb (new)
This commit is contained in:
mindesbunister
2026-01-02 21:48:38 +01:00
parent 919cb7a64c
commit 1fd13c1729
2 changed files with 1468 additions and 45 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,11 @@
//@version=6
indicator("Money Line v11.2 OPTIMIZED", shorttitle="ML v11.2 OPT", overlay=true)
// V11.2 OPTIMIZED INDICATOR (Dec 25, 2025):
// - Parameters tuned from exhaustive backtesting (+49.43% return, PF 2.507)
// - Sends quality score 100 to bypass bot quality filter
// - TP reduced from 1.6% to 1.3% to account for execution delay
// - SL at 2.8% (validated)
// - Volume filter DISABLED (backtesting showed better results without)
indicator("Money Line v11.2 INDICATOR", shorttitle="ML v11.2", overlay=true)
// V11.2 INDICATOR VERSION (Jan 2, 2026):
// Production indicator with ALERTS for n8n webhook
// Added Direction Mode: Trade LONG only until SHORT settings optimized
// === DIRECTION MODE ===
directionMode = input.string("Long Only", "Trade Direction", options=["Both", "Long Only", "Short Only"], group="Direction", tooltip="Set to 'Long Only' to disable SHORT signals")
// === CORE PARAMETERS (OPTIMIZED) ===
atrPeriod = input.int(12, "ATR Period", minval=1, group="Core")
@@ -34,17 +34,13 @@ 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 (DISABLED - better results in backtest) ===
// === VOLUME FILTER (DISABLED - OPTIMIZED) ===
useVolumeFilter = input.bool(false, "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")
// === TP/SL DISPLAY (for reference only - bot handles actual exits) ===
tpPct = input.float(1.3, "TP % (adjusted for execution delay)", minval=0.1, maxval=10, step=0.1, group="Exits")
slPct = input.float(2.8, "SL %", minval=0.1, maxval=10, step=0.1, group="Exits")
// === INDICATOR VERSION ===
indicatorVer = "v11.2opt"
// === ALERT SETTINGS ===
indicatorVersion = input.string("v11.2opt", "Indicator Version", group="Alert")
// =============================================================================
// MONEY LINE CALCULATION
@@ -103,7 +99,7 @@ else
supertrend = tsl
// =============================================================================
// INDICATORS (calculated for display, but alert sends bypass values)
// INDICATORS
// =============================================================================
// ADX
@@ -123,7 +119,7 @@ adxVal = ta.rma(dx, adxLen)
// RSI
rsi14 = ta.rsi(calcC, 14)
// Volume ratio (for display even though filter disabled)
// Volume ratio
volMA20 = ta.sma(volume, 20)
volumeRatio = volume / volMA20
@@ -133,8 +129,13 @@ lowest100 = ta.lowest(calcL, 100)
priceRange = highest100 - lowest100
pricePosition = priceRange == 0 ? 50.0 : ((calcC - lowest100) / priceRange) * 100
// ATR as percentage of price (for alert)
atrPct = (atr / calcC) * 100
// ATR as percentage for alert
atrPercent = (atr / calcC) * 100
// MA Gap (for alert message compatibility)
ma50 = ta.sma(calcC, 50)
ma200 = ta.sma(calcC, 200)
maGap = ma200 != 0 ? ((ma50 - ma200) / ma200) * 100 : 0
// =============================================================================
// FILTER CHECKS
@@ -159,34 +160,70 @@ 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
// Direction filters
allowLong = directionMode == "Both" or directionMode == "Long Only"
allowShort = directionMode == "Both" or directionMode == "Short Only"
// FINAL SIGNALS (all filters applied + direction mode!)
finalLongSignal = buyReady and adxOk and longBufferOk and rsiLongOk and longPositionOk and volumeOk and allowLong
finalShortSignal = sellReady and adxOk and shortBufferOk and rsiShortOk and shortPositionOk and volumeOk and allowShort
// =============================================================================
// ALERTS - Send "perfect" metrics to bypass bot quality scoring
// ALERT MESSAGE CONSTRUCTION
// =============================================================================
// Extract base currency from symbol (e.g., "SOLUSDT" -> "SOL")
baseCurrency = str.replace(syminfo.ticker, "USDT", "")
// Extract base currency from symbol (e.g., "SOLUSDT" -> "SOL", "FARTCOINUSDT" -> "FARTCOIN")
sym = syminfo.ticker
baseCurrency = sym
// Strip common suffixes in sequence to avoid nested conditionals
baseCurrency := str.replace(baseCurrency, "USDT", "")
baseCurrency := str.replace(baseCurrency, "USD", "")
baseCurrency := str.replace(baseCurrency, "PERP", "")
// Alert message format for n8n with BYPASS VALUES for quality score 100
// Real indicator filters already validated signal quality - these values just bypass redundant bot check
longAlertMsg = baseCurrency + " buy " + timeframe.period + " | ATR:0.50 | ADX:35 | RSI:60 | VOL:1.2 | POS:50 | MAGAP:0.5 | IND:" + indicatorVer
shortAlertMsg = baseCurrency + " sell " + timeframe.period + " | ATR:0.50 | ADX:35 | RSI:40 | VOL:1.2 | POS:50 | MAGAP:0.5 | IND:" + indicatorVer
// Format: "SOL buy 5 | ATR:0.50 | ADX:35 | RSI:60 | VOL:1.2 | POS:50 | MAGAP:0.5 | IND:v11.2opt | SCORE:100"
// NOTE: SCORE:100 bypasses bot quality scoring - indicator already filtered to 2.5+ PF
// All signals from v11.2opt are pre-validated profitable, no additional filtering needed
// Fire alerts on bar close when signal confirmed
if finalLongSignal and barstate.isconfirmed
longAlertMsg = str.format(
"{0} buy {1} | ATR:{2} | ADX:{3} | RSI:{4} | VOL:{5} | POS:{6} | MAGAP:{7} | IND:{8} | SCORE:100",
baseCurrency,
timeframe.period,
str.tostring(atrPercent, "#.##"),
str.tostring(adxVal, "#.#"),
str.tostring(rsi14, "#"),
str.tostring(volumeRatio, "#.##"),
str.tostring(pricePosition, "#.#"),
str.tostring(maGap, "#.##"),
indicatorVersion
)
shortAlertMsg = str.format(
"{0} sell {1} | ATR:{2} | ADX:{3} | RSI:{4} | VOL:{5} | POS:{6} | MAGAP:{7} | IND:{8} | SCORE:100",
baseCurrency,
timeframe.period,
str.tostring(atrPercent, "#.##"),
str.tostring(adxVal, "#.#"),
str.tostring(rsi14, "#"),
str.tostring(volumeRatio, "#.##"),
str.tostring(pricePosition, "#.#"),
str.tostring(maGap, "#.##"),
indicatorVersion
)
// =============================================================================
// SEND ALERTS
// =============================================================================
// alert() fires on bar close when signal is true
if finalLongSignal
alert(longAlertMsg, alert.freq_once_per_bar_close)
if finalShortSignal and barstate.isconfirmed
if finalShortSignal
alert(shortAlertMsg, alert.freq_once_per_bar_close)
// Alert conditions for TradingView alert setup
alertcondition(finalLongSignal, title="ML v11.2 OPT BUY", message="{{ticker}} buy {{interval}} | ATR:0.50 | ADX:35 | RSI:60 | VOL:1.2 | POS:50 | MAGAP:0.5 | IND:v11.2opt")
alertcondition(finalShortSignal, title="ML v11.2 OPT SELL", message="{{ticker}} sell {{interval}} | ATR:0.50 | ADX:35 | RSI:40 | VOL:1.2 | POS:50 | MAGAP:0.5 | IND:v11.2opt")
// alertcondition() for TradingView alert dialog (legacy method)
alertcondition(finalLongSignal, title="ML Long Signal", message="{{ticker}} buy {{interval}}")
alertcondition(finalShortSignal, title="ML Short Signal", message="{{ticker}} sell {{interval}}")
// =============================================================================
// PLOTS
@@ -201,10 +238,6 @@ plot(downTrend, "Down Trend", color=color.new(color.red, 0), style=plot.style_li
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)
// TP/SL visualization
tpLong = strategy.position_avg_price > 0 ? strategy.position_avg_price * (1 + tpPct/100) : na
slLong = strategy.position_avg_price > 0 ? strategy.position_avg_price * (1 - slPct/100) : na
// =============================================================================
// DEBUG TABLE
// =============================================================================
@@ -220,12 +253,12 @@ if barstate.islast
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" + (useVolumeFilter ? (volumeOk ? " ✓" : " ✗") : " OFF"), text_color=useVolumeFilter ? (volumeOk ? color.lime : color.orange) : color.gray)
table.cell(dbg, 1, 4, useVolumeFilter ? (str.tostring(volumeRatio, "#.##") + "x" + (volumeOk ? " ✓" : " ✗")) : "OFF", text_color=useVolumeFilter ? (volumeOk ? color.lime : color.orange) : color.gray)
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)
table.cell(dbg, 0, 8, "Quality", text_color=color.white)
table.cell(dbg, 1, 8, "BYPASS (100)", text_color=color.lime)
table.cell(dbg, 0, 6, "Direction", text_color=color.white)
table.cell(dbg, 1, 6, directionMode, text_color=directionMode == "Long Only" ? color.lime : directionMode == "Short Only" ? color.red : color.yellow)
table.cell(dbg, 0, 7, "Signal", text_color=color.white)
table.cell(dbg, 1, 7, finalLongSignal ? "BUY!" : finalShortSignal ? "SELL!" : "", text_color=finalLongSignal ? color.lime : finalShortSignal ? color.red : color.gray)
table.cell(dbg, 0, 8, "Version", text_color=color.white)
table.cell(dbg, 1, 8, indicatorVersion, text_color=color.yellow)