Agent skill

amt-strategy

Auction Market Theory (AMT) and Market Profile - volume-based price discovery, value area, POC, TPO charts, and profile analysis for trading bot implementation

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/amt-strategy

SKILL.md

Auction Market Theory (AMT) & Market Profile

Overview

Auction Market Theory (AMT) views financial markets as continuous two-way auctions where price discovery occurs through negotiation between buyers and sellers. Market Profile is the visual representation of this theory, showing where and for how long prices traded throughout a session.

Core Philosophy: Markets constantly seek fair value through price discovery. When buyers and sellers agree on value, the market consolidates (balance). When they disagree, the market trends (imbalance).

Use this skill when:

  • Building trading bots that use volume analysis
  • Understanding value area and point of control (POC) concepts
  • Coding market profile algorithms for Discord bots
  • Implementing auction-based trading logic
  • Analyzing market balance/imbalance

Current as of: January 2025


Core AMT Concepts

1. TPO (Time Price Opportunity)

What it is: A measurement of how much time the market spends at each price level. Each 30-minute period is represented by a letter (A-Z), creating a visual distribution.

Visualization:

5850  [    C DEF     ]  <- Low TPO count (rejection)
5845  [ AB CDEF GH   ]
5840  [ ABCDEFGH IJ  ]  <- High TPO count (acceptance)
5835  [ ABCDEFGHIJ K ]  <- POC (highest TPO)
5830  [  BCDEFGHIJ   ]
5825  [    DEFGH     ]

Data Structure:

typescript
interface TPOProfile {
  date: string; // Trading session date
  periods: TPOPeriod[]; // 30-min periods
  distribution: Map<number, string[]>; // price -> letters
}

interface TPOPeriod {
  letter: string; // A-Z (A=first 30min, B=second, etc.)
  startTime: Date;
  endTime: Date;
  high: number;
  low: number;
}

// Build TPO profile from intraday candles
function buildTPOProfile(
  candles: Candle[], // 1-min or 5-min candles
  sessionStart: Date,
  periodMinutes: number = 30
): TPOProfile {
  const periods: TPOPeriod[] = [];
  const distribution = new Map<number, string[]>();

  // Group candles into 30-minute periods
  const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
  let periodIndex = 0;

  for (let i = 0; i < candles.length; i += periodMinutes) {
    const periodCandles = candles.slice(i, i + periodMinutes);
    if (periodCandles.length === 0) break;

    const periodHigh = Math.max(...periodCandles.map(c => c.high));
    const periodLow = Math.min(...periodCandles.map(c => c.low));
    const letter = letters[periodIndex % letters.length];

    periods.push({
      letter,
      startTime: new Date(periodCandles[0].timestamp),
      endTime: new Date(periodCandles[periodCandles.length - 1].timestamp),
      high: periodHigh,
      low: periodLow
    });

    // Add TPO letter to each price level in range
    const tickSize = 0.25; // Adjust based on instrument
    for (let price = periodLow; price <= periodHigh; price += tickSize) {
      const roundedPrice = Math.round(price / tickSize) * tickSize;
      if (!distribution.has(roundedPrice)) {
        distribution.set(roundedPrice, []);
      }
      distribution.get(roundedPrice)!.push(letter);
    }

    periodIndex++;
  }

  return {
    date: sessionStart.toISOString().split('T')[0],
    periods,
    distribution
  };
}

2. Value Area (VA)

What it is: The price range containing 70% of the session's TPO count (or volume). Represents where the market spent most of its time, indicating "fair value."

Components:

  • VAH (Value Area High): Upper boundary of value area
  • VAL (Value Area Low): Lower boundary of value area
  • POC (Point of Control): Price level with most TPO/volume (center of value)

TypeScript Implementation:

typescript
interface ValueArea {
  high: number; // VAH
  low: number; // VAL
  poc: number; // Point of Control
  percentage: number; // Should be ~70%
}

function calculateValueArea(
  tpoProfile: TPOProfile,
  targetPercentage: number = 0.70
): ValueArea {
  // Count total TPOs
  let totalTPOs = 0;
  const priceTPOCounts = new Map<number, number>();

  tpoProfile.distribution.forEach((letters, price) => {
    const count = letters.length;
    priceTPOCounts.set(price, count);
    totalTPOs += count;
  });

  // Find POC (price with highest TPO count)
  let poc = 0;
  let maxTPOs = 0;
  priceTPOCounts.forEach((count, price) => {
    if (count > maxTPOs) {
      maxTPOs = count;
      poc = price;
    }
  });

  // Build value area from POC outward
  const targetTPOs = totalTPOs * targetPercentage;
  let currentTPOs = priceTPOCounts.get(poc) || 0;

  let vah = poc;
  let val = poc;

  const sortedPrices = Array.from(priceTPOCounts.keys()).sort((a, b) => a - b);
  const pocIndex = sortedPrices.indexOf(poc);

  let upperIndex = pocIndex + 1;
  let lowerIndex = pocIndex - 1;

  // Expand value area until we capture 70% of TPOs
  while (currentTPOs < targetTPOs && (upperIndex < sortedPrices.length || lowerIndex >= 0)) {
    const upperPrice = sortedPrices[upperIndex];
    const lowerPrice = sortedPrices[lowerIndex];

    const upperTPOs = upperPrice !== undefined ? (priceTPOCounts.get(upperPrice) || 0) : 0;
    const lowerTPOs = lowerPrice !== undefined ? (priceTPOCounts.get(lowerPrice) || 0) : 0;

    // Expand in direction with more TPOs
    if (upperTPOs >= lowerTPOs && upperIndex < sortedPrices.length) {
      vah = upperPrice;
      currentTPOs += upperTPOs;
      upperIndex++;
    } else if (lowerIndex >= 0) {
      val = lowerPrice;
      currentTPOs += lowerTPOs;
      lowerIndex--;
    } else {
      break;
    }
  }

  return {
    high: vah,
    low: val,
    poc,
    percentage: currentTPOs / totalTPOs
  };
}

Volume-Based Value Area (preferred for futures):

typescript
function calculateVolumeValueArea(
  candles: Candle[], // Intraday candles with volume
  tickSize: number = 0.25
): ValueArea {
  // Aggregate volume by price level
  const volumeByPrice = new Map<number, number>();
  let totalVolume = 0;

  candles.forEach(candle => {
    // Distribute candle volume across its price range
    const priceRange = candle.high - candle.low;
    const numTicks = Math.ceil(priceRange / tickSize);
    const volumePerTick = candle.volume / Math.max(numTicks, 1);

    for (let price = candle.low; price <= candle.high; price += tickSize) {
      const roundedPrice = Math.round(price / tickSize) * tickSize;
      volumeByPrice.set(
        roundedPrice,
        (volumeByPrice.get(roundedPrice) || 0) + volumePerTick
      );
      totalVolume += volumePerTick;
    }
  });

  // Find POC
  let poc = 0;
  let maxVolume = 0;
  volumeByPrice.forEach((volume, price) => {
    if (volume > maxVolume) {
      maxVolume = volume;
      poc = price;
    }
  });

  // Build value area from POC (same algorithm as TPO, but with volume)
  const targetVolume = totalVolume * 0.70;
  let currentVolume = volumeByPrice.get(poc) || 0;

  let vah = poc;
  let val = poc;

  const sortedPrices = Array.from(volumeByPrice.keys()).sort((a, b) => a - b);
  const pocIndex = sortedPrices.indexOf(poc);

  let upperIndex = pocIndex + 1;
  let lowerIndex = pocIndex - 1;

  while (currentVolume < targetVolume && (upperIndex < sortedPrices.length || lowerIndex >= 0)) {
    const upperPrice = sortedPrices[upperIndex];
    const lowerPrice = sortedPrices[lowerIndex];

    const upperVolume = upperPrice !== undefined ? (volumeByPrice.get(upperPrice) || 0) : 0;
    const lowerVolume = lowerPrice !== undefined ? (volumeByPrice.get(lowerPrice) || 0) : 0;

    if (upperVolume >= lowerVolume && upperIndex < sortedPrices.length) {
      vah = upperPrice;
      currentVolume += upperVolume;
      upperIndex++;
    } else if (lowerIndex >= 0) {
      val = lowerPrice;
      currentVolume += lowerVolume;
      lowerIndex--;
    } else {
      break;
    }
  }

  return { high: vah, low: val, poc, percentage: currentVolume / totalVolume };
}

3. Initial Balance (IB)

What it is: The price range established during the first hour of trading. Provides crucial context for the day's character.

Significance:

  • IB Expansion: Price breaks and holds above/below IB → Trend day likely
  • IB Rejection: Price tests IB extremes and returns → Range day likely
  • IB Size: Narrow IB → Potential for expansion; Wide IB → Likely consolidation

TypeScript Implementation:

typescript
interface InitialBalance {
  high: number;
  low: number;
  range: number;
  sessionStart: Date;
  broken: 'above' | 'below' | 'neither' | 'both';
}

function getInitialBalance(
  candles: Candle[],
  sessionStart: Date,
  ibDurationMinutes: number = 60
): InitialBalance {
  const ibEndTime = new Date(sessionStart.getTime() + ibDurationMinutes * 60 * 1000);

  // Filter candles within IB period
  const ibCandles = candles.filter(c => {
    const candleTime = new Date(c.timestamp);
    return candleTime >= sessionStart && candleTime < ibEndTime;
  });

  if (ibCandles.length === 0) {
    throw new Error('No candles found during initial balance period');
  }

  const high = Math.max(...ibCandles.map(c => c.high));
  const low = Math.min(...ibCandles.map(c => c.low));

  // Check if IB was broken by subsequent candles
  const postIBCandles = candles.filter(c => new Date(c.timestamp) >= ibEndTime);

  let brokenAbove = false;
  let brokenBelow = false;

  postIBCandles.forEach(candle => {
    if (candle.high > high) brokenAbove = true;
    if (candle.low < low) brokenBelow = true;
  });

  let broken: 'above' | 'below' | 'neither' | 'both' = 'neither';
  if (brokenAbove && brokenBelow) broken = 'both';
  else if (brokenAbove) broken = 'above';
  else if (brokenBelow) broken = 'below';

  return {
    high,
    low,
    range: high - low,
    sessionStart,
    broken
  };
}

4. Profile Types (Day Types)

Market Profile days fall into distinct categories based on IB behavior and distribution shape:

Type Classification:

typescript
enum ProfileType {
  NORMAL = 'normal',
  NORMAL_VARIATION = 'normal_variation',
  TREND_DAY = 'trend_day',
  NEUTRAL_DAY = 'neutral_day',
  DOUBLE_DISTRIBUTION = 'double_distribution'
}

interface ProfileCharacteristics {
  type: ProfileType;
  ibBroken: boolean;
  ibBreakDirection: 'up' | 'down' | 'both' | 'neither';
  ibBreakCount: number; // Number of times IB was broken
  valueAreaPosition: 'upper' | 'middle' | 'lower'; // Relative to full range
  confidence: number; // 0-1
}

function classifyProfileType(
  tpoProfile: TPOProfile,
  ib: InitialBalance,
  va: ValueArea,
  candles: Candle[]
): ProfileCharacteristics {
  const sessionHigh = Math.max(...candles.map(c => c.high));
  const sessionLow = Math.min(...candles.map(c => c.low));
  const fullRange = sessionHigh - sessionLow;

  // Count IB breaks
  let ibBreakCount = 0;
  let lastBreakDirection: 'up' | 'down' | 'neither' = 'neither';

  candles.forEach((candle, i) => {
    if (i === 0) return;

    const prev = candles[i - 1];

    // Break above IB
    if (prev.high <= ib.high && candle.high > ib.high) {
      ibBreakCount++;
      lastBreakDirection = 'up';
    }

    // Break below IB
    if (prev.low >= ib.low && candle.low < ib.low) {
      ibBreakCount++;
      lastBreakDirection = 'down';
    }
  });

  // Determine value area position in full range
  const vaMiddle = (va.high + va.low) / 2;
  const rangePosition = (vaMiddle - sessionLow) / fullRange;

  let valueAreaPosition: 'upper' | 'middle' | 'lower';
  if (rangePosition > 0.66) valueAreaPosition = 'upper';
  else if (rangePosition < 0.33) valueAreaPosition = 'lower';
  else valueAreaPosition = 'middle';

  // Classify profile type
  let type: ProfileType;
  let confidence = 0.5;

  if (ibBreakCount === 0) {
    // Normal Day: IB not broken, value area centered
    type = ProfileType.NORMAL;
    confidence = valueAreaPosition === 'middle' ? 0.9 : 0.6;
  } else if (ibBreakCount === 1) {
    // Normal Variation: IB broken once in one direction
    type = ProfileType.NORMAL_VARIATION;
    confidence = 0.8;
  } else if (ibBreakCount >= 2 && lastBreakDirection !== 'neither') {
    // Trend Day: Multiple IB breaks in same direction
    type = ProfileType.TREND_DAY;
    confidence = 0.85;
  } else if (ibBreakCount >= 2 && ib.broken === 'both') {
    // Double Distribution: Value shifted dramatically (news event)
    type = ProfileType.DOUBLE_DISTRIBUTION;
    confidence = 0.75;
  } else {
    // Neutral/Non-trend day
    type = ProfileType.NEUTRAL_DAY;
    confidence = 0.6;
  }

  return {
    type,
    ibBroken: ibBreakCount > 0,
    ibBreakDirection: lastBreakDirection,
    ibBreakCount,
    valueAreaPosition,
    confidence
  };
}

Profile Type Descriptions:

  1. Normal Day: IB not broken, value area centered, classic bell curve distribution

    • Trading: Mean reversion around VA, fade extremes
  2. Normal Variation: IB broken once, value area shifts toward break direction

    • Trading: Trade in direction of IB break, look for continuation
  3. Trend Day: IB broken multiple times (≥2) in same direction, value area at extreme

    • Trading: Momentum trading, avoid counter-trend trades, ride the trend
  4. Neutral Day: Choppy, multiple IB breaks in both directions, wide distribution

    • Trading: Difficult to trade, consider staying out or tight scalps
  5. Double Distribution: Two separate value areas (typically due to news)

    • Trading: Identify new value area, trade from new POC

AMT Trading Strategies

1. Balanced Market (Mean Reversion)

When to use: Normal days, neutral days, or when price is oscillating around VA.

Strategy:

  • Price above VAH → Look for shorts back to POC
  • Price below VAL → Look for longs back to POC
  • Price at POC → Wait for directional clues

TypeScript Implementation:

typescript
interface AMTSignal {
  direction: 'long' | 'short' | 'neutral';
  entry: number;
  stopLoss: number;
  target: number;
  reasoning: string[];
  confidence: number;
}

function generateBalancedMarketSignal(
  currentPrice: number,
  va: ValueArea,
  profile: ProfileCharacteristics
): AMTSignal | null {
  // Only trade balanced markets (normal, neutral days)
  if (profile.type === ProfileType.TREND_DAY) {
    return null; // Don't fade trend days
  }

  const vaRange = va.high - va.low;
  const atr = vaRange * 0.3; // Rough ATR estimate

  // Price above VAH → short back to POC
  if (currentPrice > va.high) {
    const distance = currentPrice - va.high;
    const confidence = Math.min(distance / vaRange, 1); // Further = higher confidence

    return {
      direction: 'short',
      entry: currentPrice,
      target: va.poc,
      stopLoss: currentPrice + atr,
      reasoning: [
        `Price above value area high (${va.high.toFixed(2)})`,
        'Mean reversion expected back to POC',
        `Profile type: ${profile.type} (favorable for mean reversion)`
      ],
      confidence: confidence * 0.7
    };
  }

  // Price below VAL → long back to POC
  if (currentPrice < va.low) {
    const distance = va.low - currentPrice;
    const confidence = Math.min(distance / vaRange, 1);

    return {
      direction: 'long',
      entry: currentPrice,
      target: va.poc,
      stopLoss: currentPrice - atr,
      reasoning: [
        `Price below value area low (${va.low.toFixed(2)})`,
        'Mean reversion expected back to POC',
        `Profile type: ${profile.type} (favorable for mean reversion)`
      ],
      confidence: confidence * 0.7
    };
  }

  // Price within value area → neutral
  return {
    direction: 'neutral',
    entry: currentPrice,
    target: currentPrice,
    stopLoss: currentPrice,
    reasoning: ['Price within value area', 'No clear edge for entry'],
    confidence: 0
  };
}

2. Imbalanced Market (Trend Following)

When to use: Trend days, normal variation days with IB break, strong directional conviction.

Strategy:

  • IB broken above + price holding above IB high → Look for longs on pullbacks
  • IB broken below + price holding below IB low → Look for shorts on pullbacks
  • Avoid counter-trend trades on trend days

TypeScript Implementation:

typescript
function generateImbalancedMarketSignal(
  currentPrice: number,
  ib: InitialBalance,
  va: ValueArea,
  profile: ProfileCharacteristics,
  candles: Candle[]
): AMTSignal | null {
  // Only trade imbalanced markets (trend days, normal variation)
  if (profile.type !== ProfileType.TREND_DAY && profile.type !== ProfileType.NORMAL_VARIATION) {
    return null;
  }

  const atr = ib.range * 0.5; // Rough ATR from IB range
  const recentCandle = candles[candles.length - 1];

  // Bullish imbalance: IB broken above
  if (profile.ibBreakDirection === 'up' && currentPrice > ib.high) {
    // Wait for pullback to IB high or VAH
    const pullbackTarget = Math.max(ib.high, va.high);
    const distanceFromPullback = currentPrice - pullbackTarget;

    if (distanceFromPullback < atr * 0.5) { // Close to pullback level
      return {
        direction: 'long',
        entry: pullbackTarget,
        target: currentPrice + (currentPrice - ib.low), // Project IB range higher
        stopLoss: ib.high - atr,
        reasoning: [
          `Trend day with IB broken above (${ib.high.toFixed(2)})`,
          'Pullback to IB high provides entry',
          `Value area in ${profile.valueAreaPosition} third (confirms trend)`
        ],
        confidence: 0.75
      };
    }
  }

  // Bearish imbalance: IB broken below
  if (profile.ibBreakDirection === 'down' && currentPrice < ib.low) {
    const pullbackTarget = Math.min(ib.low, va.low);
    const distanceFromPullback = pullbackTarget - currentPrice;

    if (distanceFromPullback < atr * 0.5) {
      return {
        direction: 'short',
        entry: pullbackTarget,
        target: currentPrice - (ib.high - currentPrice), // Project IB range lower
        stopLoss: ib.low + atr,
        reasoning: [
          `Trend day with IB broken below (${ib.low.toFixed(2)})`,
          'Pullback to IB low provides entry',
          `Value area in ${profile.valueAreaPosition} third (confirms trend)`
        ],
        confidence: 0.75
      };
    }
  }

  return null; // No clear setup
}

3. Complete AMT Analysis Function

typescript
async function performAMTAnalysis(
  symbol: string,
  sessionStart: Date,
  candles: Candle[]
): Promise<{
  profile: TPOProfile;
  va: ValueArea;
  ib: InitialBalance;
  characteristics: ProfileCharacteristics;
  signal: AMTSignal | null;
}> {
  // 1. Build TPO profile
  const profile = buildTPOProfile(candles, sessionStart);

  // 2. Calculate value area (prefer volume-based for futures)
  const va = calculateVolumeValueArea(candles);

  // 3. Get initial balance
  const ib = getInitialBalance(candles, sessionStart);

  // 4. Classify profile type
  const characteristics = classifyProfileType(profile, ib, va, candles);

  // 5. Generate signal based on market type
  const currentPrice = candles[candles.length - 1].close;

  let signal: AMTSignal | null = null;

  if (characteristics.type === ProfileType.TREND_DAY ||
      characteristics.type === ProfileType.NORMAL_VARIATION) {
    signal = generateImbalancedMarketSignal(currentPrice, ib, va, characteristics, candles);
  } else {
    signal = generateBalancedMarketSignal(currentPrice, va, characteristics);
  }

  return { profile, va, ib, characteristics, signal };
}

Integration with Discord Bot

Example: AMT Analysis Command

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

const amtAnalyzeCommand = new SlashCommandBuilder()
  .setName('amt-analyze')
  .setDescription('Analyze market using Auction Market Theory')
  .addStringOption(option =>
    option.setName('symbol')
      .setDescription('Symbol to analyze')
      .setRequired(true));

async function handleAMTAnalyze(interaction) {
  const symbol = interaction.options.getString('symbol');

  await interaction.deferReply();

  try {
    // Fetch intraday data for current session
    const sessionStart = getTodaySessionStart(); // 9:30 AM for ES/NQ
    const candles = await fetchIntradayCandles(symbol, sessionStart);

    const analysis = await performAMTAnalysis(symbol, sessionStart, candles);

    const embed = new EmbedBuilder()
      .setTitle(`📊 AMT Analysis: ${symbol}`)
      .setColor(getColorForProfileType(analysis.characteristics.type))
      .addFields([
        {
          name: 'Profile Type',
          value: `**${analysis.characteristics.type.toUpperCase()}**`,
          inline: true
        },
        {
          name: 'IB Status',
          value: analysis.ib.broken !== 'neither'
            ? `Broken ${analysis.ib.broken}`
            : 'Intact',
          inline: true
        },
        {
          name: 'Confidence',
          value: `${(analysis.characteristics.confidence * 100).toFixed(0)}%`,
          inline: true
        },
        {
          name: 'Initial Balance',
          value: `${analysis.ib.high.toFixed(2)} - ${analysis.ib.low.toFixed(2)} (${analysis.ib.range.toFixed(2)})`,
          inline: false
        },
        {
          name: 'Value Area',
          value: `VAH: ${analysis.va.high.toFixed(2)}\nPOC: ${analysis.va.poc.toFixed(2)}\nVAL: ${analysis.va.low.toFixed(2)}`,
          inline: true
        },
        {
          name: 'Signal',
          value: analysis.signal
            ? `${analysis.signal.direction.toUpperCase()} @ ${analysis.signal.entry.toFixed(2)}\nTarget: ${analysis.signal.target.toFixed(2)}\nStop: ${analysis.signal.stopLoss.toFixed(2)}`
            : 'No clear setup',
          inline: true
        }
      ]);

    if (analysis.signal && analysis.signal.reasoning.length > 0) {
      embed.addFields([{
        name: 'Reasoning',
        value: analysis.signal.reasoning.join('\n'),
        inline: false
      }]);
    }

    embed.setTimestamp();

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

function getColorForProfileType(type: ProfileType): number {
  switch (type) {
    case ProfileType.TREND_DAY: return 0x00FF00; // Green
    case ProfileType.NORMAL: return 0x0000FF; // Blue
    case ProfileType.NORMAL_VARIATION: return 0xFFFF00; // Yellow
    case ProfileType.NEUTRAL_DAY: return 0x808080; // Gray
    case ProfileType.DOUBLE_DISTRIBUTION: return 0xFF00FF; // Magenta
    default: return 0xFFFFFF;
  }
}

Common Pitfalls When Coding AMT

1. Tick Size and Price Rounding

  • Different instruments have different tick sizes (ES = 0.25, NQ = 0.25, etc.)
  • Always round prices to valid ticks
  • Incorrect rounding causes POC/VA calculation errors

2. Volume Distribution

  • TPO-based profiles work on any timeframe
  • Volume-based profiles require actual volume data (not available in all markets)
  • For futures, prefer volume-based; for forex, use TPO-based

3. Session Definition

  • Clearly define session start (e.g., ES regular session = 9:30 AM ET)
  • Handle overnight sessions separately
  • ETH (Extended Trading Hours) creates different profiles than RTH

4. IB Duration

  • Standard IB = first 60 minutes
  • Some traders use 90 minutes for volatile markets
  • Be consistent with your IB definition

5. Profile Type Misclassification

  • Profile type is subjective and context-dependent
  • Use multiple criteria (IB breaks, VA position, range)
  • Don't over-rely on automated classification

AMT + ICT Integration

Combine AMT with ICT for enhanced analysis:

Complementary Concepts:

  • POC = Institutional Reference: Both represent fair value
  • VAH/VAL = Liquidity Pools: Stops cluster at value area extremes
  • IB Break + Killzone: IB break during NY AM killzone = high probability
  • Trend Day + FVG: FVGs more reliable on trend days

Integration Example:

typescript
async function generateCombinedSignal(
  symbol: string,
  candles: Candle[]
): Promise<{ ict: ICTSignal | null; amt: AMTSignal | null; combined: string }> {
  const ictSignal = await generateICTSignal(symbol, '15m');

  const sessionStart = getTodaySessionStart();
  const amtAnalysis = await performAMTAnalysis(symbol, sessionStart, candles);
  const amtSignal = amtAnalysis.signal;

  let combined = 'No clear setup';

  // Both agree on direction
  if (ictSignal && amtSignal && ictSignal.direction === amtSignal.direction) {
    combined = `STRONG ${ictSignal.direction.toUpperCase()} - ICT and AMT aligned`;
  }

  // ICT says long, AMT says trend day with IB broken up
  else if (ictSignal?.direction === 'long' &&
           amtAnalysis.characteristics.type === ProfileType.TREND_DAY &&
           amtAnalysis.characteristics.ibBreakDirection === 'up') {
    combined = 'STRONG LONG - ICT entry on AMT trend day';
  }

  // Conflicting signals
  else if (ictSignal && amtSignal && ictSignal.direction !== amtSignal.direction) {
    combined = 'CONFLICTING - Stay out or wait for clarity';
  }

  return { ict: ictSignal, amt: amtSignal, combined };
}

After Using This Skill

If building AMT features for your Discord bot:

  1. Implement volume/TPO aggregation (data-intensive, consider caching)
  2. Use databento skill for volume data
  3. Use trading-bot-development skill for Discord integration patterns

If combining with ICT:

  • ICT provides entry precision (FVG, Order Block)
  • AMT provides context (trend day vs balanced day)
  • Use AMT to filter ICT signals (e.g., only take ICT longs on trend days with IB broken up)

For backtesting:

  • Profile characteristics change intraday (calculate real-time)
  • Historical profile data useful for understanding typical behavior
  • Test both balanced and imbalanced strategies separately

References & Further Learning

AMT/Market Profile Resources:

  • Market Profile by J. Peter Steidlmayer (original book)
  • Auction Market Theory guides (FTMO, Topstep, ATAS)
  • Volume Profile analysis (QuantConnect, TradingView implementations)

Code Libraries:

  • py-market-profile (Python): GitHub reference implementation
  • Volume Profile indicators (QuantConnect, TradingView)

2024-2025 Updates:

  • Increased focus on volume-based profiles (more reliable than TPO for futures)
  • Integration with order flow analysis
  • Real-time profile calculations during session

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

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