//@version=6 indicator("AlgoNexta Photon Trader V1 — Structure", "Photon.AlgoNexta", overlay = true, max_labels_count = 500, max_lines_count = 100, max_boxes_count = 50) // TradingView indicator port of the causal Phase 1–2 cTrader milestone. // No POI, liquidity, entry, order, SL/TP, or performance simulation is present. // Higher-timeframe requests use lookahead_off and events are emitted only after bar close. string G_GENERAL = "01 General" string G_TIME = "02 Timeframes" string G_STRUCTURE = "03 Structure" string G_DISPLAY = "13 Display" string G_ALERTS = "14 Alerts" string profile = input.string("ClassicPhoton", "Photon Profile", options = ["ClassicPhoton", "GoldResearch", "Custom"], group = G_GENERAL) string customHtf = input.timeframe("240", "Higher Timeframe", group = G_TIME) string customMtf = input.timeframe("15", "Middle Timeframe", group = G_TIME) string customLtf = input.timeframe("1", "Entry Timeframe", group = G_TIME) int swingStrength = input.int(3, "Swing Pivot Strength", minval = 1, maxval = 20, group = G_STRUCTURE) float bosBufferPips = input.float(0.0, "BOS Buffer Pips", minval = 0.0, step = 0.1, group = G_STRUCTURE) float pipSize = input.float(0.1, "Pip Size", minval = 0.0000001, step = 0.01, group = G_STRUCTURE, tooltip = "For many XAUUSD feeds this is 0.1. Match this to your broker/feed convention.") int atrPeriod = input.int(14, "ATR Period", minval = 2, group = G_STRUCTURE) float swingAtrMultiplier = input.float(1.0, "Swing ATR Multiplier", minval = 0.1, step = 0.05, group = G_STRUCTURE) float internalAtrMultiplier = input.float(0.35, "Internal ATR Multiplier", minval = 0.05, step = 0.05, group = G_STRUCTURE) int minimumSwingBars = input.int(3, "Minimum Swing Bars", minval = 1, group = G_STRUCTURE) int minimumInternalBars = input.int(1, "Minimum Internal Bars", minval = 1, group = G_STRUCTURE) bool requirePremiumDiscount = input.bool(false, "Require Premium/Discount", group = G_STRUCTURE, tooltip = "Context-only in this structure milestone; it does not filter events.") bool showHtf = input.bool(true, "Show HTF Events", group = G_DISPLAY) bool showMtf = input.bool(true, "Show MTF Events", group = G_DISPLAY) bool showLtf = input.bool(false, "Show LTF Events", group = G_DISPLAY) bool showSwingPivots = input.bool(true, "Show Swing High/Low", group = G_DISPLAY) bool showInternal = input.bool(false, "Show Internal High/Low", group = G_DISPLAY) bool showBos = input.bool(true, "Show BOS", group = G_DISPLAY) bool showChoch = input.bool(true, "Show CHoCH", group = G_DISPLAY) bool showStrongWeak = input.bool(true, "Show Strong/Weak Levels", group = G_DISPLAY) bool showEquilibrium = input.bool(true, "Show Equilibrium", group = G_DISPLAY) bool showDashboard = input.bool(true, "Show Dashboard", group = G_DISPLAY) int maximumEventLabels = input.int(250, "Maximum Event Labels", minval = 20, maxval = 480, group = G_DISPLAY) bool enableBosAlerts = input.bool(true, "Enable BOS Alerts", group = G_ALERTS) bool enableChochAlerts = input.bool(true, "Enable CHoCH Alerts", group = G_ALERTS) string htf = profile == "ClassicPhoton" ? "240" : profile == "GoldResearch" ? "60" : customHtf string mtf = profile == "ClassicPhoton" ? "15" : profile == "GoldResearch" ? "5" : customMtf string ltf = profile == "ClassicPhoton" ? "1" : profile == "GoldResearch" ? "1" : customLtf f_dirText(int direction) => direction == 1 ? "Bull" : direction == -1 ? "Bear" : "Unknown" f_phaseText(int phase) => phase == 1 ? "Impulse" : phase == -1 ? "Pullback" : "Unknown" f_flowText(int flow) => flow == 1 ? "Bull Continuation" : flow == 2 ? "Bull Pullback" : flow == -1 ? "Bear Continuation" : flow == -2 ? "Bear Pullback" : "Unknown" f_priceText(float price) => na(price) ? "—" : str.tostring(price, format.mintick) // MECHANICAL_INTERPRETATION: // Photon contains discretionary judgment in swing significance and internal mapping. // This implementation freezes a pivot after right-side confirmation and uses deterministic ATR qualification. f_structure() => var float swingHigh = na var float swingLow = na var int swingHighOrigin = na var int swingLowOrigin = na var bool swingHighBroken = false var bool swingLowBroken = false var float internalHigh = na var float internalLow = na var int internalHighOrigin = na var int internalLowOrigin = na var bool internalHighBroken = false var bool internalLowBroken = false var int swingDirection = 0 var int internalDirection = 0 var float strongHigh = na var float strongLow = na var float weakHigh = na var float weakLow = na var int lastBosTime = na var int lastBosDirection = 0 var int lastChochTime = na var int lastChochDirection = 0 var int lastSwingEventTime = na var int lastSwingEventType = 0 var float lastSwingEventPrice = na var int lastInternalEventTime = na var int lastInternalEventType = 0 var float lastInternalEventPrice = na float atr = ta.atr(atrPeriod) float ph = ta.pivothigh(high, swingStrength, swingStrength) float pl = ta.pivotlow(low, swingStrength, swingStrength) bool confirmed = barstate.isconfirmed if confirmed and not na(ph) float opposingLow = ta.lowest(low[swingStrength], minimumSwingBars + 1) bool qualifiedHigh = ph - opposingLow >= atr[swingStrength] * swingAtrMultiplier if qualifiedHigh swingHigh := ph swingHighOrigin := bar_index - swingStrength swingHighBroken := false lastSwingEventTime := time lastSwingEventType := 1 lastSwingEventPrice := ph if swingDirection == -1 strongHigh := ph else weakHigh := ph if confirmed and not na(pl) float opposingHigh = ta.highest(high[swingStrength], minimumSwingBars + 1) bool qualifiedLow = opposingHigh - pl >= atr[swingStrength] * swingAtrMultiplier if qualifiedLow swingLow := pl swingLowOrigin := bar_index - swingStrength swingLowBroken := false lastSwingEventTime := time lastSwingEventType := -1 lastSwingEventPrice := pl if swingDirection == 1 strongLow := pl else weakLow := pl // Prior candle becomes a candidate when this closed candle fails to extend its extreme. float minInternalMove = atr * internalAtrMultiplier bool highCandidate = confirmed and high <= high[1] and (na(internalLow) or high[1] - internalLow >= minInternalMove) bool lowCandidate = confirmed and low >= low[1] and (na(internalHigh) or internalHigh - low[1] >= minInternalMove) if highCandidate and (na(internalLowOrigin) or bar_index - internalLowOrigin >= minimumInternalBars) internalHigh := high[1] internalHighOrigin := bar_index - 1 internalHighBroken := false lastInternalEventTime := time lastInternalEventType := 1 lastInternalEventPrice := internalHigh if lowCandidate and (na(internalHighOrigin) or bar_index - internalHighOrigin >= minimumInternalBars) internalLow := low[1] internalLowOrigin := bar_index - 1 internalLowBroken := false lastInternalEventTime := time lastInternalEventType := -1 lastInternalEventPrice := internalLow // PHOTON_ORIGINAL: Swing BOS requires a candle close beyond the mapped wick level. bool bullBos = confirmed and not swingHighBroken and not na(swingHigh) and close > swingHigh + bosBufferPips * pipSize bool bearBos = confirmed and not swingLowBroken and not na(swingLow) and close < swingLow - bosBufferPips * pipSize if bullBos swingHighBroken := true swingDirection := 1 strongLow := swingLow weakHigh := swingHigh lastBosTime := time lastBosDirection := 1 if bearBos swingLowBroken := true swingDirection := -1 strongHigh := swingHigh weakLow := swingLow lastBosTime := time lastBosDirection := -1 // PHOTON_ORIGINAL: CHoCH is the first aggressive wick break against mapped Internal behaviour. bool bullChoch = confirmed and internalDirection == -1 and not internalHighBroken and not na(internalHigh) and high > internalHigh bool bearChoch = confirmed and internalDirection == 1 and not internalLowBroken and not na(internalLow) and low < internalLow if bullChoch internalHighBroken := true internalDirection := 1 lastChochTime := time lastChochDirection := 1 if bearChoch internalLowBroken := true internalDirection := -1 lastChochTime := time lastChochDirection := -1 // Bootstrap internal direction from a mapped break; this is not emitted as CHoCH. if confirmed and internalDirection == 0 if not na(internalHigh) and high > internalHigh internalDirection := 1 internalHighBroken := true else if not na(internalLow) and low < internalLow internalDirection := -1 internalLowBroken := true bool counter = swingDirection != 0 and internalDirection != 0 and swingDirection != internalDirection int phase = swingDirection == 0 ? 0 : counter ? -1 : 1 int expectedFlow = swingDirection == 1 ? (counter ? 2 : 1) : swingDirection == -1 ? (counter ? -2 : -1) : 0 float rangeHigh = not na(strongHigh) ? strongHigh : weakHigh float rangeLow = not na(strongLow) ? strongLow : weakLow float relativePosition = not na(rangeHigh) and not na(rangeLow) and rangeHigh > rangeLow ? (close - rangeLow) / (rangeHigh - rangeLow) : na float equilibrium = not na(rangeHigh) and not na(rangeLow) ? (rangeHigh + rangeLow) * 0.5 : na [swingDirection, internalDirection, phase, expectedFlow, strongHigh, strongLow, weakHigh, weakLow, equilibrium, relativePosition, lastBosTime, lastBosDirection, lastChochTime, lastChochDirection, lastSwingEventTime, lastSwingEventType, lastSwingEventPrice, lastInternalEventTime, lastInternalEventType, lastInternalEventPrice] [hSwing, hInternal, hPhase, hFlow, hStrongHigh, hStrongLow, hWeakHigh, hWeakLow, hEq, hPosition, hBosTime, hBosDir, hChochTime, hChochDir, hSwingTime, hSwingType, hSwingPrice, hInternalTime, hInternalType, hInternalPrice] = request.security(syminfo.tickerid, htf, f_structure(), gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off) [mSwing, mInternal, mPhase, mFlow, mStrongHigh, mStrongLow, mWeakHigh, mWeakLow, mEq, mPosition, mBosTime, mBosDir, mChochTime, mChochDir, mSwingTime, mSwingType, mSwingPrice, mInternalTime, mInternalType, mInternalPrice] = request.security(syminfo.tickerid, mtf, f_structure(), gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off) [lSwing, lInternal, lPhase, lFlow, lStrongHigh, lStrongLow, lWeakHigh, lWeakLow, lEq, lPosition, lBosTime, lBosDir, lChochTime, lChochDir, lSwingTime, lSwingType, lSwingPrice, lInternalTime, lInternalType, lInternalPrice] = request.security(syminfo.tickerid, ltf, f_structure(), gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off) bool hNewBos = not na(hBosTime) and (na(hBosTime[1]) or hBosTime != hBosTime[1]) bool mNewBos = not na(mBosTime) and (na(mBosTime[1]) or mBosTime != mBosTime[1]) bool lNewBos = not na(lBosTime) and (na(lBosTime[1]) or lBosTime != lBosTime[1]) bool hNewChoch = not na(hChochTime) and (na(hChochTime[1]) or hChochTime != hChochTime[1]) bool mNewChoch = not na(mChochTime) and (na(mChochTime[1]) or mChochTime != mChochTime[1]) bool lNewChoch = not na(lChochTime) and (na(lChochTime[1]) or lChochTime != lChochTime[1]) bool hNewSwing = not na(hSwingTime) and (na(hSwingTime[1]) or hSwingTime != hSwingTime[1]) bool mNewSwing = not na(mSwingTime) and (na(mSwingTime[1]) or mSwingTime != mSwingTime[1]) bool lNewSwing = not na(lSwingTime) and (na(lSwingTime[1]) or lSwingTime != lSwingTime[1]) bool hNewInternal = not na(hInternalTime) and (na(hInternalTime[1]) or hInternalTime != hInternalTime[1]) bool mNewInternal = not na(mInternalTime) and (na(mInternalTime[1]) or mInternalTime != mInternalTime[1]) bool lNewInternal = not na(lInternalTime) and (na(lInternalTime[1]) or lInternalTime != lInternalTime[1]) var label[] eventLabels = array.new_label() f_event(bool event, string role, string eventName, int direction, float eventPrice, color eventColor) => if event float y = na(eventPrice) ? close : eventPrice label item = label.new(bar_index, y, role + " " + eventName + (direction == 1 ? " ↑" : " ↓"), style = direction == 1 ? label.style_label_up : label.style_label_down, color = eventColor, textcolor = color.white, size = size.tiny) array.push(eventLabels, item) while array.size(eventLabels) > maximumEventLabels label.delete(array.shift(eventLabels)) if showBos f_event(showHtf and hNewBos, "HTF", "BOS", hBosDir, hBosDir == 1 ? hWeakHigh : hWeakLow, color.new(hBosDir == 1 ? color.lime : color.red, 0)) f_event(showMtf and mNewBos, "MTF", "BOS", mBosDir, mBosDir == 1 ? mWeakHigh : mWeakLow, color.new(mBosDir == 1 ? color.lime : color.red, 15)) f_event(showLtf and lNewBos, "LTF", "BOS", lBosDir, lBosDir == 1 ? lWeakHigh : lWeakLow, color.new(lBosDir == 1 ? color.lime : color.red, 30)) if showChoch f_event(showHtf and hNewChoch, "HTF", "CHoCH", hChochDir, close, color.purple) f_event(showMtf and mNewChoch, "MTF", "CHoCH", mChochDir, close, color.new(color.purple, 15)) f_event(showLtf and lNewChoch, "LTF", "CHoCH", lChochDir, close, color.new(color.purple, 30)) if showSwingPivots f_event(showHtf and hNewSwing, "HTF", hSwingType == 1 ? "SH" : "SL", hSwingType == 1 ? -1 : 1, hSwingPrice, color.new(color.blue, 20)) f_event(showMtf and mNewSwing, "MTF", mSwingType == 1 ? "SH" : "SL", mSwingType == 1 ? -1 : 1, mSwingPrice, color.new(color.blue, 40)) f_event(showLtf and lNewSwing, "LTF", lSwingType == 1 ? "SH" : "SL", lSwingType == 1 ? -1 : 1, lSwingPrice, color.new(color.blue, 60)) if showInternal f_event(showHtf and hNewInternal, "HTF", hInternalType == 1 ? "IH" : "IL", hInternalType == 1 ? -1 : 1, hInternalPrice, color.new(color.gray, 20)) f_event(showMtf and mNewInternal, "MTF", mInternalType == 1 ? "IH" : "IL", mInternalType == 1 ? -1 : 1, mInternalPrice, color.new(color.gray, 40)) f_event(showLtf and lNewInternal, "LTF", lInternalType == 1 ? "IH" : "IL", lInternalType == 1 ? -1 : 1, lInternalPrice, color.new(color.gray, 60)) plot(showStrongWeak ? hStrongHigh : na, "HTF Strong High", color = color.new(color.red, 5), linewidth = 2, style = plot.style_linebr) plot(showStrongWeak ? hStrongLow : na, "HTF Strong Low", color = color.new(color.lime, 5), linewidth = 2, style = plot.style_linebr) plot(showStrongWeak ? hWeakHigh : na, "HTF Weak High", color = color.new(color.orange, 20), linewidth = 1, style = plot.style_linebr) plot(showStrongWeak ? hWeakLow : na, "HTF Weak Low", color = color.new(color.orange, 20), linewidth = 1, style = plot.style_linebr) plot(showStrongWeak ? mStrongHigh : na, "MTF Strong High", color = color.new(color.red, 55), linewidth = 1, style = plot.style_linebr) plot(showStrongWeak ? mStrongLow : na, "MTF Strong Low", color = color.new(color.lime, 55), linewidth = 1, style = plot.style_linebr) plot(showEquilibrium ? hEq : na, "HTF Equilibrium", color = color.new(color.aqua, 35), linewidth = 1, style = plot.style_linebr) bool anyBullBos = (showHtf and hNewBos and hBosDir == 1) or (showMtf and mNewBos and mBosDir == 1) or (showLtf and lNewBos and lBosDir == 1) bool anyBearBos = (showHtf and hNewBos and hBosDir == -1) or (showMtf and mNewBos and mBosDir == -1) or (showLtf and lNewBos and lBosDir == -1) bool anyBullChoch = (showHtf and hNewChoch and hChochDir == 1) or (showMtf and mNewChoch and mChochDir == 1) or (showLtf and lNewChoch and lChochDir == 1) bool anyBearChoch = (showHtf and hNewChoch and hChochDir == -1) or (showMtf and mNewChoch and mChochDir == -1) or (showLtf and lNewChoch and lChochDir == -1) alertcondition(enableBosAlerts and anyBullBos, "Photon Bullish BOS", "Photon bullish close-confirmed BOS on {{ticker}} at {{close}}") alertcondition(enableBosAlerts and anyBearBos, "Photon Bearish BOS", "Photon bearish close-confirmed BOS on {{ticker}} at {{close}}") alertcondition(enableChochAlerts and anyBullChoch, "Photon Bullish CHoCH", "Photon bullish wick-based CHoCH on {{ticker}} at {{close}}") alertcondition(enableChochAlerts and anyBearChoch, "Photon Bearish CHoCH", "Photon bearish wick-based CHoCH on {{ticker}} at {{close}}") var table dashboard = table.new(position.top_right, 3, 10, bgcolor = color.new(color.black, 12), border_width = 1) if barstate.islast and showDashboard table.cell(dashboard, 0, 0, "ALGONEXTA PHOTON V1", text_color = color.white, bgcolor = color.new(color.blue, 40)) table.merge_cells(dashboard, 0, 0, 2, 0) table.cell(dashboard, 0, 1, "Role", text_color = color.silver) table.cell(dashboard, 1, 1, "HTF " + htf, text_color = color.aqua) table.cell(dashboard, 2, 1, "MTF " + mtf, text_color = color.aqua) table.cell(dashboard, 0, 2, "Swing", text_color = color.silver) table.cell(dashboard, 1, 2, f_dirText(hSwing), text_color = hSwing == 1 ? color.lime : hSwing == -1 ? color.red : color.orange) table.cell(dashboard, 2, 2, f_dirText(mSwing), text_color = mSwing == 1 ? color.lime : mSwing == -1 ? color.red : color.orange) table.cell(dashboard, 0, 3, "Internal", text_color = color.silver) table.cell(dashboard, 1, 3, f_dirText(hInternal), text_color = hInternal == 1 ? color.lime : hInternal == -1 ? color.red : color.orange) table.cell(dashboard, 2, 3, f_dirText(mInternal), text_color = mInternal == 1 ? color.lime : mInternal == -1 ? color.red : color.orange) table.cell(dashboard, 0, 4, "Phase", text_color = color.silver) table.cell(dashboard, 1, 4, f_phaseText(hPhase), text_color = color.white) table.cell(dashboard, 2, 4, f_phaseText(mPhase), text_color = color.white) table.cell(dashboard, 0, 5, "Expected Flow", text_color = color.silver) table.cell(dashboard, 1, 5, f_flowText(hFlow), text_color = color.white) table.cell(dashboard, 2, 5, f_flowText(mFlow), text_color = color.white) table.cell(dashboard, 0, 6, "Strong H", text_color = color.silver) table.cell(dashboard, 1, 6, f_priceText(hStrongHigh), text_color = color.white) table.cell(dashboard, 2, 6, f_priceText(mStrongHigh), text_color = color.white) table.cell(dashboard, 0, 7, "Strong L", text_color = color.silver) table.cell(dashboard, 1, 7, f_priceText(hStrongLow), text_color = color.white) table.cell(dashboard, 2, 7, f_priceText(mStrongLow), text_color = color.white) table.cell(dashboard, 0, 8, "Range Pos.", text_color = color.silver) table.cell(dashboard, 1, 8, na(hPosition) ? "—" : str.tostring(hPosition, "#.0%"), text_color = color.white) table.cell(dashboard, 2, 8, na(mPosition) ? "—" : str.tostring(mPosition, "#.0%"), text_color = color.white) table.cell(dashboard, 0, 9, "Status", text_color = color.silver) table.cell(dashboard, 1, 9, "STRUCTURE ONLY", text_color = color.yellow) table.merge_cells(dashboard, 1, 9, 2, 9) else if barstate.islast table.clear(dashboard, 0, 0, 2, 9)