Strategy · Pine Script v6 · On the chart

Crypto TA Pro Day Trading S/R Execution

by Xellos1010

A Pine v6 day trading research strategy for crypto charts.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code1,023
Settings40inputs
LicenceMIT
Last update11 days ago18 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new strategy, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart. The trades and results appear in the Strategy Tester tab; adjust the settings with the gear icon.

Source Code

1,023 lines of Pine Script v6 from pine-scripts/ctapv6_daytrading_sr_execution_v3_warning_clean.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

ctapv6_daytrading_sr_execution_v3_warning_clean.pine
//@version=6
//@strategy_alert_message {{strategy.order.alert_message}}
strategy(
     title = "Crypto TA Pro v6 - Day Trading S/R Execution Bible",
     shorttitle = "CTAP-DaySR",
     overlay = true,
     precision = 8,
     max_bars_back = 1000,
     max_labels_count = 500,
     max_lines_count = 500,
     max_boxes_count = 500,
     pyramiding = 1,
     initial_capital = 100,
     default_qty_type = strategy.percent_of_equity,
     default_qty_value = 100,
     commission_type = strategy.commission.percent,
     commission_value = 0.06,
     slippage = 1,
     margin_long = 100,
     margin_short = 100,
     use_bar_magnifier = false
)

// -----------------------------------------------------------------------------
// Crypto TA Pro v6 - Day Trading S/R Execution Bible
// Built for quick-turnaround Coinbase crypto chart research.
//
// RESEARCH ASSUMPTIONS — not live-execution defaults:
//   commission = 0.06% per side (Coinbase Advanced Trade taker rate; maker is ~0.04%).
//     Adjust for your actual fee tier. Does not model complex-order rebates.
//   slippage = 1 tick (TradingView "points"). For crypto tick = smallest price increment.
//     Real market-order slippage on non-trivial size is typically larger; 1-tick understates cost.
//   initial_capital = $100 (research-scale; NOT representative of live notional).
//   default_qty_value = 100% of equity per trade (full Kelly equivalent) when researchSizingMode = true.
//     Use fractional sizing (10-25%) for live; disable researchSizingMode and set liveQtyPct instead.
//     The override is applied at strategy.entry via explicit qty; strategy() declaration is unchanged.
//   These values affect absolute P&L and drawdown numbers but not signal timing or direction.
//
// Major systems:
// - RSI, Fibonacci Bollinger Bands, Trend/HTF MA, Ichimoku.
// - Pivot support/resistance zones with visual boxes.
// - Candlestick pattern visualization and weighted trading influence.
// - Chart/breakout pattern visualization and weighted trading influence.
// - Entry price range boxes, green profit boxes, red loss boxes.
// - Trade outcome labels: WIN / FAILED / TIMEOUT / SESSION / DATE / SCORE.
// - Day-trading auto-exit by session end, max bars, and max minutes.
// - In-sample / out-of-sample / forward-paper windows for module unlock testing.
//
// Research only. Strategies simulate hypothetical orders on chart data.
// Validate in TradingView before using alerts. Not financial advice.
// -----------------------------------------------------------------------------

// === INPUT GROUPS ===
const string G_EXEC     = "01. Execution + Day Trading"
const string G_RESEARCH = "02. Research / OOS Testing"
const string G_MODULES  = "03. Module Activation"
const string G_WEIGHTS  = "04. Weighted Signal Engine"
const string G_SR       = "05. Support / Resistance"
const string G_REGIME   = "06. Trend MA / Regime"
const string G_RSI      = "07. RSI"
const string G_BB       = "08. Fibonacci Bollinger Bands"
const string G_ICHI     = "09. Ichimoku"
const string G_CANDLE   = "10. Candlestick Patterns"
const string G_PATTERN  = "11. Chart / Breakout Patterns"
const string G_FILTERS  = "12. Volume + Volatility Filters"
const string G_RISK     = "13. Risk Management"
const string G_VIS      = "14. Visual Trade Map"
const string G_ALERT    = "15. Alerts"

// === EXECUTION INPUTS ===
tradeMode      = input.string("Spot", "Market mode", options = ["Spot", "Derivatives"], group = G_EXEC, tooltip = "Spot forces long-only logic. Derivatives unlocks short-side research logic.")
tradeDirection = input.string("Long only", "Trade direction", options = ["Long only", "Long + Short"], group = G_EXEC)
startTime      = input.time(1767225600000, "Backtest start", group = G_EXEC)   // 2026-01-01 00:00 UTC
endTime        = input.time(1798761540000, "Backtest end", group = G_EXEC)     // 2026-12-31 23:59 UTC
confirmedOnly  = input.bool(true, "Only act after confirmed bar close", group = G_EXEC)
useSessionFilter = input.bool(true, "Use day-trading session filter", group = G_EXEC)
tradeSession   = input.session("0800-2000:1234567", "Trading session", group = G_EXEC, tooltip = "For crypto, use 24x7 or define a daily trading window. Session uses sessionTimezone below.")
sessionTimezone = input.string("Etc/UTC", "Session timezone", group = G_EXEC)
closeAtSessionEnd = input.bool(true, "Auto-close when outside session", group = G_EXEC)
enableTimedExit = input.bool(true, "Auto-exit after time in market", group = G_EXEC)
maxBarsInTrade = input.int(24, "Max bars in trade", minval = 1, maxval = 500, group = G_EXEC)
maxMinutesInTrade = input.int(240, "Max minutes in trade", minval = 1, maxval = 10080, group = G_EXEC)
// Timeout mode: "Bars OR Minutes" fires on whichever limit is hit first; "Bars only" ignores wall-clock
// time (useful for variable-length bars); "Minutes only" ignores bar count (useful for fixed-interval
// sessions where bar length is guaranteed). Default "Bars OR Minutes" preserves prior behaviour.
timedExitMode = input.string("Bars OR Minutes", "Timed exit fires on", options = ["Bars OR Minutes", "Bars only", "Minutes only"], group = G_EXEC)
closeOutsideRange = input.bool(true, "Close positions outside selected range", group = G_EXEC)
entryProcess   = input.string("Market next bar", "Entry model label", options = ["Market next bar", "Close-confirmed research", "Limit retest research"], group = G_EXEC)

// === RESEARCH / OOS TESTING ===
// Bounded tuning discipline:
//   researchPreset adds indicators one at a time (ordered). Tune ONLY the current preset's parameters;
//   do not sweep across all presets simultaneously — that is re-fitting on OOS data.
//   Each parameter group has explicit min/max bounds in its input() call; stay within them.
//   Promote a parameter set to OOS/Paper only after it passes the IS gate.
//   The background shading and OOS markers visually enforce which bars belong to each segment.
// Experimental pattern engine (enablePatterns) is OFF by default — enable only in dedicated
// pattern-research studies; do not include pattern gates in baseline IS/OOS comparisons.
researchPreset = input.string("Custom toggles", "Indicator unlock preset", options = ["Custom toggles", "01 RSI only", "02 RSI + S/R", "03 Add FibBB", "04 Add Trend/HTF", "05 Add Ichimoku", "06 Add Candles", "07 Add Chart Patterns", "08 Full weighted"], group = G_RESEARCH)
validationSegment = input.string("Full selected range", "Trade segment", options = ["Full selected range", "In-sample only", "Out-of-sample only", "Forward/Paper only"], group = G_RESEARCH)
inSampleStart  = input.time(1767225600000, "In-sample start", group = G_RESEARCH)
inSampleEnd    = input.time(1782863940000, "In-sample end", group = G_RESEARCH)
oosStart       = input.time(1782864000000, "Out-of-sample start", group = G_RESEARCH)
oosEnd         = input.time(1798761540000, "Out-of-sample end", group = G_RESEARCH)
paperStart     = input.time(1798761600000, "Forward/Paper start", group = G_RESEARCH)
paperEnd       = input.time(1830297540000, "Forward/Paper end", group = G_RESEARCH)
showResearchZones = input.bool(true, "Shade research segments", group = G_RESEARCH)
// OOS segment transition markers: labels at the first bar of each segment so parity review can
// identify IS/OOS/Paper boundaries in screenshots and reports without relying on background color alone.
showOOSMarkers = input.bool(true, "Show IS/OOS/Paper transition markers", group = G_RESEARCH)
// Position sizing: research mode uses 100% equity per trade (strategy() default) for clean ratio math.
// Operators switching to live use must disable research mode and set a conservative live size.
// Because strategy() default_qty_value is static, the live override is applied via explicit qty at entry.
researchSizingMode = input.bool(true, "Research sizing (100% equity)", group = G_RESEARCH,
     tooltip = "True = 100% of equity per trade (backtest/research only). False = use live position size below.")
liveQtyPct = input.float(5.0, "Live position size (% equity)", minval = 0.1, maxval = 25.0, step = 0.5, group = G_RESEARCH,
     tooltip = "Only active when Research sizing is disabled. Set to your intended live risk per trade.")

// === MODULE ACTIVATION ===
enableRSI       = input.bool(true, "Enable RSI", group = G_MODULES)
enableSR        = input.bool(true, "Enable support/resistance", group = G_MODULES)
enableFibBB     = input.bool(true, "Enable Fibonacci Bollinger Bands", group = G_MODULES)
enableTrendMA   = input.bool(true, "Enable trend MA / regime filter", group = G_MODULES)
enableIchimoku  = input.bool(false, "Enable Ichimoku", group = G_MODULES)
enableCandles   = input.bool(true, "Enable candlestick pattern engine", group = G_MODULES)
enablePatterns  = input.bool(false, "Enable chart/breakout pattern engine", group = G_MODULES)

