Daily Profile

Description

Statistical Daily Profile & Ranges A session-based market structure indicator that maps out the daily trading profile with session range boxes, ADR levels, statistical breakdowns by day of week, and real-time engulfment probability data. How It Works The indicator tracks four key trading sessions in Eastern Time and draws colored boxes around each session's price range as it develops: Asia (8pm - 2am) — Overnight session establishing the initial range London (2am - 8am) — European session that often sets the directional tone NY Open (8am - 9am) — The first hour of US equity futures activity NY Initial Balance (9:30am - 10:30am) — The first hour of cash market trading ADR (Average Daily Range) lines are projected from the daily open, showing the statistically expected high and low based on recent daily ranges. A median line marks the open as a reference point. Session Statistics Table A breakdown table shows the average range for each session by day of week (Monday through Friday) with optional sample sizes. This helps identify which sessions tend to produce the largest moves on specific days — useful for planning which sessions to trade and when to expect expansion or contraction. Engulfment Probabilities After the London session closes, the indicator detects the relationship between the Asia and London ranges and displays condition-based probabilities for the New York session: London Engulfs Asia — London's range fully contains Asia's range Asia Engulfs London — Asia's range fully contains London's range Partial Engulfment Up/Down — One side is taken out while the other holds Each condition triggers a probability table showing the statistical likelihood of NY taking session highs/lows, engulfing the overnight range, and the directional bias for NY cash hours. Features Real-time session range boxes with dashed borders during active sessions ADR High/Low/Median lines with configurable styles (solid, dashed, dotted) Per-session average range broken down by weekday with sample sizes Engulfment detection with hardcoded probability data across all four conditions Configurable session colors, table positions, line widths, and lookback periods Works on any intraday timeframe Inputs ADR Days — Lookback period for average daily range calculation (default: 10) Session Lookback Days — Number of days for session statistics sampling (default: 50) Show/Hide toggles for ADR lines, median, session boxes, labels, and both tables Independent color pickers for each session Configurable table positions and line styles

Categories & Tags

Trend AnalysisVolume#ict#dailyprofile

Comments (0)

0/2000

Loading comments…

Source code

function calculate(bars, ctx) {
  if (bars.length < 2) return;

  // ─── INPUTS ───
  var adrDays = ctx.input('ADR Days', 10, { min: 1, max: 250, step: 1 });
  var sessionDays = ctx.input('Session Lookback Days', 50, { min: 5, max: 250, step: 5 });
  var lineWidth = ctx.input('Line Width', 1, { min: 1, max: 4, step: 1 });

  var showADR = ctx.input('Show ADR Lines', true);
  var adrColor = ctx.input('ADR Color', '#888888');
  var adrStyle = ctx.input('ADR Style', 'solid', {
    options: [
      { label: 'Solid', value: 'solid' },
      { label: 'Dashed', value: 'dashed' },
      { label: 'Dotted', value: 'dotted' }
    ]
  });
  var showMedian = ctx.input('Show Median Line', true);
  var medianColor = ctx.input('Median Color', '#2dd4bf');
  var medianStyle = ctx.input('Median Style', 'dashed', {
    options: [
      { label: 'Solid', value: 'solid' },
      { label: 'Dashed', value: 'dashed' },
      { label: 'Dotted', value: 'dotted' }
    ]
  });

  var showADRLabels = ctx.input('Show ADR Labels', true);
  var showMedianLabel = ctx.input('Show Median Label', true);
  var showSessionLabels = ctx.input('Show Session Labels', true);
  var showSessionBoxes = ctx.input('Show Session Boxes', true);

  var asiaColor = ctx.input('Asia Color', 'rgba(59,130,246,0.5)');
  var londonColor = ctx.input('London Color', 'rgba(168,85,247,0.5)');
  var nyOpenColor = ctx.input('NY Open Color', 'rgba(20,184,166,0.5)');
  var nyIBColor = ctx.input('NY IB Color', 'rgba(239,68,68,0.5)');

  var showStats = ctx.input('Show Stats Table', true);
  var showSampleSize = ctx.input('Show Sample Sizes', true);
  var statsPos = ctx.input('Stats Table Position', 'bottom_right', {
    options: [
      { label: 'Top Left', value: 'top_left' },
      { label: 'Top Right', value: 'top_right' },
      { label: 'Top Center', value: 'top_center' },
      { label: 'Bottom Left', value: 'bottom_left' },
      { label: 'Bottom Right', value: 'bottom_right' },
      { label: 'Bottom Center', value: 'bottom_center' },
      { label: 'Middle Left', value: 'middle_left' },
      { label: 'Middle Right', value: 'middle_right' }
    ]
  });

  var showProbTable = ctx.input('Show Probabilities Table', true);
  var probPos = ctx.input('Probabilities Table Position', 'top_right', {
    options: [
      { label: 'Top Left', value: 'top_left' },
      { label: 'Top Right', value: 'top_right' },
      { label: 'Top Center', value: 'top_center' },
      { label: 'Bottom Left', value: 'bottom_left' },
      { label: 'Bottom Right', value: 'bottom_right' },
      { label: 'Bottom Center', value: 'bottom_center' },
      { label: 'Middle Left', value: 'middle_left' },
      { label: 'Middle Right', value: 'middle_right' }
    ]
  });

  var TZ = 'America/New_York';
  var last = bars.length - 1;

  // ─── HELPER: Minute-precision session check ───
  var inSessionMinute = (i, startH, startM, endH, endM) => {
    var h = ctx.time.hourIn(i, TZ);
    var m = ctx.time.minuteIn(i, TZ);
    var t = h * 60 + m;
    var s = startH * 60 + startM;
    var e = endH * 60 + endM;
    if (s < e) return t >= s && t < e;
    return t >= s || t < e;
  };

  // ─── HELPER: Parse rgba/hex to get a fill version ───
  var toFill = (colorStr, alpha) => {
    var rgbaMatch = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
    if (rgbaMatch) {
      return 'rgba(' + rgbaMatch[1] + ',' + rgbaMatch[2] + ',' + rgbaMatch[3] + ',' + alpha + ')';
    }
    if (colorStr.charAt(0) === '#') {
      var r = parseInt(colorStr.slice(1, 3), 16);
      var g = parseInt(colorStr.slice(3, 5), 16);
      var b = parseInt(colorStr.slice(5, 7), 16);
      return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
    }
    return colorStr;
  };

  var toBorder = (colorStr) => {
    var rgbaMatch = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
    if (rgbaMatch) {
      return 'rgba(' + rgbaMatch[1] + ',' + rgbaMatch[2] + ',' + rgbaMatch[3] + ',0.8)';
    }
    return colorStr;
  };

  // ─── SESSION DEFINITIONS ───
  var sessions = [
    { name: 'Asia',    label: '8pm-2am',       startH: 20, startM: 0,  endH: 2,  endM: 0,  color: asiaColor },
    { name: 'London',  label: '2-8am',         startH: 2,  startM: 0,  endH: 8,  endM: 0,  color: londonColor },
    { name: 'NY Open', label: '8-9am',         startH: 8,  startM: 0,  endH: 9,  endM: 0,  color: nyOpenColor },
    { name: 'NY IB',   label: '9:30-10:30am',  startH: 9,  startM: 30, endH: 10, endM: 30, color: nyIBColor },
  ];

  // ─── ADR CALCULATION ───
  var daily;
  try {
    daily = ctx.request('1D');
  } catch (e) {
    ctx.log('Daily Profile requires an intraday timeframe');
    return;
  }

  var confirmedBars = daily.bars;
  if (!confirmedBars || confirmedBars.length < 1) return;

  var usable = confirmedBars.slice(-adrDays);
  var adrSum = 0;
  for (var di = 0; di < usable.length; di++) {
    adrSum += usable[di].high - usable[di].low;
  }
  var adr = usable.length > 0 ? adrSum / usable.length : 0;

  var todayOpen, todayStartBar;
  if (daily.forming) {
    todayOpen = daily.forming.open;
    todayStartBar = daily.forming.startBar;
  } else {
    var lastDay = confirmedBars[confirmedBars.length - 1];
    todayOpen = lastDay.open;
    todayStartBar = lastDay.startIdx || 0;
  }

  var adrHigh = todayOpen + adr;
  var adrLow = todayOpen - adr;

  // ─── ADR LINES ───
  if (showADR) {
    ctx.line(todayStartBar, adrHigh, last + 10, adrHigh, {
      color: adrColor, lineWidth: lineWidth, lineStyle: adrStyle
    });
    ctx.line(todayStartBar, adrLow, last + 10, adrLow, {
      color: adrColor, lineWidth: lineWidth, lineStyle: adrStyle
    });
    if (showADRLabels) {
      ctx.label(last + 10, adrHigh, 'ADR High (' + adrDays + 'D)', {
        color: 'rgba(0,0,0,0)', textColor: adrColor, size: 10, position: 'above', style: 'none'
      });
      ctx.label(last + 10, adrLow, 'ADR Low (' + adrDays + 'D)', {
        color: 'rgba(0,0,0,0)', textColor: adrColor, size: 10, position: 'below', style: 'none'
      });
    }
  }

  if (showMedian) {
    ctx.line(todayStartBar, todayOpen, last + 10, todayOpen, {
      color: medianColor, lineWidth: lineWidth, lineStyle: medianStyle
    });
    if (showMedianLabel) {
      ctx.label(last + 10, todayOpen, 'ADR Median', {
        color: 'rgba(0,0,0,0)', textColor: medianColor, size: 10, position: 'above', style: 'none'
      });
    }
  }

  // ─── FIND CUTOFF BAR ───
  var dayCount = 0;
  var cutoffBar = 0;
  for (var ci = last; ci >= 1; ci--) {
    if (ctx.time.isNewSession(ci)) {
      dayCount++;
      if (dayCount > sessionDays) { cutoffBar = ci; break; }
    }
  }

  // ─── SESSION TRACKING ───
  var sessionStats = {};
  for (var si = 0; si < sessions.length; si++) {
    sessionStats[sessions[si].name] = {};
  }

  var active = {};
  var todaySessions = {};
  var todayActive = {};

  var todayCutoff = 0;
  for (var tc = last; tc >= 1; tc--) {
    if (ctx.time.isNewSession(tc)) { todayCutoff = tc; break; }
  }

  for (var i = cutoffBar; i <= last; i++) {
    for (var si2 = 0; si2 < sessions.length; si2++) {
      var sess = sessions[si2];
      var key = sess.name;
      var inSess = inSessionMinute(i, sess.startH, sess.startM, sess.endH, sess.endM);

      if (inSess) {
        if (!active[key]) {
          active[key] = {
            high: bars[i].high,
            low: bars[i].low,
            open: bars[i].open,
            startBar: i
          };
        } else {
          if (bars[i].high > active[key].high) active[key].high = bars[i].high;
          if (bars[i].low < active[key].low) active[key].low = bars[i].low;
        }
      } else if (active[key]) {
        var completed = active[key];
        completed.endBar = i - 1;
        completed.range = completed.high - completed.low;
        completed.name = key;
        completed.color = sess.color;
        completed.label = sess.label;

        var dow = ctx.time.dayOfWeek(completed.endBar);
        if (dow >= 1 && dow <= 5) {
          if (!sessionStats[key][dow]) sessionStats[key][dow] = [];
          sessionStats[key][dow].push(completed.range);
        }

        if (completed.startBar >= todayCutoff) {
          todaySessions[key] = completed;
        }

        active[key] = null;
      }
    }
  }

  for (var ak in active) {
    if (active[ak] && active[ak].startBar >= todayCutoff) {
      todayActive[ak] = active[ak];
    }
  }

  // ─── DRAW SESSION BOXES ───
  if (showSessionBoxes) {
    for (var sk in todaySessions) {
      var s = todaySessions[sk];
      var si3 = sessions.filter((x) => x.name === sk)[0];
      ctx.box(s.startBar, s.high, s.endBar, s.low, {
        borderColor: toBorder(si3.color),
        fillColor: toFill(si3.color, 0.08),
        borderWidth: 1,
        borderStyle: 'solid'
      });
      if (showSessionLabels) {
        var boxCenter = Math.floor((s.startBar + s.endBar) / 2);
        ctx.label(boxCenter, s.high, sk + ' (' + si3.label + ')', {
          color: 'rgba(0,0,0,0)', textColor: toBorder(si3.color),
          size: 9, position: 'above', style: 'none'
        });
      }
    }

    for (var ak2 in todayActive) {
      var a = todayActive[ak2];
      var si4 = sessions.filter((x) => x.name === ak2)[0];
      ctx.box(a.startBar, a.high, last, a.low, {
        borderColor: toBorder(si4.color),
        fillColor: toFill(si4.color, 0.08),
        borderWidth: 1,
        borderStyle: 'dashed'
      });
      if (showSessionLabels) {
        var boxCenter2 = Math.floor((a.startBar + last) / 2);
        ctx.label(boxCenter2, a.high, ak2 + ' (' + si4.label + ')', {
          color: 'rgba(0,0,0,0)', textColor: toBorder(si4.color),
          size: 9, position: 'above', style: 'none'
        });
      }
    }
  }

  // ─── ENGULFMENT DETECTION ───
  var condition = 'None';
  var asia = todaySessions['Asia'];
  var london = todaySessions['London'];

  if (asia && london) {
    if (london.high > asia.high && london.low < asia.low) {
      condition = 'London Engulfs Asia';
    } else if (asia.high > london.high && asia.low < london.low) {
      condition = 'Asia Engulfs London';
    } else if (london.high > asia.high && london.low > asia.low) {
      condition = 'London Partially Engulfs Upwards';
    } else if (london.low < asia.low && london.high < asia.high) {
      condition = 'London Partially Engulfs Downwards';
    } else {
      condition = 'No Engulfment';
    }
  }

  // ─── SESSION STATS TABLE ───
  if (showStats) {
    var tbl = ctx.table(statsPos, 7, 5, {
      bgcolor: 'rgba(13,17,28,0.92)',
      borderColor: 'rgba(255,255,255,0.10)',
      borderWidth: 1,
      textColor: '#aaa',
      fontSize: 10,
      cellPadding: 4,
      paddingTop: 0,
      paddingBottom: 0
    });

    var headers = ['Session', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Total'];
    for (var h = 0; h < headers.length; h++) {
      ctx.cell(tbl, 0, h, headers[h], {
        textColor: '#ccc', fontSize: 9, bgcolor: 'rgba(255,255,255,0.06)', align: 'center'
      });
    }

    for (var sr = 0; sr < sessions.length; sr++) {
      var sessName = sessions[sr].name;
      ctx.cell(tbl, sr + 1, 0, sessName, {
        textColor: '#fff', fontSize: 9, bgcolor: toFill(sessions[sr].color, 0.3), align: 'center'
      });

      var allRanges = [];
      for (var d = 1; d <= 5; d++) {
        var ranges = sessionStats[sessName][d] || [];
        var avg = 0;
        if (ranges.length > 0) {
          var sum = 0;
          for (var ri = 0; ri < ranges.length; ri++) sum += ranges[ri];
          avg = sum / ranges.length;
        }
        var cellText = avg > 0 ? avg.toFixed(2) : 'N/A';
        if (showSampleSize && ranges.length > 0) {
          cellText += ' [' + ranges.length + ']';
        }
        ctx.cell(tbl, sr + 1, d, cellText, {
          textColor: '#ccc', fontSize: 9, bgcolor: toFill(sessions[sr].color, 0.15), align: 'center'
        });
        for (var ri2 = 0; ri2 < ranges.length; ri2++) allRanges.push(ranges[ri2]);
      }

      var totalSum = 0;
      for (var ti = 0; ti < allRanges.length; ti++) totalSum += allRanges[ti];
      var totalAvg = allRanges.length > 0 ? totalSum / allRanges.length : 0;
      var totalText = totalAvg > 0 ? totalAvg.toFixed(2) : 'N/A';
      if (showSampleSize && allRanges.length > 0) {
        totalText += ' [' + allRanges.length + ']';
      }
      ctx.cell(tbl, sr + 1, 6, totalText, {
        textColor: '#ccc', fontSize: 9, bgcolor: toFill(sessions[sr].color, 0.15), align: 'center'
      });
    }
  }

  // ─── PROBABILITIES TABLE ───
  if (showProbTable) {
    var probRows = [];

    if (condition === 'London Engulfs Asia') {
      probRows = [
        ['NY high takes London high', '72.33%'],
        ['NY low takes London low', '71.12%'],
        ['NY engulfs overnight session', '44.13%'],
        ['NY Cash (9:30-4:00) Bullish', '54.80%'],
        ['NY Cash (9:30-4:00) Bearish', '45.20%'],
      ];
    } else if (condition === 'Asia Engulfs London') {
      probRows = [
        ['Occurrence rate', '5.36%'],
        ['NY high takes Asia high', '75.86%'],
        ['NY low takes Asia low', '65.52%'],
        ['NY engulfs overnight session', '44.83%'],
        ['NY Cash (9:30-4:00) Bullish', '58.42%'],
        ['NY Cash (9:30-4:00) Bearish', '41.58%'],
      ];
    } else if (condition === 'London Partially Engulfs Upwards') {
      probRows = [
        ['NY high takes London high', '81.51%'],
        ['NY low takes London low', '64.45%'],
        ['NY low takes Asian low', '53.16%'],
        ['NY engulfs overnight session', '37.07%'],
        ['NY Cash (9:30-4:00) Bullish', '55.52%'],
        ['NY Cash (9:30-4:00) Bearish', '44.48%'],
      ];
    } else if (condition === 'London Partially Engulfs Downwards') {
      probRows = [
        ['NY low takes London low', '75.29%'],
        ['NY high takes London high', '68.80%'],
        ['NY high takes Asian high', '56.44%'],
        ['NY engulfs overnight session', '34.40%'],
        ['NY Cash (9:30-4:00) Bullish', '52.99%'],
        ['NY Cash (9:30-4:00) Bearish', '47.01%'],
      ];
    } else {
      probRows = [
        ['Waiting for condition...', ''],
      ];
    }

    var ptbl = ctx.table(probPos, 2, probRows.length + 1, {
      bgcolor: 'rgba(13,17,28,0.92)',
      borderColor: 'rgba(255,255,255,0.10)',
      borderWidth: 1,
      textColor: '#aaa',
      fontSize: 9,
      cellPadding: 4,
      paddingTop: 0,
      paddingBottom: 0
    });

    ctx.cell(ptbl, 0, 0, condition, {
      textColor: '#fff', fontSize: 9, bgcolor: 'rgba(255,255,255,0.08)', align: 'center'
    });
    ctx.cell(ptbl, 0, 1, '', {
      bgcolor: 'rgba(255,255,255,0.08)', align: 'center'
    });

    for (var pr = 0; pr < probRows.length; pr++) {
      ctx.cell(ptbl, pr + 1, 0, probRows[pr][0], {
        textColor: '#ccc', fontSize: 9, align: 'left'
      });
      ctx.cell(ptbl, pr + 1, 1, probRows[pr][1], {
        textColor: '#fff', fontSize: 9, align: 'center'
      });
    }
  }
}