Agent skill

tjr-smt-divergence

TJR SMT (Structure Market Technicals) divergence detection between ES and NQ futures - identifies high-probability reversals through structural mismatches during market open

Stars 2
Forks 0

Install this agent skill to your Project

npx add-skill https://github.com/Nice-Wolf-Studio/wolf-skills-marketplace/tree/main/tjr-smt-divergence

SKILL.md

TJR SMT Divergence Detection

Overview

SMT (Structure Market Technicals) divergence is a core component of The Jared Ryan (TJR) trading system. It identifies when two highly correlated instruments (ES and NQ futures) diverge structurally, signaling potential market reversals.

Core Principle: When ES and NQ are highly correlated (r ≥ 0.7) but form divergent market structures (e.g., ES makes higher high while NQ makes lower high), this indicates institutional positioning disagreement and often precedes significant reversals.

Use this skill when:

  • Building TJR-based trading bots for ES/NQ futures
  • Detecting market reversals during the market open window (9:30-10:30 AM ET)
  • Identifying which index (ES or NQ) is leading the market
  • Validating directional bias through structural divergence confirmation

Critical Time Window: SMT divergences are most reliable during the NY market open (9:30-10:30 AM ET).


What is SMT Divergence?

Definition

SMT Divergence occurs when two correlated instruments form different structural patterns despite moving together. In TJR, we specifically compare:

  • ES (E-mini S&P 500 futures)
  • NQ (E-mini Nasdaq-100 futures)

Example Bearish SMT:

ES: Makes HIGHER HIGH (HH)
NQ: Makes LOWER HIGH (LH)
→ Bearish divergence → Expect downward reversal

Example Bullish SMT:

ES: Makes LOWER LOW (LL)
NQ: Makes HIGHER LOW (HL)
→ Bullish divergence → Expect upward reversal

Why SMT Works

Fundamental Basis:

  • ES (S&P 500) represents broad market exposure
  • NQ (Nasdaq-100) represents tech-heavy exposure
  • When they diverge structurally, it signals sector rotation or institutional repositioning
  • One index "leads" while the other "lags" → divergence creates trading opportunity

Statistical Validation:

  • Requires rolling correlation ≥ 0.7 (70% correlated movement)
  • If correlation is too low (<0.7), divergence is noise, not signal
  • Time lag tolerance: ≤2 bars between structural formations

Market Structure Classification

To detect SMT, we first classify each instrument's structure using swing points.

Structure Types

Bullish Structures:

  1. Higher High (HH): Current swing high > previous swing high
  2. Higher Low (HL): Current swing low > previous swing low

Bearish Structures: 3. Lower High (LH): Current swing high < previous swing high 4. Lower Low (LL): Current swing low < previous swing low

TypeScript Implementation:

typescript
enum StructureType {
  HIGHER_HIGH = 'HH',
  HIGHER_LOW = 'HL',
  LOWER_HIGH = 'LH',
  LOWER_LOW = 'LL'
}

interface SwingPoint {
  type: 'high' | 'low';
  index: number; // Candle index
  price: number;
  timestamp: number;
}

// Classify structure between two swing points
function classifyStructure(
  prev: SwingPoint,
  current: SwingPoint
): StructureType | null {
  if (prev.type !== current.type) {
    return null; // Can only compare high-to-high or low-to-low
  }

  if (prev.type === 'high') {
    return current.price > prev.price ? StructureType.HIGHER_HIGH : StructureType.LOWER_HIGH;
  } else {
    return current.price > prev.price ? StructureType.HIGHER_LOW : StructureType.LOWER_LOW;
  }
}

// Label all swing structures in a series
function labelSwingStructures(swings: SwingPoint[]): Map<number, StructureType> {
  const structures = new Map<number, StructureType>();

  const highs = swings.filter(s => s.type === 'high');
  const lows = swings.filter(s => s.type === 'low');

  // Compare consecutive highs
  for (let i = 1; i < highs.length; i++) {
    const structure = classifyStructure(highs[i - 1], highs[i]);
    if (structure) {
      structures.set(highs[i].index, structure);
    }
  }

  // Compare consecutive lows
  for (let i = 1; i < lows.length; i++) {
    const structure = classifyStructure(lows[i - 1], lows[i]);
    if (structure) {
      structures.set(lows[i].index, structure);
    }
  }

  return structures;
}

SMT Divergence Detection Algorithm

Core Logic

Bearish SMT Divergence Patterns:

  1. ES makes HH while NQ makes LH (or stays flat)
  2. ES makes HL while NQ makes LL

Bullish SMT Divergence Patterns:

  1. ES makes LL while NQ makes HL (or stays flat)
  2. ES makes LH while NQ makes HH

TypeScript Implementation:

typescript
interface SMTDivergence {
  timestamp: number;
  type: 'bullish' | 'bearish';
  esStructure: StructureType;
  nqStructure: StructureType;
  esSwingIndex: number;
  nqSwingIndex: number;
  correlation: number; // Rolling correlation at time of divergence
  confidence: number; // 0-1 score
  leadingIndex: 'ES' | 'NQ';
}

function detectSMTDivergences(
  esSwings: SwingPoint[],
  nqSwings: SwingPoint[],
  esCandles: Candle[],
  nqCandles: Candle[],
  params: {
    minCorrelation: number; // e.g., 0.7
    correlationWindow: number; // e.g., 60 bars
    timeLagTolerance: number; // e.g., 2 bars
  }
): SMTDivergence[] {
  const divergences: SMTDivergence[] = [];

  // Label structures
  const esStructures = labelSwingStructures(esSwings);
  const nqStructures = labelSwingStructures(nqSwings);

  // Get structure entries
  const esEntries = Array.from(esStructures.entries());
  const nqEntries = Array.from(nqStructures.entries());

  // Compare structures within time lag tolerance
  for (const [esIndex, esStruct] of esEntries) {
    const esTime = esCandles[esIndex].timestamp;

    for (const [nqIndex, nqStruct] of nqEntries) {
      const nqTime = nqCandles[nqIndex].timestamp;

      // Check time lag tolerance (within N bars)
      const timeLag = Math.abs(esIndex - nqIndex);
      if (timeLag > params.timeLagTolerance) continue;

      // Calculate rolling correlation at this point
      const correlation = calculateRollingCorrelation(
        esCandles.slice(Math.max(0, esIndex - params.correlationWindow), esIndex + 1),
        nqCandles.slice(Math.max(0, nqIndex - params.correlationWindow), nqIndex + 1)
      );

      // Correlation must be strong enough
      if (correlation < params.minCorrelation) continue;

      // Check for bearish divergence
      if (
        (esStruct === StructureType.HIGHER_HIGH && nqStruct === StructureType.LOWER_HIGH) ||
        (esStruct === StructureType.HIGHER_LOW && nqStruct === StructureType.LOWER_LOW)
      ) {
        divergences.push({
          timestamp: esTime,
          type: 'bearish',
          esStructure: esStruct,
          nqStructure: nqStruct,
          esSwingIndex: esIndex,
          nqSwingIndex: nqIndex,
          correlation,
          confidence: calculateDivergenceConfidence(esStruct, nqStruct, correlation),
          leadingIndex: determineLeadingIndex(esStruct, nqStruct)
        });
      }

      // Check for bullish divergence
      if (
        (esStruct === StructureType.LOWER_LOW && nqStruct === StructureType.HIGHER_LOW) ||
        (esStruct === StructureType.LOWER_HIGH && nqStruct === StructureType.HIGHER_HIGH)
      ) {
        divergences.push({
          timestamp: esTime,
          type: 'bullish',
          esStructure: esStruct,
          nqStructure: nqStruct,
          esSwingIndex: esIndex,
          nqSwingIndex: nqIndex,
          correlation,
          confidence: calculateDivergenceConfidence(esStruct, nqStruct, correlation),
          leadingIndex: determineLeadingIndex(esStruct, nqStruct)
        });
      }
    }
  }

  return divergences;
}

Rolling Correlation Calculation

Essential for validating that ES and NQ are truly correlated before trusting the divergence.

TypeScript Implementation:

typescript
function calculateRollingCorrelation(
  esCandles: Candle[],
  nqCandles: Candle[]
): number {
  if (esCandles.length !== nqCandles.length || esCandles.length < 2) {
    return 0;
  }

  const esPrices = esCandles.map(c => c.close);
  const nqPrices = nqCandles.map(c => c.close);

  return pearsonCorrelation(esPrices, nqPrices);
}

function pearsonCorrelation(x: number[], y: number[]): number {
  const n = x.length;
  if (n !== y.length || n === 0) return 0;

  const meanX = x.reduce((sum, val) => sum + val, 0) / n;
  const meanY = y.reduce((sum, val) => sum + val, 0) / n;

  let numerator = 0;
  let sumX2 = 0;
  let sumY2 = 0;

  for (let i = 0; i < n; i++) {
    const dx = x[i] - meanX;
    const dy = y[i] - meanY;

    numerator += dx * dy;
    sumX2 += dx * dx;
    sumY2 += dy * dy;
  }

  const denominator = Math.sqrt(sumX2 * sumY2);

  return denominator === 0 ? 0 : numerator / denominator;
}

Leading Index Identification

Critical Concept: When structures diverge, one index is "leading" the market direction.

Rules:

  • ES Leading: ES makes higher lows while NQ makes lower lows → Bullish bias
  • NQ Leading: NQ makes higher lows while ES makes lower lows → Bullish bias (for NQ)
  • Bearish equivalent: Opposite patterns

TypeScript Implementation:

typescript
function determineLeadingIndex(
  esStructure: StructureType,
  nqStructure: StructureType
): 'ES' | 'NQ' {
  // Bullish divergences
  if (esStructure === StructureType.HIGHER_LOW && nqStructure === StructureType.LOWER_LOW) {
    return 'ES'; // ES is stronger (making higher lows)
  }

  if (esStructure === StructureType.LOWER_LOW && nqStructure === StructureType.HIGHER_LOW) {
    return 'NQ'; // NQ is stronger
  }

  // Bearish divergences
  if (esStructure === StructureType.HIGHER_HIGH && nqStructure === StructureType.LOWER_HIGH) {
    return 'ES'; // ES is weaker (making higher highs while NQ weakens)
  }

  if (esStructure === StructureType.LOWER_HIGH && nqStructure === StructureType.HIGHER_HIGH) {
    return 'NQ'; // NQ is weaker
  }

  // Default fallback (shouldn't happen with proper filtering)
  return 'ES';
}

Market Open Window Filter

SMT divergences are most reliable during the 9:30-10:30 AM ET window after cash market open.

TypeScript Implementation:

typescript
function isInMarketOpenWindow(timestamp: number): boolean {
  const est = new Date(timestamp.toLocaleString('en-US', {
    timeZone: 'America/New_York'
  }));

  const hour = est.getHours();
  const minute = est.getMinutes();

  // 9:30 AM to 10:30 AM ET
  const startMinutes = 9 * 60 + 30; // 9:30 AM = 570 minutes
  const endMinutes = 10 * 60 + 30; // 10:30 AM = 630 minutes
  const currentMinutes = hour * 60 + minute;

  return currentMinutes >= startMinutes && currentMinutes < endMinutes;
}

// Filter divergences to market open window only
function filterToMarketOpen(divergences: SMTDivergence[]): SMTDivergence[] {
  return divergences.filter(div => isInMarketOpenWindow(div.timestamp));
}

Confidence Scoring

Not all SMT divergences are equal. Score confidence based on:

  1. Correlation strength (higher = better)
  2. Structure clarity (HH vs LH is clearer than HL vs LL)
  3. Time window (market open = higher confidence)

TypeScript Implementation:

typescript
function calculateDivergenceConfidence(
  esStructure: StructureType,
  nqStructure: StructureType,
  correlation: number
): number {
  let confidence = 0.5; // Base confidence

  // Bonus for strong correlation
  if (correlation >= 0.8) confidence += 0.2;
  else if (correlation >= 0.7) confidence += 0.1;

  // Bonus for clear high divergences (more obvious than low divergences)
  const isHighDivergence =
    (esStructure === StructureType.HIGHER_HIGH && nqStructure === StructureType.LOWER_HIGH) ||
    (esStructure === StructureType.LOWER_HIGH && nqStructure === StructureType.HIGHER_HIGH);

  if (isHighDivergence) confidence += 0.15;

  // Bonus for extreme divergence (HH vs LH is more extreme than HL vs LL)
  const isExtremeDivergence =
    (esStructure === StructureType.HIGHER_HIGH && nqStructure === StructureType.LOWER_HIGH) ||
    (esStructure === StructureType.LOWER_LOW && nqStructure === StructureType.HIGHER_LOW);

  if (isExtremeDivergence) confidence += 0.15;

  return Math.min(confidence, 1.0); // Cap at 1.0
}

Complete SMT Signal Generation

Bringing it all together into a usable signal generator:

TypeScript Implementation:

typescript
interface SMTSignal {
  timestamp: number;
  direction: 'long' | 'short';
  divergence: SMTDivergence;
  reasoning: string[];
  entry?: number; // Suggested entry (midpoint of ES and NQ current prices)
  confidence: number;
}

async function generateSMTSignals(
  esCandles: Candle[],
  nqCandles: Candle[],
  sessionStart: Date
): Promise<SMTSignal[]> {
  const signals: SMTSignal[] = [];

  // 1. Find swing points for both instruments
  const esSwings = findSwingPoints(esCandles, 2); // k=2 lookback (from trading-foundations)
  const nqSwings = findSwingPoints(nqCandles, 2);

  // 2. Detect SMT divergences
  const divergences = detectSMTDivergences(
    esSwings,
    nqSwings,
    esCandles,
    nqCandles,
    {
      minCorrelation: 0.7,
      correlationWindow: 60,
      timeLagTolerance: 2
    }
  );

  // 3. Filter to market open window
  const marketOpenDivergences = filterToMarketOpen(divergences);

  // 4. Convert divergences to trading signals
  for (const div of marketOpenDivergences) {
    const esCurrentPrice = esCandles[div.esSwingIndex].close;
    const nqCurrentPrice = nqCandles[div.nqSwingIndex].close;

    signals.push({
      timestamp: div.timestamp,
      direction: div.type === 'bullish' ? 'long' : 'short',
      divergence: div,
      entry: esCurrentPrice, // Use ES price as entry reference
      confidence: div.confidence,
      reasoning: [
        `SMT ${div.type} divergence detected`,
        `ES: ${div.esStructure}, NQ: ${div.nqStructure}`,
        `Correlation: ${(div.correlation * 100).toFixed(1)}%`,
        `Leading index: ${div.leadingIndex}`,
        `Market open window (9:30-10:30 AM ET)`
      ]
    });
  }

  return signals;
}

Integration with Existing Skills

Reusing Trading-Foundations

Swing Point Detection:

typescript
// Import from trading-foundations skill
import { findSwingPoints, SwingPoint } from '../trading-foundations';

// Use directly in SMT detection
const esSwings = findSwingPoints(esCandles, 2);
const nqSwings = findSwingPoints(nqCandles, 2);

Reusing Trading-Bot-Development

ES/NQ Data Fetching:

typescript
// Import from trading-bot-development
import { DataManager } from '../trading-bot-development';

// Fetch both ES and NQ in parallel
const dataManager = new DataManager(databentoApiKey);
const [esCandles, nqCandles] = await Promise.all([
  dataManager.getCandles('ES', '5m', 200),
  dataManager.getCandles('NQ', '5m', 200)
]);

Signal Schema Compatibility:

typescript
// SMT signals implement the same Signal interface
interface Signal {
  strategy: string; // 'tjr-smt'
  symbol: string; // 'ES' or 'NQ'
  direction: 'long' | 'short';
  entry: number;
  confidence: number;
  reasoning: string[];
  timestamp: Date;
  metadata?: {
    divergence: SMTDivergence;
    correlation: number;
    leadingIndex: string;
  };
}

Discord Bot Integration

Example Slash Command:

typescript
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';

const smtAnalyzeCommand = new SlashCommandBuilder()
  .setName('smt-analyze')
  .setDescription('Analyze ES vs NQ for SMT divergences');

async function handleSMTAnalyze(interaction) {
  await interaction.deferReply();

  try {
    // Fetch data
    const sessionStart = getTodaySessionStart();
    const [esCandles, nqCandles] = await Promise.all([
      dataManager.getCandles('ES', '5m', 200),
      dataManager.getCandles('NQ', '5m', 200)
    ]);

    // Generate signals
    const signals = await generateSMTSignals(esCandles, nqCandles, sessionStart);

    if (signals.length === 0) {
      await interaction.editReply('No SMT divergences detected during market open window.');
      return;
    }

    // Build embed for most recent signal
    const latestSignal = signals[signals.length - 1];

    const embed = new EmbedBuilder()
      .setTitle(`🔀 SMT Divergence Detected`)
      .setColor(latestSignal.direction === 'long' ? 0x00FF00 : 0xFF0000)
      .addFields([
        {
          name: 'Direction',
          value: latestSignal.direction.toUpperCase(),
          inline: true
        },
        {
          name: 'Confidence',
          value: `${(latestSignal.confidence * 100).toFixed(0)}%`,
          inline: true
        },
        {
          name: 'Leading Index',
          value: latestSignal.divergence.leadingIndex,
          inline: true
        },
        {
          name: 'Structures',
          value: `ES: **${latestSignal.divergence.esStructure}**\nNQ: **${latestSignal.divergence.nqStructure}**`,
          inline: false
        },
        {
          name: 'Correlation',
          value: `${(latestSignal.divergence.correlation * 100).toFixed(1)}%`,
          inline: true
        },
        {
          name: 'Entry Suggestion',
          value: latestSignal.entry?.toFixed(2) || 'N/A',
          inline: true
        },
        {
          name: 'Reasoning',
          value: latestSignal.reasoning.join('\n'),
          inline: false
        }
      ])
      .setTimestamp()
      .setFooter({ text: `${signals.length} divergence(s) found in market open window` });

    await interaction.editReply({ embeds: [embed] });
  } catch (error) {
    await interaction.editReply(`Error: ${error.message}`);
  }
}

Common Pitfalls

1. Ignoring Correlation Threshold

  • ❌ Detecting divergences when correlation < 0.7
  • ✅ Always validate rolling correlation ≥ 0.7 before trusting divergence

2. Wrong Time Window

  • ❌ Trading SMT divergences outside 9:30-10:30 AM ET
  • ✅ Filter signals to market open window only

3. Mixing Swing Types

  • ❌ Comparing ES swing high to NQ swing low
  • ✅ Only compare high-to-high or low-to-low

4. Ignoring Time Lag

  • ❌ Comparing ES structure at 9:35 to NQ structure at 10:15
  • ✅ Enforce time lag tolerance (≤2 bars)

5. No Swing Point Validation

  • ❌ Using insufficient lookback (k=1) for swing detection
  • ✅ Use k=2 minimum for reliable swing identification

After Using This Skill

If building TJR trading bot:

  1. Use tjr-liquidity-detection skill for liquidity sweep patterns
  2. Use tjr-session-patterns skill for multi-timeframe confirmation
  3. Use tjr-multi-timeframe-confluence skill to combine SMT with other TJR signals

For multi-strategy bots:

  • SMT divergence provides directional bias confirmation
  • Combine with ICT Fair Value Gaps for precise entry timing
  • Use AMT value area for target identification

For backtesting:

  • Test SMT signals isolated first (measure win rate, avg return)
  • Then test combined with liquidity sweeps
  • Track correlation threshold impact on performance

References

TJR Session Analysis:

  • Nov 13-14, 2025 session notes (~/Dev/tjr-suite/docs/knowledge/tjr/)
  • SMT divergence detection methodology
  • ES vs NQ structural analysis patterns

Related Concepts:

  • Market structure analysis (trading-foundations)
  • Correlation analysis (statistical methods)
  • Leading/lagging index relationships

Last Updated: January 2025 Version: 1.0.0 Part of Wolf Skills Marketplace - TJR Series

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results