// === WEIGHTED SIGNAL ENGINE ===
// Confluence semantics: score is 0–100 (percent-weighted agreement), not a vote count.
//   entryThreshold = 60 → at least 60% weighted confluence required before entry.
//   Weights are per-indicator multipliers applied to the indicator's 0–1 vote; final score
//   is normalized by total active weight so the threshold is scale-invariant.
//   Recommended weight range: 0.0–3.0. Values above 3.0 allow a single indicator to
//   dominate the score and effectively bypass confluence; use only for explicit override studies.
//   requirePrimarySetup adds a boolean gate independent of the score (at least one primary
//   indicator must give a positive vote, regardless of weighted sum from secondary indicators).
entryThreshold = input.float(60.0, "Entry score threshold", minval = 1.0, maxval = 100.0, step = 1.0, group = G_WEIGHTS)
exitThreshold  = input.float(60.0, "Signal-exit score threshold", minval = 1.0, maxval = 100.0, step = 1.0, group = G_WEIGHTS)
requirePrimarySetup = input.bool(true, "Require at least one primary setup", group = G_WEIGHTS)
useRSIForEntry      = input.bool(true, "RSI influences entry", group = G_WEIGHTS)
useRSIForExit       = input.bool(true, "RSI influences exit", group = G_WEIGHTS)
rsiEntryWeight      = input.float(1.00, "RSI entry weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
rsiExitWeight       = input.float(1.00, "RSI exit weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
useSRForEntry       = input.bool(true, "S/R influences entry", group = G_WEIGHTS)
useSRForExit        = input.bool(true, "S/R influences exit", group = G_WEIGHTS)
srEntryWeight       = input.float(1.25, "S/R entry weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
srExitWeight        = input.float(1.00, "S/R exit weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
useFibBBForEntry    = input.bool(true, "FibBB influences entry", group = G_WEIGHTS)
useFibBBForExit     = input.bool(true, "FibBB influences exit", group = G_WEIGHTS)
fibBBEntryWeight    = input.float(1.00, "FibBB entry weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
fibBBExitWeight     = input.float(1.00, "FibBB exit weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
useTrendForEntry    = input.bool(true, "Trend/regime influences entry", group = G_WEIGHTS)
useTrendForExit     = input.bool(false, "Trend/regime influences exit", group = G_WEIGHTS)
trendEntryWeight    = input.float(1.00, "Trend entry weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
trendExitWeight     = input.float(0.50, "Trend exit weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
useIchiForEntry     = input.bool(true, "Ichimoku influences entry", group = G_WEIGHTS)
useIchiForExit      = input.bool(true, "Ichimoku influences exit", group = G_WEIGHTS)
ichiEntryWeight     = input.float(1.00, "Ichimoku entry weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
ichiExitWeight      = input.float(1.00, "Ichimoku exit weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
useCandlesForEntry  = input.bool(true, "Candles influence entry", group = G_WEIGHTS)
useCandlesForExit   = input.bool(true, "Candles influence exit", group = G_WEIGHTS)
candleEntryWeight   = input.float(1.00, "Candle entry weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
candleExitWeight    = input.float(1.00, "Candle exit weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
usePatternsForEntry = input.bool(true, "Chart patterns influence entry", group = G_WEIGHTS)
usePatternsForExit  = input.bool(true, "Chart patterns influence exit", group = G_WEIGHTS)
patternEntryWeight  = input.float(1.00, "Pattern entry weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)
patternExitWeight   = input.float(1.00, "Pattern exit weight", minval = 0.0, step = 0.25, group = G_WEIGHTS)

// === SUPPORT / RESISTANCE INPUTS ===
showSRZones = input.bool(true, "Show support/resistance zones", group = G_SR)
srPivotLeft = input.int(5, "S/R pivot left bars", minval = 1, maxval = 50, group = G_SR)
srPivotRight = input.int(5, "S/R pivot right bars", minval = 1, maxval = 50, group = G_SR)
srAtrBufferMult = input.float(0.25, "Zone thickness ATR multiplier", minval = 0.01, maxval = 5.0, step = 0.01, group = G_SR)
srExtendBars = input.int(150, "Extend S/R zones right bars", minval = 5, maxval = 500, group = G_SR)
srMaxZones = input.int(20, "Max visible S/R zones", minval = 2, maxval = 100, group = G_SR)
requireSRContext = input.bool(false, "Require S/R context for entries", group = G_SR)
srFailExit = input.bool(true, "Exit when active S/R zone fails", group = G_SR)
// S/R break semantics: "Close below zone" — bar must close below support / above resistance (body-confirmed,
// conservative). "Wick below zone" — any intra-bar penetration of the zone triggers a break (wick-sensitive,
// fires earlier but more prone to false signals on wicks). Default close-based for parity fidelity.
srBreakMode = input.string("Close below zone", "S/R break detection mode", options = ["Close below zone", "Wick below zone"], group = G_SR)
entryRangeMode = input.string("Hybrid ATR + S/R", "Entry price range model", options = ["ATR around signal close", "Nearest support/resistance zone", "Hybrid ATR + S/R"], group = G_SR)
entryRangeAtrMult = input.float(0.20, "Entry range ATR multiplier", minval = 0.01, maxval = 5.0, step = 0.01, group = G_SR)

// === REGIME INPUTS ===
maType   = input.string("EMA", "Trend MA type", options = ["EMA", "SMA", "WMA", "HMA", "DEMA", "TEMA", "VWMA", "RMA", "ZLEMA"], group = G_REGIME)
trendLen = input.int(200, "Trend MA length", minval = 20, maxval = 1000, group = G_REGIME)
trendFilter = input.string("Chart + HTF", "Regime filter", options = ["Off", "Chart MA", "Chart + HTF"], group = G_REGIME)
htfTf = input.timeframe("240", "Higher timeframe", group = G_REGIME)
nonRepaintHTF = input.bool(true, "Use confirmed HTF values", group = G_REGIME)

// === RSI INPUTS ===
rsiSrc = input.source(close, "RSI source", group = G_RSI)
rsiLen = input.int(10, "RSI length", minval = 2, maxval = 100, group = G_RSI)
rsiOS = input.float(19.0, "Oversold", minval = 1, maxval = 50, step = 0.5, group = G_RSI)
rsiOB = input.float(80.0, "Overbought", minval = 50, maxval = 99, step = 0.5, group = G_RSI)
rsiSetupLookback = input.int(5, "RSI setup lookback bars", minval = 1, maxval = 50, group = G_RSI)

// === FIBONACCI BB INPUTS ===
bbLen = input.int(200, "BB length", minval = 10, maxval = 1000, group = G_BB)
bbSrc = input.source(hlc3, "BB source", group = G_BB)
bbMult = input.float(3.0, "Deviation multiplier", minval = 0.1, maxval = 50, step = 0.1, group = G_BB)
bbBasisType = input.string("VWMA", "BB basis type", options = ["EMA", "SMA", "WMA", "HMA", "DEMA", "TEMA", "VWMA", "RMA", "ZLEMA"], group = G_BB)
bbSetupLookback = input.int(5, "FibBB setup lookback bars", minval = 1, maxval = 50, group = G_BB)

// === ICHIMOKU INPUTS ===
conversionPeriods = input.int(9, "Conversion line length", minval = 1, group = G_ICHI)
basePeriods = input.int(26, "Base line length", minval = 1, group = G_ICHI)
spanBPeriods = input.int(52, "Leading Span B length", minval = 1, group = G_ICHI)
displacement = input.int(26, "Displacement", minval = 1, group = G_ICHI)

// === CANDLESTICK PATTERN INPUTS ===
showCandlePatterns = input.bool(true, "Show candlestick pattern labels", group = G_CANDLE)
candleSetupLookback = input.int(5, "Candle setup lookback bars", minval = 1, maxval = 50, group = G_CANDLE)
use382Candle = input.bool(true, "Detect 38.2 pressure candles", group = G_CANDLE)
useEngulfing = input.bool(true, "Detect engulfing candles", group = G_CANDLE)
useCloseBreak = input.bool(true, "Detect close above/below candles", group = G_CANDLE)
useWickReject = input.bool(true, "Detect wick rejection candles", group = G_CANDLE)
wickToBodyMin = input.float(1.5, "Wick rejection min wick:body", minval = 0.1, step = 0.1, group = G_CANDLE)

// === CHART / BREAKOUT PATTERN INPUTS ===
showChartPatterns = input.bool(true, "Show chart/breakout pattern labels", group = G_PATTERN)
pivotLeft = input.int(3, "Pattern pivot left bars", minval = 1, maxval = 20, group = G_PATTERN)
pivotRight = input.int(3, "Pattern pivot right bars", minval = 1, maxval = 20, group = G_PATTERN)
doubleTolerancePct = input.float(1.0, "Double top/bottom tolerance %", minval = 0.05, maxval = 10.0, step = 0.05, group = G_PATTERN)
patternLookback = input.int(40, "Pattern lookback bars", minval = 10, maxval = 200, group = G_PATTERN)
flagLen = input.int(12, "Flag consolidation length", minval = 5, maxval = 50, group = G_PATTERN)
flagMaxRangePct = input.float(8.0, "Flag max consolidation range %", minval = 0.1, maxval = 50.0, step = 0.1, group = G_PATTERN)

// === VOLUME + VOLATILITY FILTERS ===
useVolumeFilter = input.bool(true, "Require volume expansion", group = G_FILTERS)
volumeLen = input.int(20, "Volume SMA length", minval = 1, group = G_FILTERS)
volumeMult = input.float(0.80, "Min volume x SMA", minval = 0.0, step = 0.05, group = G_FILTERS)
useAtrFilter = input.bool(true, "Filter abnormal volatility", group = G_FILTERS)
minAtrPct = input.float(0.20, "Min ATR %", minval = 0.0, step = 0.05, group = G_FILTERS)
maxAtrPct = input.float(12.0, "Max ATR %", minval = 0.1, step = 0.25, group = G_FILTERS)

// === RISK INPUTS ===
riskModel = input.string("Tighter of % / ATR", "Stop model", options = ["Percent", "ATR", "Tighter of % / ATR", "Wider of % / ATR"], group = G_RISK)
freezeRiskAtEntry = input.bool(true, "Freeze ATR risk at entry", group = G_RISK)
stopPct = input.float(1.2, "Stop loss %", minval = 0.05, step = 0.05, group = G_RISK)
atrLen = input.int(14, "ATR length", minval = 1, group = G_RISK)
atrStopMult = input.float(1.5, "ATR stop multiplier", minval = 0.1, step = 0.1, group = G_RISK)
useTakeProfit = input.bool(true, "Use take profit", group = G_RISK)
rewardRisk = input.float(1.5, "Reward:risk target", minval = 0.1, step = 0.1, group = G_RISK)
useBreakeven = input.bool(true, "Move stop to breakeven", group = G_RISK)
breakevenAtR = input.float(1.0, "Breakeven trigger in R", minval = 0.1, step = 0.1, group = G_RISK)
breakevenOffsetPct = input.float(0.03, "Breakeven offset %", minval = 0.0, step = 0.01, group = G_RISK)
useMATrailingStop = input.bool(false, "Use trend MA as trailing stop", group = G_RISK)
exitOnOppositeScore = input.bool(true, "Exit on opposite weighted score", group = G_RISK)
exitOnDateEnd = input.bool(true, "Close at selected date end", group = G_RISK)

// === VISUAL INPUTS ===
showMA = input.bool(true, "Show trend MA when active", group = G_VIS)
showHTFMA = input.bool(false, "Show HTF trend MA when active", group = G_VIS)
showFibBB = input.bool(true, "Show Fibonacci BB when active", group = G_VIS)
showFibFill = input.bool(true, "Fill Fibonacci BB envelope when active", group = G_VIS)
showIchimoku = input.bool(true, "Show Ichimoku when active", group = G_VIS)
showSignals = input.bool(true, "Show entry/exit labels", group = G_VIS)
showRiskLines = input.bool(true, "Show active stop/target", group = G_VIS)
showEntryRangeBoxes = input.bool(true, "Show entry price range boxes", group = G_VIS)
showTradeZoneBoxes = input.bool(true, "Show active green/red trade zones", group = G_VIS)
showTradeOutcomeLabels = input.bool(true, "Show WIN/FAILED outcome labels", group = G_VIS)
showRegimeBg = input.bool(true, "Show regime background", group = G_VIS)
colorBars = input.bool(false, "Color bars by dominant score", group = G_VIS)
showDashboard = input.bool(true, "Show dashboard", group = G_VIS)
showScorePlots = input.bool(false, "Show scores in Data Window", group = G_VIS)

// === ALERT INPUTS ===
botId = input.string("230050", "Bot ID", group = G_ALERT)
includeScoresInAlerts = input.bool(true, "Include scores in alert JSON", group = G_ALERT)

// === FUNCTIONS ===
f_dema(series float src, simple int length) =>
    float ema1 = ta.ema(src, length)
    float ema2 = ta.ema(ema1, length)
    2.0 * ema1 - ema2

f_tema(series float src, simple int length) =>
    float ema1 = ta.ema(src, length)
    float ema2 = ta.ema(ema1, length)
    float ema3 = ta.ema(ema2, length)
    3.0 * (ema1 - ema2) + ema3

f_zlema(series float src, simple int length) =>
    int lag = math.max(1, int(math.floor((length - 1) / 2)))
    ta.ema(src + (src - nz(src[lag], src)), length)

f_ma(series float src, simple int length, simple string kind) =>
    switch kind
        "EMA"   => ta.ema(src, length)
        "SMA"   => ta.sma(src, length)
        "WMA"   => ta.wma(src, length)
        "HMA"   => ta.hma(src, length)
        "DEMA"  => f_dema(src, length)
        "TEMA"  => f_tema(src, length)
        "VWMA"  => ta.vwma(src, length)
        "RMA"   => ta.rma(src, length)
        "ZLEMA" => f_zlema(src, length)
        => ta.ema(src, length)

f_donchian(simple int length) =>
    math.avg(ta.lowest(low, length), ta.highest(high, length))

f_recent(bool condition, simple int lookback) =>
    int bars = ta.barssince(condition)
    not na(bars) and bars <= lookback

f_in_window(int left, int right) =>
    time >= left and time <= right

f_weighted_pct(float raw, float weight) =>
    weight > 0.0 ? math.max(0.0, math.min(100.0, 100.0 * raw / weight)) : 0.0

f_json(string action, float longScore, float shortScore) =>
    string scorePart = includeScoresInAlerts ? ',"longScore":' + str.tostring(longScore, "#.##") + ',"shortScore":' + str.tostring(shortScore, "#.##") : ""
    '{"botId":"' + botId + '","action":"' + action + '","symbol":"' + syminfo.ticker + '","price":' + str.tostring(close, format.mintick) + scorePart + '}'

f_add_box(array<box> arr, box b, int maxCount) =>
    array.push(arr, b)
    if array.size(arr) > maxCount
        box.delete(array.shift(arr))

f_fmt(float value) =>
    str.tostring(value, format.mintick)

// === RESEARCH PRESET MODULE GATES ===
customPreset = researchPreset == "Custom toggles"
activeRSI = enableRSI and (customPreset or researchPreset == "01 RSI only" or researchPreset == "02 RSI + S/R" or researchPreset == "03 Add FibBB" or researchPreset == "04 Add Trend/HTF" or researchPreset == "05 Add Ichimoku" or researchPreset == "06 Add Candles" or researchPreset == "07 Add Chart Patterns" or researchPreset == "08 Full weighted")
activeSR = enableSR and (customPreset or researchPreset == "02 RSI + S/R" or researchPreset == "03 Add FibBB" or researchPreset == "04 Add Trend/HTF" or researchPreset == "05 Add Ichimoku" or researchPreset == "06 Add Candles" or researchPreset == "07 Add Chart Patterns" or researchPreset == "08 Full weighted")
activeFibBB = enableFibBB and (customPreset or researchPreset == "03 Add FibBB" or researchPreset == "04 Add Trend/HTF" or researchPreset == "05 Add Ichimoku" or researchPreset == "06 Add Candles" or researchPreset == "07 Add Chart Patterns" or researchPreset == "08 Full weighted")
activeTrendMA = enableTrendMA and (customPreset or researchPreset == "04 Add Trend/HTF" or researchPreset == "05 Add Ichimoku" or researchPreset == "06 Add Candles" or researchPreset == "07 Add Chart Patterns" or researchPreset == "08 Full weighted")
activeIchimoku = enableIchimoku and (customPreset or researchPreset == "05 Add Ichimoku" or researchPreset == "06 Add Candles" or researchPreset == "07 Add Chart Patterns" or researchPreset == "08 Full weighted")
activeCandles = enableCandles and (customPreset or researchPreset == "06 Add Candles" or researchPreset == "07 Add Chart Patterns" or researchPreset == "08 Full weighted")
activePatterns = enablePatterns and (customPreset or researchPreset == "07 Add Chart Patterns" or researchPreset == "08 Full weighted")

// === EXECUTION WINDOWS ===
inBacktestDateRange = f_in_window(startTime, endTime)
inInSample = f_in_window(inSampleStart, inSampleEnd)
inOOS = f_in_window(oosStart, oosEnd)
inPaper = f_in_window(paperStart, paperEnd)
segmentOK = validationSegment == "Full selected range" ? inBacktestDateRange : validationSegment == "In-sample only" ? inInSample : validationSegment == "Out-of-sample only" ? inOOS : inPaper
// Session semantics: sessionTimezone must match the exchange/clock you intend to gate on.
// For 24x7 crypto use "Etc/UTC" (default) with session "0000-0000:1234567" (always active), or define
// a specific UTC window (e.g. "0800-2000:1234567" for a US/EU overlap). TradingView evaluates the
// session in the given timezone; mismatched timezones will silently shift the session window.
inSession = not useSessionFilter or not na(time(timeframe.period, tradeSession, sessionTimezone))
signalGate = not confirmedOnly or barstate.isconfirmed
flat = strategy.position_size == 0
allowShorts = tradeMode == "Derivatives" and tradeDirection == "Long + Short"
canOpenTrade = segmentOK and inSession and signalGate

// === CORE CALCULATIONS ===
// HTF confirmation semantics:
//   htfMARepainting  — lookahead_off, current HTF bar's live value; updates intra-bar (repaints on replay).
//   htfMANonRepaint  — lookahead_on + [1] offset; accesses prior *confirmed* HTF close; no intra-bar repaint.
//   nonRepaintHTF toggle (default true) selects the confirmed variant. Set false only to compare bar-by-bar
//   behaviour in replay mode; never use repainting mode for signal decisions that reach live orders.
trendMA = f_ma(close, trendLen, maType)
htfMARepainting = request.security(syminfo.tickerid, htfTf, f_ma(close, trendLen, maType), barmerge.gaps_off, barmerge.lookahead_off)
htfMANonRepaint = request.security(syminfo.tickerid, htfTf, f_ma(close, trendLen, maType)[1], barmerge.gaps_off, barmerge.lookahead_on)
htfMA = nonRepaintHTF ? htfMANonRepaint : htfMARepainting

trendBull = close > trendMA and trendMA >= trendMA[1]
trendBear = close < trendMA and trendMA <= trendMA[1]
htfBull = close > htfMA and htfMA >= htfMA[1]
htfBear = close < htfMA and htfMA <= htfMA[1]
longRegimeOK = not activeTrendMA or trendFilter == "Off" or (trendBull and (trendFilter != "Chart + HTF" or htfBull))
shortRegimeOK = not activeTrendMA or trendFilter == "Off" or (trendBear and (trendFilter != "Chart + HTF" or htfBear))

rsi = ta.rsi(rsiSrc, rsiLen)
rsiMid = math.avg(rsiOS, rsiOB)

basis = f_ma(bbSrc, bbLen, bbBasisType)
dev = bbMult * ta.stdev(bbSrc, bbLen)
upper1 = basis + 0.236 * dev
upper2 = basis + 0.382 * dev
upper3 = basis + 0.500 * dev
upper4 = basis + 0.618 * dev
upper5 = basis + 0.764 * dev
upper6 = basis + 1.000 * dev
lower1 = basis - 0.236 * dev
lower2 = basis - 0.382 * dev
lower3 = basis - 0.500 * dev
lower4 = basis - 0.618 * dev
lower5 = basis - 0.764 * dev
lower6 = basis - 1.000 * dev

atr = ta.atr(atrLen)
atrPct = close == 0.0 ? na : atr / close * 100.0
volumeSma = ta.sma(volume, volumeLen)
volumeOK = not useVolumeFilter or volume >= volumeSma * volumeMult
volatilityOK = not useAtrFilter or (atrPct >= minAtrPct and atrPct <= maxAtrPct)
filtersOK = volumeOK and volatilityOK

// === SUPPORT / RESISTANCE ENGINE ===
srPivotLow = ta.pivotlow(low, srPivotLeft, srPivotRight)
srPivotHigh = ta.pivothigh(high, srPivotLeft, srPivotRight)
srBuffer = math.max(atr * srAtrBufferMult, syminfo.mintick)
var float lastSupportTop = na
var float lastSupportBottom = na
var float lastResistanceTop = na
var float lastResistanceBottom = na
var int lastSupportBar = na
var int lastResistanceBar = na
var array<box> supportBoxes = array.new<box>()
var array<box> resistanceBoxes = array.new<box>()

if activeSR and not na(srPivotLow)
    float zMid = srPivotLow
    lastSupportTop := zMid + srBuffer
    lastSupportBottom := zMid - srBuffer
    lastSupportBar := bar_index - srPivotRight
    if showSRZones
        box b = box.new(lastSupportBar, lastSupportTop, bar_index + srExtendBars, lastSupportBottom, border_color = color.new(color.green, 20), bgcolor = color.new(color.green, 88), text = "SUPPORT", text_color = color.new(color.green, 0), text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center)
        f_add_box(supportBoxes, b, srMaxZones)

if activeSR and not na(srPivotHigh)
    float zMid = srPivotHigh
    lastResistanceTop := zMid + srBuffer
    lastResistanceBottom := zMid - srBuffer
    lastResistanceBar := bar_index - srPivotRight
    if showSRZones
        box b = box.new(lastResistanceBar, lastResistanceTop, bar_index + srExtendBars, lastResistanceBottom, border_color = color.new(color.red, 20), bgcolor = color.new(color.red, 88), text = "RESIST", text_color = color.new(color.red, 0), text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center)
        f_add_box(resistanceBoxes, b, srMaxZones)

if activeSR and showSRZones
    if array.size(supportBoxes) > 0
        for i = 0 to array.size(supportBoxes) - 1
            box.set_right(array.get(supportBoxes, i), bar_index + srExtendBars)
    if array.size(resistanceBoxes) > 0
        for i = 0 to array.size(resistanceBoxes) - 1
            box.set_right(array.get(resistanceBoxes, i), bar_index + srExtendBars)

nearSupport = activeSR and not na(lastSupportTop) and low <= lastSupportTop and high >= lastSupportBottom
nearResistance = activeSR and not na(lastResistanceBottom) and high >= lastResistanceBottom and low <= lastResistanceTop
supportHold = nearSupport and close >= lastSupportBottom
resistanceHold = nearResistance and close <= lastResistanceTop
// S/R break detection: close-based requires a candle body close outside the zone; wick-based fires on
// any intra-bar penetration. Close-based is more conservative and reduces false-break exits on wicks.
srSupportBreak    = srBreakMode == "Wick below zone" ? low  < lastSupportBottom  : close < lastSupportBottom
srResistanceBreak = srBreakMode == "Wick below zone" ? high > lastResistanceTop  : close > lastResistanceTop
supportFail    = activeSR and not na(lastSupportBottom) and srSupportBreak
resistanceFail = activeSR and not na(lastResistanceTop) and srResistanceBreak
srLongVote = supportHold ? 1.0 : nearSupport ? 0.50 : 0.0
srShortVote = resistanceHold ? 1.0 : nearResistance ? 0.50 : 0.0
srExitLongVote = supportFail ? 1.0 : nearResistance ? 0.40 : 0.0
srExitShortVote = resistanceFail ? 1.0 : nearSupport ? 0.40 : 0.0
srContextOKLong = not requireSRContext or supportHold or nearSupport
srContextOKShort = not requireSRContext or resistanceHold or nearResistance

// === ICHIMOKU ===
conversionLine = f_donchian(conversionPeriods)
baseLine = f_donchian(basePeriods)
leadLine1 = math.avg(conversionLine, baseLine)
leadLine2 = f_donchian(spanBPeriods)
ichiBull = close > math.max(leadLine1, leadLine2) and conversionLine > baseLine
ichiBear = close < math.min(leadLine1, leadLine2) and conversionLine < baseLine

// === RSI SETUPS / CONFIRMATIONS ===
// Historical functions are evaluated on every bar first, then gated.
// This avoids Pine consistency warnings from conditional historical calls.
rsiRecentLongRaw = f_recent(rsi <= rsiOS, rsiSetupLookback)
rsiRecentShortRaw = f_recent(rsi >= rsiOB, rsiSetupLookback)
rsiCrossOverOSRaw = ta.crossover(rsi, rsiOS)
rsiCrossUnderOBRaw = ta.crossunder(rsi, rsiOB)
recentLongRSI = activeRSI and rsiRecentLongRaw
recentShortRSI = activeRSI and rsiRecentShortRaw
longRSIReclaim = activeRSI and rsiCrossOverOSRaw
shortRSIReclaim = activeRSI and rsiCrossUnderOBRaw
rsiLongVote = longRSIReclaim ? 1.0 : recentLongRSI and rsi < rsiMid ? 0.50 : 0.0
rsiShortVote = shortRSIReclaim ? 1.0 : recentShortRSI and rsi > rsiMid ? 0.50 : 0.0
rsiExitLongVote = activeRSI and rsi >= rsiOB ? 1.0 : activeRSI and rsi > rsiMid ? 0.35 : 0.0
rsiExitShortVote = activeRSI and rsi <= rsiOS ? 1.0 : activeRSI and rsi < rsiMid ? 0.35 : 0.0

// === FIB BB SETUPS / CONFIRMATIONS ===
bbRecentLongRaw = f_recent(low <= lower6, bbSetupLookback)
bbRecentShortRaw = f_recent(high >= upper6, bbSetupLookback)
bbLongReclaimRaw = ta.crossover(close, lower5)
bbShortReclaimRaw = ta.crossunder(close, upper5)
bbCloseCrossUnderBasisRaw = ta.crossunder(close, basis)
bbCloseCrossOverBasisRaw = ta.crossover(close, basis)
recentLongBB = activeFibBB and bbRecentLongRaw
recentShortBB = activeFibBB and bbRecentShortRaw
longBBReclaim = activeFibBB and bbLongReclaimRaw
shortBBReclaim = activeFibBB and bbShortReclaimRaw
closeCrossUnderBasis = activeFibBB and bbCloseCrossUnderBasisRaw
closeCrossOverBasis = activeFibBB and bbCloseCrossOverBasisRaw
fibBBLongVote = longBBReclaim ? 1.0 : recentLongBB and close > lower6 ? 0.50 : 0.0
fibBBShortVote = shortBBReclaim ? 1.0 : recentShortBB and close < upper6 ? 0.50 : 0.0
fibBBExitLongVote = closeCrossUnderBasis ? 1.0 : activeFibBB and high >= upper6 ? 0.60 : 0.0
fibBBExitShortVote = closeCrossOverBasis ? 1.0 : activeFibBB and low <= lower6 ? 0.60 : 0.0

// === TREND / ICHIMOKU VOTES ===
trendLongVote = activeTrendMA ? longRegimeOK ? 1.0 : 0.0 : 0.0
trendShortVote = activeTrendMA ? shortRegimeOK ? 1.0 : 0.0 : 0.0
trendExitLongVote = activeTrendMA and trendBear ? 1.0 : 0.0
trendExitShortVote = activeTrendMA and trendBull ? 1.0 : 0.0
ichiLongVote = activeIchimoku ? ichiBull ? 1.0 : 0.0 : 0.0
ichiShortVote = activeIchimoku ? ichiBear ? 1.0 : 0.0 : 0.0
ichiExitLongVote = activeIchimoku and ichiBear ? 1.0 : 0.0
ichiExitShortVote = activeIchimoku and ichiBull ? 1.0 : 0.0

// === CANDLESTICK PATTERN ENGINE ===
// Pattern definitions (explicit for Rust parity):
//   bull382: bullish close AND body bottom ≥ low + 38.2% of candle range (pressure candle — body
//     sits in the upper portion; lower wick absorbs selling). bear382: inverse.
//   bullEngulf: current body (close-open) > prior body (close[1]-open[1]) AND current bar closes
//     above prior bar's high (full body engulfing). Does not require prior bar to be bearish.
//   bullCloseBreak: close strictly above prior bar's high (candle body close-based breakout).
//   bullWickReject: lower wick ≥ wickToBodyMin × body size AND close above body low
//     (rejection wick off low; wickToBodyMin default 1.5 means wick is 150% of body).
//   safeBody clamps to mintick to avoid division by zero on doji candles.
bodyHigh = math.max(open, close)
bodyLow = math.min(open, close)
bodySize = math.abs(close - open)
prevBodySize = math.abs(close[1] - open[1])
candleRange = high - low
safeBody = math.max(bodySize, syminfo.mintick)
upperWick = high - bodyHigh
lowerWick = bodyLow - low
bull382 = activeCandles and use382Candle and candleRange > syminfo.mintick and bodyLow >= low + candleRange * 0.382 and close > open
bear382 = activeCandles and use382Candle and candleRange > syminfo.mintick and bodyHigh <= high - candleRange * 0.382 and close < open
bullEngulf = activeCandles and useEngulfing and close > open and close[1] < open[1] and bodySize > prevBodySize and bodyHigh >= math.max(open[1], close[1])
bearEngulf = activeCandles and useEngulfing and close < open and close[1] > open[1] and bodySize > prevBodySize and bodyLow <= math.min(open[1], close[1])
bullCloseBreak = activeCandles and useCloseBreak and close > high[1]
bearCloseBreak = activeCandles and useCloseBreak and close < low[1]
bullWickReject = activeCandles and useWickReject and lowerWick / safeBody >= wickToBodyMin and close > bodyLow
bearWickReject = activeCandles and useWickReject and upperWick / safeBody >= wickToBodyMin and close < bodyHigh
bullCandleEvent = bull382 or bullEngulf or bullCloseBreak or bullWickReject
bearCandleEvent = bear382 or bearEngulf or bearCloseBreak or bearWickReject
candleRecentBullRaw = f_recent(bullCandleEvent, candleSetupLookback)
candleRecentBearRaw = f_recent(bearCandleEvent, candleSetupLookback)
recentBullCandle = activeCandles and candleRecentBullRaw
recentBearCandle = activeCandles and candleRecentBearRaw
candleLongVote = bullCandleEvent ? 1.0 : recentBullCandle ? 0.50 : 0.0
candleShortVote = bearCandleEvent ? 1.0 : recentBearCandle ? 0.50 : 0.0
candleExitLongVote = bearCandleEvent ? 1.0 : recentBearCandle ? 0.40 : 0.0
candleExitShortVote = bullCandleEvent ? 1.0 : recentBullCandle ? 0.40 : 0.0

// === CHART / BREAKOUT PATTERN ENGINE ===
pivotLow = ta.pivotlow(low, pivotLeft, pivotRight)
pivotHigh = ta.pivothigh(high, pivotLeft, pivotRight)
var float prevPivotLow = na
var float lastPivotLow = na
var float prevPivotHigh = na
var float lastPivotHigh = na
if not na(pivotLow)
    prevPivotLow := lastPivotLow
    lastPivotLow := pivotLow
if not na(pivotHigh)
    prevPivotHigh := lastPivotHigh
    lastPivotHigh := pivotHigh

haveTwoLows = not na(prevPivotLow) and not na(lastPivotLow)
haveTwoHighs = not na(prevPivotHigh) and not na(lastPivotHigh)
nearDoubleLow = haveTwoLows and math.abs(lastPivotLow - prevPivotLow) / close * 100.0 <= doubleTolerancePct
nearDoubleHigh = haveTwoHighs and math.abs(lastPivotHigh - prevPivotHigh) / close * 100.0 <= doubleTolerancePct
necklineLong = ta.highest(high, patternLookback)
necklineShort = ta.lowest(low, patternLookback)
patternHighestPrev = ta.highest(high[1], patternLookback)
patternLowestPrev = ta.lowest(low[1], patternLookback)
doubleBottomCrossRaw = ta.crossover(close, necklineLong[1])
doubleTopCrossRaw = ta.crossunder(close, necklineShort[1])
doubleBottomBreak = activePatterns and nearDoubleLow and doubleBottomCrossRaw
doubleTopBreak = activePatterns and nearDoubleHigh and doubleTopCrossRaw
flagHigh = ta.highest(high[1], flagLen)
flagLow = ta.lowest(low[1], flagLen)
flagRangePct = close == 0.0 ? na : (flagHigh - flagLow) / close * 100.0
bullFlagBreak = activePatterns and flagRangePct <= flagMaxRangePct and close > flagHigh and close > trendMA
bearFlagBreak = activePatterns and flagRangePct <= flagMaxRangePct and close < flagLow and close < trendMA
risingSupport = activePatterns and haveTwoLows and lastPivotLow > prevPivotLow
fallingResistance = activePatterns and haveTwoHighs and lastPivotHigh < prevPivotHigh
ascendingBreak = activePatterns and risingSupport and close > patternHighestPrev
descendingBreak = activePatterns and fallingResistance and close < patternLowestPrev
bullChartEvent = doubleBottomBreak or bullFlagBreak or ascendingBreak
bearChartEvent = doubleTopBreak or bearFlagBreak or descendingBreak
chartRecentBullRaw = f_recent(bullChartEvent, candleSetupLookback)
chartRecentBearRaw = f_recent(bearChartEvent, candleSetupLookback)
recentBullChart = activePatterns and chartRecentBullRaw
recentBearChart = activePatterns and chartRecentBearRaw
patternLongVote = bullChartEvent ? 1.0 : recentBullChart ? 0.50 : 0.0
patternShortVote = bearChartEvent ? 1.0 : recentBearChart ? 0.50 : 0.0
patternExitLongVote = bearChartEvent ? 1.0 : recentBearChart ? 0.40 : 0.0
patternExitShortVote = bullChartEvent ? 1.0 : recentBullChart ? 0.40 : 0.0

// === WEIGHTED SCORE AGGREGATION ===
float longRaw = 0.0
float shortRaw = 0.0
float longExitRaw = 0.0
float shortExitRaw = 0.0
float entryWeightTotal = 0.0
float exitWeightTotal = 0.0

if activeRSI and useRSIForEntry and rsiEntryWeight > 0
    entryWeightTotal += rsiEntryWeight
    longRaw += rsiEntryWeight * rsiLongVote
    shortRaw += rsiEntryWeight * rsiShortVote
if activeSR and useSRForEntry and srEntryWeight > 0
    entryWeightTotal += srEntryWeight
    longRaw += srEntryWeight * srLongVote
    shortRaw += srEntryWeight * srShortVote
if activeFibBB and useFibBBForEntry and fibBBEntryWeight > 0
    entryWeightTotal += fibBBEntryWeight
    longRaw += fibBBEntryWeight * fibBBLongVote
    shortRaw += fibBBEntryWeight * fibBBShortVote
if activeTrendMA and useTrendForEntry and trendEntryWeight > 0
    entryWeightTotal += trendEntryWeight
    longRaw += trendEntryWeight * trendLongVote
    shortRaw += trendEntryWeight * trendShortVote
if activeIchimoku and useIchiForEntry and ichiEntryWeight > 0
    entryWeightTotal += ichiEntryWeight
    longRaw += ichiEntryWeight * ichiLongVote
    shortRaw += ichiEntryWeight * ichiShortVote
if activeCandles and useCandlesForEntry and candleEntryWeight > 0
    entryWeightTotal += candleEntryWeight
    longRaw += candleEntryWeight * candleLongVote
    shortRaw += candleEntryWeight * candleShortVote
if activePatterns and usePatternsForEntry and patternEntryWeight > 0
    entryWeightTotal += patternEntryWeight
    longRaw += patternEntryWeight * patternLongVote
    shortRaw += patternEntryWeight * patternShortVote

if activeRSI and useRSIForExit and rsiExitWeight > 0
    exitWeightTotal += rsiExitWeight
    longExitRaw += rsiExitWeight * rsiExitLongVote
    shortExitRaw += rsiExitWeight * rsiExitShortVote
if activeSR and useSRForExit and srExitWeight > 0
    exitWeightTotal += srExitWeight
    longExitRaw += srExitWeight * srExitLongVote
    shortExitRaw += srExitWeight * srExitShortVote
if activeFibBB and useFibBBForExit and fibBBExitWeight > 0
    exitWeightTotal += fibBBExitWeight
    longExitRaw += fibBBExitWeight * fibBBExitLongVote
    shortExitRaw += fibBBExitWeight * fibBBExitShortVote
if activeTrendMA and useTrendForExit and trendExitWeight > 0
    exitWeightTotal += trendExitWeight
    longExitRaw += trendExitWeight * trendExitLongVote
    shortExitRaw += trendExitWeight * trendExitShortVote
if activeIchimoku and useIchiForExit and ichiExitWeight > 0
    exitWeightTotal += ichiExitWeight
    longExitRaw += ichiExitWeight * ichiExitLongVote
    shortExitRaw += ichiExitWeight * ichiExitShortVote
if activeCandles and useCandlesForExit and candleExitWeight > 0
    exitWeightTotal += candleExitWeight
    longExitRaw += candleExitWeight * candleExitLongVote
    shortExitRaw += candleExitWeight * candleExitShortVote
if activePatterns and usePatternsForExit and patternExitWeight > 0
    exitWeightTotal += patternExitWeight
    longExitRaw += patternExitWeight * patternExitLongVote
    shortExitRaw += patternExitWeight * patternExitShortVote

longScore = f_weighted_pct(longRaw, entryWeightTotal)
shortScore = f_weighted_pct(shortRaw, entryWeightTotal)
longExitScore = f_weighted_pct(longExitRaw, exitWeightTotal)
shortExitScore = f_weighted_pct(shortExitRaw, exitWeightTotal)

primaryLongSetup = recentLongRSI or recentLongBB or bullCandleEvent or bullChartEvent or supportHold
primaryShortSetup = recentShortRSI or recentShortBB or bearCandleEvent or bearChartEvent or resistanceHold
primaryLongOK = not requirePrimarySetup or primaryLongSetup
primaryShortOK = not requirePrimarySetup or primaryShortSetup

longSignal = canOpenTrade and flat and filtersOK and longRegimeOK and srContextOKLong and longScore >= entryThreshold and primaryLongOK
shortSignal = canOpenTrade and flat and allowShorts and filtersOK and shortRegimeOK and srContextOKShort and shortScore >= entryThreshold and primaryShortOK
longScoreExit = exitOnOppositeScore and strategy.position_size > 0 and shortExitScore >= exitThreshold
shortScoreExit = exitOnOppositeScore and strategy.position_size < 0 and longExitScore >= exitThreshold

// === ENTRY RANGE VISUALS ===
atrEntryHalf = math.max(atr * entryRangeAtrMult, syminfo.mintick)
longSRRangeTop = not na(lastSupportTop) ? lastSupportTop : close + atrEntryHalf
longSRRangeBottom = not na(lastSupportBottom) ? lastSupportBottom : close - atrEntryHalf
shortSRRangeTop = not na(lastResistanceTop) ? lastResistanceTop : close + atrEntryHalf
shortSRRangeBottom = not na(lastResistanceBottom) ? lastResistanceBottom : close - atrEntryHalf
longEntryRangeTop = entryRangeMode == "ATR around signal close" ? close + atrEntryHalf : entryRangeMode == "Nearest support/resistance zone" ? longSRRangeTop : math.max(close + atrEntryHalf, longSRRangeTop)
longEntryRangeBottom = entryRangeMode == "ATR around signal close" ? close - atrEntryHalf : entryRangeMode == "Nearest support/resistance zone" ? longSRRangeBottom : math.min(close - atrEntryHalf, longSRRangeBottom)
shortEntryRangeTop = entryRangeMode == "ATR around signal close" ? close + atrEntryHalf : entryRangeMode == "Nearest support/resistance zone" ? shortSRRangeTop : math.max(close + atrEntryHalf, shortSRRangeTop)
shortEntryRangeBottom = entryRangeMode == "ATR around signal close" ? close - atrEntryHalf : entryRangeMode == "Nearest support/resistance zone" ? shortSRRangeBottom : math.min(close - atrEntryHalf, shortSRRangeBottom)
var array<box> entryRangeBoxes = array.new<box>()
if showEntryRangeBoxes and longSignal
    box b = box.new(bar_index, longEntryRangeTop, bar_index + maxBarsInTrade, longEntryRangeBottom, border_color = color.new(color.teal, 0), bgcolor = color.new(color.teal, 86), text = "LONG ENTRY RANGE", text_color = color.white, text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center)
    f_add_box(entryRangeBoxes, b, 40)
if showEntryRangeBoxes and shortSignal
    box b = box.new(bar_index, shortEntryRangeTop, bar_index + maxBarsInTrade, shortEntryRangeBottom, border_color = color.new(color.purple, 0), bgcolor = color.new(color.purple, 86), text = "SHORT ENTRY RANGE", text_color = color.white, text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center)
    f_add_box(entryRangeBoxes, b, 40)

// === ALERT STRINGS ===
buyLongMsg = f_json("BUY", longScore, shortScore)
sellLongMsg = f_json("SELL", longScore, shortScore)
sellShortMsg = f_json("SELL_SHORT", longScore, shortScore)
buyCoverMsg = f_json("BUY_TO_COVER", longScore, shortScore)

// === ORDERS ===
// Entry fill semantics: signal fires on bar *close* (gated by barstate.isconfirmed via signalGate).
// TradingView fills strategy.entry at the *open of the next bar* (market order).
// Simulated slippage = strategy() slippage param (default 1 tick).
// Zone-based entry: longSignal requires price to be inside or approaching a support zone; the assumed
// fill is next-bar open, not the zone boundary. Backtests may show fills slightly above/below zone edge.
// Position sizing: researchSizingMode = true uses 100% equity (research ratios); false uses liveQtyPct.
// qty is computed in base-asset units so it works regardless of default_qty_type in strategy().
positionQtyPct = researchSizingMode ? 100.0 : liveQtyPct
entryQty = close > 0.0 ? strategy.equity * (positionQtyPct / 100.0) / close : na
if longSignal
    strategy.entry("L", strategy.long, qty = entryQty, alert_message = buyLongMsg, comment = "Long weighted S/R day trade")
if shortSignal
    strategy.entry("S", strategy.short, qty = entryQty, alert_message = sellShortMsg, comment = "Short weighted S/R day trade")

// === RISK ENGINE ===
// Freeze semantics (requires freezeRiskAtEntry = true):
//   entryAtr   — ATR value captured on the entry bar; used for all ATR-derived stop/target math.
//   frozenStop — base stop price computed on the entry bar (before breakeven adjustments); zone does not drift.
//   frozenTarget — target price computed on the entry bar; zone does not drift.
// Breakeven adjustments (longBEActive/shortBEActive) operate on top of the live exit path and are
// intentionally NOT frozen — they represent a separate runtime risk control layer.
// All three are reset to na when the position closes so the next trade starts clean.
var float entryAtr = na
var float frozenStop = na
var float frozenTarget = na
var int entryBarIndex = na
var int entryTimeMs = na
var string entrySide = ""
newLongPosition = strategy.position_size > 0 and strategy.position_size[1] <= 0
newShortPosition = strategy.position_size < 0 and strategy.position_size[1] >= 0
newPosition = newLongPosition or newShortPosition
closedPosition = strategy.position_size == 0 and strategy.position_size[1] != 0

if newPosition
    entryAtr := atr
    entryBarIndex := bar_index
    entryTimeMs := time
    entrySide := newLongPosition ? "LONG" : "SHORT"
if closedPosition
    entryAtr := na
    frozenStop := na
    frozenTarget := na

riskAtr = freezeRiskAtEntry ? nz(entryAtr, atr) : atr
avg = strategy.position_avg_price
longPctStop = avg * (1.0 - stopPct / 100.0)
longAtrStop = avg - riskAtr * atrStopMult
longStopBaseRaw = switch riskModel
    "Percent"          => longPctStop
    "ATR"              => longAtrStop
    "Wider of % / ATR" => math.min(longPctStop, longAtrStop)
    => math.max(longPctStop, longAtrStop)
longStopBase = useMATrailingStop and strategy.position_size > 0 ? math.max(longStopBaseRaw, trendMA) : longStopBaseRaw
longRisk = avg - longStopBase
longTarget = avg + longRisk * rewardRisk
longBEActive = useBreakeven and strategy.position_size > 0 and longRisk > 0 and high >= avg + longRisk * breakevenAtR
longStop = longBEActive ? math.max(longStopBase, avg * (1.0 + breakevenOffsetPct / 100.0)) : longStopBase

shortPctStop = avg * (1.0 + stopPct / 100.0)
shortAtrStop = avg + riskAtr * atrStopMult
shortStopBaseRaw = switch riskModel
    "Percent"          => shortPctStop
    "ATR"              => shortAtrStop
    "Wider of % / ATR" => math.max(shortPctStop, shortAtrStop)
    => math.min(shortPctStop, shortAtrStop)
shortStopBase = useMATrailingStop and strategy.position_size < 0 ? math.min(shortStopBaseRaw, trendMA) : shortStopBaseRaw
shortRisk = shortStopBase - avg
shortTarget = avg - shortRisk * rewardRisk
shortBEActive = useBreakeven and strategy.position_size < 0 and shortRisk > 0 and low <= avg - shortRisk * breakevenAtR
shortStop = shortBEActive ? math.min(shortStopBase, avg * (1.0 - breakevenOffsetPct / 100.0)) : shortStopBase

// Capture base stop and target on entry bar so zone state does not drift if ATR changes mid-trade.
// frozenStop/frozenTarget hold entry-bar levels; actual exit stop can move (e.g. breakeven),
// but the zone reference stays anchored to the original risk-at-entry calculation.
if newLongPosition
    frozenStop := longStopBase
    frozenTarget := longTarget
if newShortPosition
    frozenStop := shortStopBase
    frozenTarget := shortTarget

if strategy.position_size > 0 and longRisk > 0
    strategy.exit("XL Risk", from_entry = "L", stop = longStop, limit = useTakeProfit ? longTarget : na, alert_message = sellLongMsg, comment_profit = "Long TP", comment_loss = "Long SL")
if strategy.position_size < 0 and shortRisk > 0
    strategy.exit("XS Risk", from_entry = "S", stop = shortStop, limit = useTakeProfit ? shortTarget : na, alert_message = buyCoverMsg, comment_profit = "Short TP", comment_loss = "Short SL")

barsInTrade = strategy.position_size != 0 and not na(entryBarIndex) ? bar_index - entryBarIndex : 0
minutesInTrade = strategy.position_size != 0 and not na(entryTimeMs) ? (time - entryTimeMs) / 60000.0 : 0.0
// Timed exit: timedExitMode controls which limit(s) trigger the auto-exit.
// "Bars OR Minutes" (default): fires when either bar count OR elapsed minutes is exceeded — first reached wins.
// "Bars only": ignores elapsed time; useful when bar length is variable (e.g. tick charts).
// "Minutes only": ignores bar count; useful when bar interval is fixed and clock time is the binding constraint.
timedExitCond = timedExitMode == "Bars only" ? barsInTrade >= maxBarsInTrade : timedExitMode == "Minutes only" ? minutesInTrade >= maxMinutesInTrade : (barsInTrade >= maxBarsInTrade or minutesInTrade >= maxMinutesInTrade)
timeExit = enableTimedExit and strategy.position_size != 0 and timedExitCond
sessionExit = closeAtSessionEnd and strategy.position_size != 0 and useSessionFilter and not inSession
dateExit = exitOnDateEnd and strategy.position_size != 0 and not segmentOK
srExitLong = srFailExit and strategy.position_size > 0 and supportFail
srExitShort = srFailExit and strategy.position_size < 0 and resistanceFail

if longScoreExit
    strategy.close("L", alert_message = sellLongMsg, comment = "Long score exit")
if shortScoreExit
    strategy.close("S", alert_message = buyCoverMsg, comment = "Short score exit")
if srExitLong
    strategy.close("L", alert_message = sellLongMsg, comment = "Long S/R failed")
if srExitShort
    strategy.close("S", alert_message = buyCoverMsg, comment = "Short S/R failed")
if timeExit and strategy.position_size > 0
    strategy.close("L", alert_message = sellLongMsg, comment = "Long timed exit")
if timeExit and strategy.position_size < 0
    strategy.close("S", alert_message = buyCoverMsg, comment = "Short timed exit")
if sessionExit and strategy.position_size > 0
    strategy.close("L", alert_message = sellLongMsg, comment = "Long session exit")
if sessionExit and strategy.position_size < 0
    strategy.close("S", alert_message = buyCoverMsg, comment = "Short session exit")
if dateExit and strategy.position_size > 0
    strategy.close("L", alert_message = sellLongMsg, comment = "Long date exit")
if dateExit and strategy.position_size < 0
    strategy.close("S", alert_message = buyCoverMsg, comment = "Short date exit")
if closeOutsideRange and not inBacktestDateRange and strategy.position_size != 0
    strategy.close_all(alert_message = strategy.position_size > 0 ? sellLongMsg : buyCoverMsg, comment = "Outside date range")

// === ACTIVE TRADE ZONE BOXES ===
var box activeProfitBox = na
var box activeLossBox = na
var box activeEntryBox = na
var label activeTimeLabel = na
var float visualEntry = na
var float visualStop = na
var float visualTarget = na

if newPosition and showTradeZoneBoxes
    if not na(activeProfitBox)
        box.delete(activeProfitBox)
    if not na(activeLossBox)
        box.delete(activeLossBox)
    if not na(activeEntryBox)
        box.delete(activeEntryBox)
    if not na(activeTimeLabel)
        label.delete(activeTimeLabel)
    visualEntry := strategy.position_avg_price
    visualStop := newLongPosition ? longStop : shortStop
    visualTarget := newLongPosition ? longTarget : shortTarget
    int plannedRight = bar_index + maxBarsInTrade
    float profitTop = newLongPosition ? visualTarget : visualEntry
    float profitBottom = newLongPosition ? visualEntry : visualTarget
    float lossTop = newLongPosition ? visualEntry : visualStop
    float lossBottom = newLongPosition ? visualStop : visualEntry
    activeProfitBox := box.new(bar_index, profitTop, plannedRight, profitBottom, border_color = color.new(color.green, 0), bgcolor = color.new(color.green, 84), text = "PROFIT ZONE", text_color = color.white, text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center)
    activeLossBox := box.new(bar_index, lossTop, plannedRight, lossBottom, border_color = color.new(color.red, 0), bgcolor = color.new(color.red, 84), text = "LOSS ZONE", text_color = color.white, text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center)
    activeEntryBox := box.new(bar_index, visualEntry + atrEntryHalf, plannedRight, visualEntry - atrEntryHalf, border_color = color.new(color.blue, 0), bgcolor = color.new(color.blue, 88), text = entrySide + " FILL RANGE", text_color = color.white, text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center)
    activeTimeLabel := label.new(bar_index, newLongPosition ? high : low, "Active 0 bars / 0m", style = newLongPosition ? label.style_label_down : label.style_label_up, textcolor = color.white, color = color.new(color.black, 0), size = size.tiny)

if strategy.position_size != 0 and showTradeZoneBoxes and not na(activeProfitBox)
    int plannedRight = nz(entryBarIndex, bar_index) + maxBarsInTrade
    box.set_right(activeProfitBox, plannedRight)
    box.set_right(activeLossBox, plannedRight)
    box.set_right(activeEntryBox, plannedRight)
    // Zone text shows frozen entry-bar levels when freezeRiskAtEntry is on; live levels otherwise.
    float dispTarget = freezeRiskAtEntry and not na(frozenTarget) ? frozenTarget : (strategy.position_size > 0 ? longTarget : shortTarget)
    float dispStop   = freezeRiskAtEntry and not na(frozenStop)   ? frozenStop   : (strategy.position_size > 0 ? longStop   : shortStop)
    box.set_text(activeProfitBox, "PROFIT ZONE\nTP " + f_fmt(dispTarget))
    box.set_text(activeLossBox, "LOSS ZONE\nSL " + f_fmt(dispStop))
    box.set_text(activeEntryBox, entrySide + " ENTRY\n" + f_fmt(strategy.position_avg_price))
    if not na(activeTimeLabel)
        label.set_x(activeTimeLabel, bar_index)
        label.set_y(activeTimeLabel, strategy.position_size > 0 ? high : low)
        label.set_text(activeTimeLabel, "Active " + str.tostring(barsInTrade) + " bars / " + str.tostring(minutesInTrade, "#.0") + "m\nAuto-exit: " + str.tostring(maxBarsInTrade) + " bars or " + str.tostring(maxMinutesInTrade) + "m")

if closedPosition and showTradeOutcomeLabels
    float closedProfit = strategy.closedtrades > 0 ? strategy.closedtrades.profit(strategy.closedtrades - 1) : na
    string outcome = na(closedProfit) ? "CLOSED" : closedProfit > 0 ? "WIN" : "FAILED"
    string reason = timeExit ? "TIME" : sessionExit ? "SESSION" : dateExit ? "DATE" : longScoreExit or shortScoreExit ? "SCORE" : srExitLong or srExitShort ? "S/R" : outcome
    if not na(activeProfitBox)
        box.set_right(activeProfitBox, bar_index)
        box.set_text(activeProfitBox, outcome + "\nProfit zone")
    if not na(activeLossBox)
        box.set_right(activeLossBox, bar_index)
        box.set_text(activeLossBox, outcome + "\nLoss zone")
    if not na(activeEntryBox)
        box.set_right(activeEntryBox, bar_index)
        box.set_text(activeEntryBox, outcome + " / " + reason)
    label.new(bar_index, close, outcome + " / " + reason + "\nPnL: " + str.tostring(closedProfit, "#.##"), style = outcome == "WIN" ? label.style_label_up : label.style_label_down, color = outcome == "WIN" ? color.new(color.green, 0) : color.new(color.red, 0), textcolor = color.white, size = size.small)
    entryBarIndex := na
    entryTimeMs := na
    entrySide := ""

// === BACKGROUND / RESEARCH ZONES ===
researchBg = showResearchZones ? inInSample ? color.new(color.blue, 94) : inOOS ? color.new(color.orange, 94) : inPaper ? color.new(color.purple, 94) : na : na
regimeBg = activeTrendMA and showRegimeBg ? trendBull ? color.new(color.green, 94) : trendBear ? color.new(color.red, 95) : na : na
sessionBg = useSessionFilter and not inSession ? color.new(color.gray, 94) : na
bgcolor(researchBg, title = "Research segment")
bgcolor(regimeBg, title = "Regime background")
bgcolor(sessionBg, title = "Outside trading session")

// OOS/Paper transition markers: label the FIRST bar of each hold-out segment for parity review.
// Placed at bar open so they remain visible even when candlestick labels overlap.
isToOOS   = showOOSMarkers and inOOS and not inOOS[1]
oOSToPaper = showOOSMarkers and inPaper and not inPaper[1]
if isToOOS
    label.new(bar_index, high, "OOS START", style = label.style_label_down, color = color.new(color.orange, 0), textcolor = color.white, size = size.small)
if oOSToPaper
    label.new(bar_index, high, "PAPER START", style = label.style_label_down, color = color.new(color.purple, 0), textcolor = color.white, size = size.small)

// === PLOTS ===
regimeColor = trendBull ? color.new(color.lime, 0) : trendBear ? color.new(color.red, 0) : color.new(color.gray, 0)
barcolor(colorBars ? longScore > shortScore ? color.new(color.green, 0) : shortScore > longScore ? color.new(color.red, 0) : na : na)
plot(activeTrendMA and showMA ? trendMA : na, "Trend MA", color = regimeColor, linewidth = 2)
plot(activeTrendMA and showHTFMA ? htfMA : na, "HTF Trend MA", color = color.new(color.orange, 0), linewidth = 2)
plot(activeRSI ? rsi : na, "RSI data-window only", display = display.data_window)
plot(showScorePlots ? longScore : na, "Long score", display = display.data_window)
plot(showScorePlots ? shortScore : na, "Short score", display = display.data_window)

bbBasisPlot = plot(activeFibBB and showFibBB ? basis : na, "Fib BB Basis", color = color.new(color.fuchsia, 0), linewidth = 2)
u1 = plot(activeFibBB and showFibBB ? upper1 : na, "Fib BB +0.236", color = color.new(color.white, 55))
u2 = plot(activeFibBB and showFibBB ? upper2 : na, "Fib BB +0.382", color = color.new(color.white, 50))
u3 = plot(activeFibBB and showFibBB ? upper3 : na, "Fib BB +0.500", color = color.new(color.white, 45))
u4 = plot(activeFibBB and showFibBB ? upper4 : na, "Fib BB +0.618", color = color.new(color.white, 40))
u5 = plot(activeFibBB and showFibBB ? upper5 : na, "Fib BB +0.764", color = color.new(color.white, 35))
u6 = plot(activeFibBB and showFibBB ? upper6 : na, "Fib BB +1.000", color = color.new(color.red, 0), linewidth = 2)
l1 = plot(activeFibBB and showFibBB ? lower1 : na, "Fib BB -0.236", color = color.new(color.white, 55))
l2 = plot(activeFibBB and showFibBB ? lower2 : na, "Fib BB -0.382", color = color.new(color.white, 50))
l3 = plot(activeFibBB and showFibBB ? lower3 : na, "Fib BB -0.500", color = color.new(color.white, 45))
l4 = plot(activeFibBB and showFibBB ? lower4 : na, "Fib BB -0.618", color = color.new(color.white, 40))
l5 = plot(activeFibBB and showFibBB ? lower5 : na, "Fib BB -0.764", color = color.new(color.white, 35))
l6 = plot(activeFibBB and showFibBB ? lower6 : na, "Fib BB -1.000", color = color.new(color.green, 0), linewidth = 2)
fill(u6, l6, color = activeFibBB and showFibBB and showFibFill ? color.new(color.gray, 96) : na, title = "Fib BB envelope")

plot(showRiskLines and strategy.position_size > 0 ? longStop : na, "Long stop", color = color.new(color.red, 0), linewidth = 2, style = plot.style_linebr)
plot(showRiskLines and strategy.position_size > 0 and useTakeProfit ? longTarget : na, "Long target", color = color.new(color.green, 0), linewidth = 2, style = plot.style_linebr)
plot(showRiskLines and strategy.position_size < 0 ? shortStop : na, "Short stop", color = color.new(color.red, 0), linewidth = 2, style = plot.style_linebr)
plot(showRiskLines and strategy.position_size < 0 and useTakeProfit ? shortTarget : na, "Short target", color = color.new(color.green, 0), linewidth = 2, style = plot.style_linebr)

plot(activeIchimoku and showIchimoku ? conversionLine : na, "Ichimoku Conversion", color = color.new(color.red, 0))
plot(activeIchimoku and showIchimoku ? baseLine : na, "Ichimoku Base", color = color.new(color.blue, 0))
plot(activeIchimoku and showIchimoku ? close : na, "Ichimoku Lagging Span", color = color.new(color.orange, 0), offset = -displacement + 1)
ichiA = plot(activeIchimoku and showIchimoku ? leadLine1 : na, "Ichimoku Leading Span A", color = color.new(color.purple, 0), offset = displacement - 1)
ichiB = plot(activeIchimoku and showIchimoku ? leadLine2 : na, "Ichimoku Leading Span B", color = color.new(color.lime, 0), offset = displacement - 1)
fill(ichiA, ichiB, color = activeIchimoku and showIchimoku ? leadLine1 > leadLine2 ? color.new(color.green, 88) : color.new(color.red, 88) : na, title = "Ichimoku Cloud")

plotshape(showSignals and longSignal, title = "Long entry", location = location.belowbar, style = shape.labelup, text = "LONG", color = color.new(color.green, 0), textcolor = color.white, size = size.tiny)
plotshape(showSignals and shortSignal, title = "Short entry", location = location.abovebar, style = shape.labeldown, text = "SHORT", color = color.new(color.red, 0), textcolor = color.white, size = size.tiny)
plotshape(showSignals and longScoreExit, title = "Long score exit", location = location.abovebar, style = shape.labeldown, text = "XL", color = color.new(color.orange, 0), textcolor = color.white, size = size.tiny)
plotshape(showSignals and shortScoreExit, title = "Short score exit", location = location.belowbar, style = shape.labelup, text = "XS", color = color.new(color.orange, 0), textcolor = color.white, size = size.tiny)

plotshape(activeSR and showSRZones and supportHold, title = "Support hold", location = location.belowbar, style = shape.square, text = "SUP", color = color.new(color.green, 0), textcolor = color.white, size = size.tiny)
plotshape(activeSR and showSRZones and resistanceHold, title = "Resistance hold", location = location.abovebar, style = shape.square, text = "RES", color = color.new(color.red, 0), textcolor = color.white, size = size.tiny)
plotshape(activeSR and showSRZones and supportFail, title = "Support failed", location = location.belowbar, style = shape.xcross, text = "SF", color = color.new(color.red, 0), textcolor = color.white, size = size.tiny)
plotshape(activeSR and showSRZones and resistanceFail, title = "Resistance failed", location = location.abovebar, style = shape.xcross, text = "RF", color = color.new(color.green, 0), textcolor = color.white, size = size.tiny)

plotshape(activeCandles and showCandlePatterns and bull382, title = "Bull 38.2 candle", location = location.belowbar, style = shape.triangleup, text = "38B", color = color.new(color.lime, 0), textcolor = color.white, size = size.tiny)
plotshape(activeCandles and showCandlePatterns and bear382, title = "Bear 38.2 candle", location = location.abovebar, style = shape.triangledown, text = "38S", color = color.new(color.red, 0), textcolor = color.white, size = size.tiny)
plotshape(activeCandles and showCandlePatterns and bullEngulf, title = "Bull engulfing", location = location.belowbar, style = shape.labelup, text = "ENG", color = color.new(color.green, 0), textcolor = color.white, size = size.tiny)
plotshape(activeCandles and showCandlePatterns and bearEngulf, title = "Bear engulfing", location = location.abovebar, style = shape.labeldown, text = "ENG", color = color.new(color.maroon, 0), textcolor = color.white, size = size.tiny)
plotshape(activeCandles and showCandlePatterns and bullCloseBreak, title = "Bull close above", location = location.belowbar, style = shape.circle, text = "CA", color = color.new(color.teal, 0), textcolor = color.white, size = size.tiny)
plotshape(activeCandles and showCandlePatterns and bearCloseBreak, title = "Bear close below", location = location.abovebar, style = shape.circle, text = "CB", color = color.new(color.orange, 0), textcolor = color.white, size = size.tiny)
plotshape(activeCandles and showCandlePatterns and bullWickReject, title = "Bull wick rejection", location = location.belowbar, style = shape.diamond, text = "WR", color = color.new(color.aqua, 0), textcolor = color.black, size = size.tiny)
plotshape(activeCandles and showCandlePatterns and bearWickReject, title = "Bear wick rejection", location = location.abovebar, style = shape.diamond, text = "WR", color = color.new(color.yellow, 0), textcolor = color.black, size = size.tiny)

plotshape(activePatterns and showChartPatterns and doubleBottomBreak, title = "Double bottom break", location = location.belowbar, style = shape.labelup, text = "DB", color = color.new(color.lime, 0), textcolor = color.white, size = size.small)
plotshape(activePatterns and showChartPatterns and doubleTopBreak, title = "Double top break", location = location.abovebar, style = shape.labeldown, text = "DT", color = color.new(color.red, 0), textcolor = color.white, size = size.small)
plotshape(activePatterns and showChartPatterns and bullFlagBreak, title = "Bull flag break", location = location.belowbar, style = shape.labelup, text = "FLAG", color = color.new(color.green, 0), textcolor = color.white, size = size.tiny)
plotshape(activePatterns and showChartPatterns and bearFlagBreak, title = "Bear flag break", location = location.abovebar, style = shape.labeldown, text = "FLAG", color = color.new(color.maroon, 0), textcolor = color.white, size = size.tiny)
plotshape(activePatterns and showChartPatterns and ascendingBreak, title = "Ascending breakout", location = location.belowbar, style = shape.labelup, text = "ASC", color = color.new(color.teal, 0), textcolor = color.white, size = size.tiny)
plotshape(activePatterns and showChartPatterns and descendingBreak, title = "Descending breakout", location = location.abovebar, style = shape.labeldown, text = "DESC", color = color.new(color.orange, 0), textcolor = color.white, size = size.tiny)

// === INFORMATIONAL ALERTS ===
// alertcondition() does not create selectable alertcondition events in strategies.
// Order-fill alerts are handled through strategy.* alert_message fields above.
// These optional alert() calls can be used by selecting "Any alert() function call".
enableInfoAlerts = input.bool(false, "Enable non-order informational alert() calls", group = G_ALERT)
if enableInfoAlerts and signalGate
    if supportHold
        alert(f_json("SUPPORT_HOLD", longScore, shortScore), alert.freq_once_per_bar_close)
    if resistanceHold
        alert(f_json("RESISTANCE_HOLD", longScore, shortScore), alert.freq_once_per_bar_close)
    if supportFail
        alert(f_json("SUPPORT_FAIL", longScore, shortScore), alert.freq_once_per_bar_close)
    if resistanceFail
        alert(f_json("RESISTANCE_FAIL", longScore, shortScore), alert.freq_once_per_bar_close)
    if bullCandleEvent
        alert(f_json("BULL_CANDLE", longScore, shortScore), alert.freq_once_per_bar_close)
    if bearCandleEvent
        alert(f_json("BEAR_CANDLE", longScore, shortScore), alert.freq_once_per_bar_close)
    if bullChartEvent
        alert(f_json("BULL_PATTERN", longScore, shortScore), alert.freq_once_per_bar_close)
    if bearChartEvent
        alert(f_json("BEAR_PATTERN", longScore, shortScore), alert.freq_once_per_bar_close)

// === DASHBOARD ===
var table dash = table.new(position.top_right, 2, 15, border_width = 1)
if showDashboard and barstate.isconfirmed
    string regimeTxt = trendBull ? "Bull" : trendBear ? "Bear" : "Neutral"
    string htfTxt = htfBull ? "HTF Bull" : htfBear ? "HTF Bear" : "HTF Neutral"
    string posTxt = strategy.position_size > 0 ? "Long" : strategy.position_size < 0 ? "Short" : "Flat"
    string segmentTxt = validationSegment == "Full selected range" ? "Full" : validationSegment == "In-sample only" ? "IS" : validationSegment == "Out-of-sample only" ? "OOS" : "Paper"
    string modulesTxt = (activeRSI ? "R" : "-") + (activeSR ? "S" : "-") + (activeFibBB ? "B" : "-") + (activeTrendMA ? "M" : "-") + (activeIchimoku ? "I" : "-") + (activeCandles ? "C" : "-") + (activePatterns ? "P" : "-")
    string setupTxt = primaryLongSetup ? "Long setup" : primaryShortSetup ? "Short setup" : "None"
    string filterTxt = filtersOK ? "OK" : "Blocked"
    string sessionTxt = inSession ? "Session ON" : "Session OFF"
    string srTxt = supportHold ? "Support hold" : resistanceHold ? "Resistance hold" : supportFail ? "Support fail" : resistanceFail ? "Resistance fail" : "Neutral"
    table.cell(dash, 0, 0, "CTAPv6 Day", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 0, researchPreset, text_color = color.white, bgcolor = color.new(color.blue, 20))
    table.cell(dash, 0, 1, "Segment", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 1, segmentTxt + " / " + (segmentOK ? "ON" : "OFF"), text_color = color.white, bgcolor = segmentOK ? color.new(color.green, 30) : color.new(color.gray, 30))
    table.cell(dash, 0, 2, "Session", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 2, sessionTxt, text_color = color.white, bgcolor = inSession ? color.new(color.green, 35) : color.new(color.red, 35))
    table.cell(dash, 0, 3, "Mode", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 3, tradeMode + " / " + tradeDirection, text_color = color.white, bgcolor = allowShorts ? color.new(color.purple, 30) : color.new(color.gray, 30))
    table.cell(dash, 0, 4, "Modules", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 4, modulesTxt, text_color = color.white, bgcolor = color.new(color.gray, 20))
    table.cell(dash, 0, 5, "Position", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 5, posTxt, text_color = color.white, bgcolor = strategy.position_size > 0 ? color.new(color.green, 20) : strategy.position_size < 0 ? color.new(color.red, 20) : color.new(color.gray, 20))
    table.cell(dash, 0, 6, "Active Time", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 6, str.tostring(barsInTrade) + " bars / " + str.tostring(minutesInTrade, "#.0") + "m", text_color = color.white, bgcolor = timeExit ? color.new(color.red, 25) : color.new(color.gray, 20))
    table.cell(dash, 0, 7, "S/R", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 7, srTxt, text_color = color.white, bgcolor = supportHold ? color.new(color.green, 20) : resistanceHold ? color.new(color.red, 20) : color.new(color.gray, 20))
    table.cell(dash, 0, 8, "Regime", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 8, regimeTxt + " / " + htfTxt, text_color = color.white, bgcolor = trendBull ? color.new(color.green, 20) : trendBear ? color.new(color.red, 20) : color.new(color.gray, 20))
    table.cell(dash, 0, 9, "Setup", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 9, setupTxt, text_color = color.white, bgcolor = color.new(color.gray, 20))
    table.cell(dash, 0, 10, "Long Score", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 10, str.tostring(longScore, "#.##"), text_color = color.white, bgcolor = longScore >= entryThreshold ? color.new(color.green, 20) : color.new(color.gray, 20))
    table.cell(dash, 0, 11, "Short Score", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 11, str.tostring(shortScore, "#.##"), text_color = color.white, bgcolor = shortScore >= entryThreshold ? color.new(color.red, 20) : color.new(color.gray, 20))
    table.cell(dash, 0, 12, "RSI / ATR%", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 12, str.tostring(rsi, "#.##") + " / " + str.tostring(atrPct, "#.##") + "%", text_color = color.white, bgcolor = volatilityOK ? color.new(color.green, 35) : color.new(color.red, 25))
    table.cell(dash, 0, 13, "Volume", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 13, volumeOK ? "OK" : "Low", text_color = color.white, bgcolor = volumeOK ? color.new(color.green, 35) : color.new(color.red, 25))
    table.cell(dash, 0, 14, "Filters", text_color = color.white, bgcolor = color.new(color.black, 0))
    table.cell(dash, 1, 14, filterTxt, text_color = color.white, bgcolor = filtersOK ? color.new(color.green, 35) : color.new(color.red, 25))

Overview

Crypto TA Pro Day Trading S/R Execution blends RSI, Fibonacci Bollinger Bands, trend and higher timeframe filters, pivot support and resistance zones, Ichimoku and pattern modules into a weighted score for entries. A session filter, auto-close at session end and timed exits by bars or minutes limit time in the market. It draws entry boxes and outcome labels, offers research and live position sizing choices, and supports date-range and out-of-sample testing.

Details

Published by

Xellos1010 on GitHub

Draws

On the price chart

Markets

Crypto

Timeframes

M15-H1

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

algorithmic-tradingcoinbasepolymarketrisk-managementrusttokiotrading

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Market modestringSpottradeMode
Trade directionstringLong onlytradeDirection
Backtest starttime1767225600000startTime
Backtest endtime1798761540000endTime
Only act after confirmed bar closebooltrueconfirmedOnly
Use day-trading session filterbooltrueuseSessionFilter
Trading sessionsession0800-2000:1234567tradeSession
Session timezonestringEtc/UTCsessionTimezone
Auto-close when outside sessionbooltruecloseAtSessionEnd
Auto-exit after time in marketbooltrueenableTimedExit
Max bars in tradeint24maxBarsInTrade
Max minutes in tradeint240maxMinutesInTrade
Show all 40 settings
SettingTypeDefaultVariable
Timed exit fires onstringBars OR MinutestimedExitMode
Close positions outside selected rangebooltruecloseOutsideRange
Entry model labelstringMarket next barentryProcess
Indicator unlock presetstringCustom togglesresearchPreset
Trade segmentstringFull selected rangevalidationSegment
In-sample starttime1767225600000inSampleStart
In-sample endtime1782863940000inSampleEnd
Out-of-sample starttime1782864000000oosStart
Out-of-sample endtime1798761540000oosEnd
Forward/Paper starttime1798761600000paperStart
Forward/Paper endtime1830297540000paperEnd
Shade research segmentsbooltrueshowResearchZones
Show IS/OOS/Paper transition markersbooltrueshowOOSMarkers
Research sizing (100% equity)booltrueresearchSizingMode
Live position size (% equity)float5.0liveQtyPct
Enable RSIbooltrueenableRSI
Enable support/resistancebooltrueenableSR
Enable Fibonacci Bollinger BandsbooltrueenableFibBB
Enable trend MA / regime filterbooltrueenableTrendMA
Enable IchimokuboolfalseenableIchimoku
Enable candlestick pattern enginebooltrueenableCandles
Enable chart/breakout pattern engineboolfalseenablePatterns
Entry score thresholdfloat60.0entryThreshold
Signal-exit score thresholdfloat60.0exitThreshold
Require at least one primary setupbooltruerequirePrimarySetup
RSI influences entrybooltrueuseRSIForEntry
RSI influences exitbooltrueuseRSIForExit
RSI entry weightfloat1.00rsiEntryWeight
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

ORB + Fib + MACD Strategy

by alboogycOdR
6 stars

This strategy builds a timed opening range and tracks session high and low on the price chart. It flags bullish and bearish breakouts of that range and uses Fibonacci levels for an entry zone, a default stop, and an optional take-profit extension. Take profit

Strategy v5 BreakoutSupport & resistance
Updated 7 months ago · None stated GitHub repository of ORB + Fib + MACD Strategy

Daily Bias + FVG Strategy

by TradersPost
228 stars

This overlay strategy plots previous-day high and low, the daily open, and fair value gap zones on the price chart. It takes entries on a selectable timeframe when the FVG requirement and an optional session window allow them, then uses an ATR or fixed pip sto

Strategy v6 SMC / ICT / price actionSupport & resistance Repaints
Updated 3 months ago · None stated GitHub repository of Daily Bias + FVG Strategy
106 stars

This script monitors opening gap conditions and breakout behavior relative to the first candle's range across up to twenty configurable stock tickers. It detects whether following bars remain inside the opening range before breaking out, displaying the status

Strategy v4 Screeners & dashboardsBreakout Repaints
Updated 2 years ago · GPL-3.0 GitHub repository of 1St Bar GAP+BkOut Screener v2 by RM

This strategy plots the previous daily high and low on the price chart and calculates the Andean oscillator bull and bear components with an exponential moving average signal. It looks for a bull or bear cross of that signal, then confirms the setup against re

Strategy v5 Oscillators & momentumSupport & resistance Repaints
Updated 3 years ago · MIT GitHub repository of Daily High/Low and Andean Oscillator Strategy
53 stars

This strategy enters long or short on the price chart when price is on the correct side of a 200-period EMA, MACD agrees, RSI is not at the opposing extreme, ADX exceeds a threshold, volume beats a 20-bar average, and close breaks the recent high or low. A tim

Strategy v6 TrendOscillators & momentum
Updated 2 months ago · MPL-2.0 GitHub repository of BTCUSD 5M Optimized Strategy

ATOM 1m scalp HK

by dearvn
43 stars

This overlay strategy targets one-minute ATOM scalps. It tracks pivot highs and lows as resistance and support, compares a three-period EMA with a multi-stage smoothed close, and flags long or short when price crosses that curve in the matching direction. Dail

Strategy v5 ScalpingSupport & resistance Repaints
Updated 2 years ago · None stated GitHub repository of ATOM 1m scalp HK

More Support & resistance scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers