Agent skill

trading-foundations

Common trading concepts, terminology, data structures, and utility functions shared across all trading strategies (ICT, AMT, etc.)

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/trading-foundations

SKILL.md

Trading Foundations

Overview

This skill provides common terminology, data structures, and utility functions used across all trading strategies. Reference this skill when you need standardized definitions or reusable code patterns.

Use this skill when:

  • Understanding trading terminology (support/resistance, market structure, etc.)
  • Implementing data models (Candle, Trade, Order structures)
  • Building utility functions (timeframe conversion, price rounding)
  • Ensuring consistency across multiple strategy implementations

Common Terminology

Market Structure

Trend Definitions:

  • Uptrend: Series of higher highs (HH) and higher lows (HL)
  • Downtrend: Series of lower highs (LH) and lower lows (LL)
  • Range/Consolidation: Price oscillates between support and resistance without clear direction

Swing Points:

  • Swing High: A peak in price where price reverses downward (local maximum)
  • Swing Low: A trough in price where price reverses upward (local minimum)
  • Break of Structure (BOS): Price breaks most recent swing high (uptrend) or swing low (downtrend)
  • Change of Character (ChoCH): Price breaks counter-trend swing (potential reversal signal)

TypeScript Implementation:

typescript
enum Trend {
  UPTREND = 'uptrend',
  DOWNTREND = 'downtrend',
  RANGING = 'ranging'
}

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

// Detect swing highs and lows using lookback window
function findSwingPoints(candles: Candle[], lookback: number = 5): SwingPoint[] {
  const swings: SwingPoint[] = [];

  for (let i = lookback; i < candles.length - lookback; i++) {
    const current = candles[i];
    const leftWindow = candles.slice(i - lookback, i);
    const rightWindow = candles.slice(i + 1, i + lookback + 1);

    // Swing high: current high is higher than all candles in window
    const isSwingHigh =
      leftWindow.every(c => current.high > c.high) &&
      rightWindow.every(c => current.high > c.high);

    if (isSwingHigh) {
      swings.push({
        type: 'high',
        index: i,
        price: current.high,
        timestamp: current.timestamp
      });
    }

    // Swing low: current low is lower than all candles in window
    const isSwingLow =
      leftWindow.every(c => current.low < c.low) &&
      rightWindow.every(c => current.low < c.low);

    if (isSwingLow) {
      swings.push({
        type: 'low',
        index: i,
        price: current.low,
        timestamp: current.timestamp
      });
    }
  }

  return swings;
}

// Determine current trend from swings
function determineTrend(swings: SwingPoint[]): Trend {
  if (swings.length < 4) return Trend.RANGING;

  const recentSwings = swings.slice(-4); // Last 4 swings

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

  if (highs.length >= 2 && lows.length >= 2) {
    const isHigherHighs = highs[1].price > highs[0].price;
    const isHigherLows = lows[1].price > lows[0].price;

    if (isHigherHighs && isHigherLows) return Trend.UPTREND;

    const isLowerHighs = highs[1].price < highs[0].price;
    const isLowerLows = lows[1].price < lows[0].price;

    if (isLowerHighs && isLowerLows) return Trend.DOWNTREND;
  }

  return Trend.RANGING;
}

Support and Resistance

Definitions:

  • Support: Price level where buying pressure historically prevents further decline
  • Resistance: Price level where selling pressure historically prevents further advance
  • Horizontal Support/Resistance: Static price levels (e.g., round numbers, previous highs/lows)
  • Dynamic Support/Resistance: Moving levels (e.g., moving averages, trendlines)

Detection:

typescript
interface SupportResistanceLevel {
  price: number;
  strength: number; // Number of touches
  touches: number[]; // Candle indices where touched
  type: 'support' | 'resistance';
}

function findSupportResistance(
  candles: Candle[],
  tolerance: number = 0.001 // 0.1% tolerance for "same" price
): SupportResistanceLevel[] {
  const levels: SupportResistanceLevel[] = [];

  // Group swing highs and lows into clusters
  const swings = findSwingPoints(candles);

  const clusters = clusterPrices(
    swings.map(s => s.price),
    tolerance
  );

  clusters.forEach(cluster => {
    const touchIndices = swings
      .filter(s => Math.abs(s.price - cluster.price) / s.price <= tolerance)
      .map(s => s.index);

    if (touchIndices.length >= 2) { // At least 2 touches to be valid
      // Determine if support or resistance by context
      const avgSwingType = swings
        .filter(s => touchIndices.includes(s.index))
        .reduce((sum, s) => sum + (s.type === 'high' ? 1 : 0), 0) / touchIndices.length;

      levels.push({
        price: cluster.price,
        strength: touchIndices.length,
        touches: touchIndices,
        type: avgSwingType > 0.5 ? 'resistance' : 'support'
      });
    }
  });

  return levels.sort((a, b) => b.strength - a.strength); // Sort by strength
}

function clusterPrices(prices: number[], tolerance: number): { price: number; count: number }[] {
  const clusters: { price: number; count: number }[] = [];
  const used = new Set<number>();

  prices.forEach((price, i) => {
    if (used.has(i)) return;

    const cluster: number[] = [price];
    used.add(i);

    for (let j = i + 1; j < prices.length; j++) {
      if (used.has(j)) continue;

      if (Math.abs(price - prices[j]) / price <= tolerance) {
        cluster.push(prices[j]);
        used.add(j);
      }
    }

    clusters.push({
      price: cluster.reduce((sum, p) => sum + p, 0) / cluster.length, // Average price
      count: cluster.length
    });
  });

  return clusters;
}

Data Models

Core Data Structures

Candle (OHLCV):

typescript
interface Candle {
  timestamp: number; // Unix timestamp in milliseconds
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
}

// Helper functions
function isBullish(candle: Candle): boolean {
  return candle.close > candle.open;
}

function isBearish(candle: Candle): boolean {
  return candle.close < candle.open;
}

function candleRange(candle: Candle): number {
  return candle.high - candle.low;
}

function candleBody(candle: Candle): number {
  return Math.abs(candle.close - candle.open);
}

function upperWick(candle: Candle): number {
  return candle.high - Math.max(candle.open, candle.close);
}

function lowerWick(candle: Candle): number {
  return Math.min(candle.open, candle.close) - candle.low;
}

Trade:

typescript
interface Trade {
  id: string;
  symbol: string;
  direction: 'long' | 'short';
  entry: {
    price: number;
    timestamp: number;
    size: number; // Contracts or shares
  };
  exit?: {
    price: number;
    timestamp: number;
    reason: 'take_profit' | 'stop_loss' | 'manual';
  };
  stopLoss: number;
  takeProfit: number;
  strategy: string;
  status: 'open' | 'closed';
  pnl?: number; // Profit/loss
}

function calculatePnL(trade: Trade, pointValue: number): number | null {
  if (!trade.exit) return null;

  const direction = trade.direction === 'long' ? 1 : -1;
  const priceChange = (trade.exit.price - trade.entry.price) * direction;

  return priceChange * trade.entry.size * pointValue;
}

Order:

typescript
enum OrderType {
  MARKET = 'market',
  LIMIT = 'limit',
  STOP = 'stop',
  STOP_LIMIT = 'stop_limit'
}

enum OrderSide {
  BUY = 'buy',
  SELL = 'sell'
}

enum OrderStatus {
  PENDING = 'pending',
  FILLED = 'filled',
  CANCELLED = 'cancelled',
  REJECTED = 'rejected'
}

interface Order {
  id: string;
  symbol: string;
  type: OrderType;
  side: OrderSide;
  quantity: number;
  price?: number; // For limit orders
  stopPrice?: number; // For stop orders
  status: OrderStatus;
  filledQuantity: number;
  timestamp: number;
}

Utility Functions

Timeframe Conversion

typescript
type Timeframe = '1m' | '5m' | '15m' | '30m' | '1h' | '4h' | '1d';

function timeframeToMinutes(timeframe: Timeframe): number {
  const map: Record<Timeframe, number> = {
    '1m': 1,
    '5m': 5,
    '15m': 15,
    '30m': 30,
    '1h': 60,
    '4h': 240,
    '1d': 1440
  };

  return map[timeframe];
}

// Resample lower timeframe candles to higher timeframe
function resampleCandles(candles: Candle[], targetTimeframe: Timeframe): Candle[] {
  const minutes = timeframeToMinutes(targetTimeframe);
  const periodMs = minutes * 60 * 1000;

  const resampled: Candle[] = [];
  let currentPeriod: Candle[] = [];
  let periodStart = 0;

  candles.forEach(candle => {
    const candlePeriod = Math.floor(candle.timestamp / periodMs);

    if (periodStart === 0) {
      periodStart = candlePeriod;
    }

    if (candlePeriod === periodStart) {
      currentPeriod.push(candle);
    } else {
      // Aggregate current period
      if (currentPeriod.length > 0) {
        resampled.push(aggregateCandles(currentPeriod));
      }

      // Start new period
      currentPeriod = [candle];
      periodStart = candlePeriod;
    }
  });

  // Aggregate final period
  if (currentPeriod.length > 0) {
    resampled.push(aggregateCandles(currentPeriod));
  }

  return resampled;
}

function aggregateCandles(candles: Candle[]): Candle {
  return {
    timestamp: candles[0].timestamp,
    open: candles[0].open,
    high: Math.max(...candles.map(c => c.high)),
    low: Math.min(...candles.map(c => c.low)),
    close: candles[candles.length - 1].close,
    volume: candles.reduce((sum, c) => sum + c.volume, 0)
  };
}

Price Utilities

typescript
// Round price to valid tick size
function roundToTick(price: number, tickSize: number): number {
  return Math.round(price / tickSize) * tickSize;
}

// Get instrument tick size
function getTickSize(symbol: string): number {
  const tickSizes: Record<string, number> = {
    'ES': 0.25,  // E-mini S&P 500
    'NQ': 0.25,  // E-mini Nasdaq
    'YM': 1.00,  // E-mini Dow
    'RTY': 0.10, // E-mini Russell 2000
    // Add more as needed
  };

  return tickSizes[symbol] || 0.01;
}

// Get instrument point value (dollar value per point)
function getPointValue(symbol: string): number {
  const pointValues: Record<string, number> = {
    'ES': 50,   // $50 per point
    'NQ': 20,   // $20 per point
    'YM': 5,    // $5 per point
    'RTY': 50,  // $50 per point
  };

  return pointValues[symbol] || 1;
}

// Calculate distance in ticks
function tickDistance(price1: number, price2: number, tickSize: number): number {
  return Math.abs(price1 - price2) / tickSize;
}

Statistical Utilities

typescript
// Average True Range (ATR)
function calculateATR(candles: Candle[], period: number = 14): number {
  if (candles.length < period + 1) {
    throw new Error(`Need at least ${period + 1} candles for ATR`);
  }

  const trueRanges: number[] = [];

  for (let i = 1; i < candles.length; i++) {
    const current = candles[i];
    const previous = candles[i - 1];

    const tr = Math.max(
      current.high - current.low,
      Math.abs(current.high - previous.close),
      Math.abs(current.low - previous.close)
    );

    trueRanges.push(tr);
  }

  // Simple average of true ranges
  const recentTRs = trueRanges.slice(-period);
  return recentTRs.reduce((sum, tr) => sum + tr, 0) / period;
}

// Standard Deviation
function standardDeviation(values: number[]): number {
  const mean = values.reduce((sum, v) => sum + v, 0) / values.length;
  const squaredDiffs = values.map(v => Math.pow(v - mean, 2));
  const variance = squaredDiffs.reduce((sum, d) => sum + d, 0) / values.length;

  return Math.sqrt(variance);
}

// Simple Moving Average (SMA)
function calculateSMA(candles: Candle[], period: number, priceType: 'close' | 'high' | 'low' = 'close'): number {
  if (candles.length < period) {
    throw new Error(`Need at least ${period} candles for SMA`);
  }

  const recent = candles.slice(-period);
  const sum = recent.reduce((s, c) => s + c[priceType], 0);

  return sum / period;
}

// Exponential Moving Average (EMA)
function calculateEMA(candles: Candle[], period: number, priceType: 'close' | 'high' | 'low' = 'close'): number {
  if (candles.length < period) {
    throw new Error(`Need at least ${period} candles for EMA`);
  }

  const multiplier = 2 / (period + 1);
  let ema = calculateSMA(candles.slice(0, period), period, priceType); // Start with SMA

  for (let i = period; i < candles.length; i++) {
    ema = (candles[i][priceType] - ema) * multiplier + ema;
  }

  return ema;
}

Time Utilities

typescript
// Convert timestamp to EST/New York time
function toEST(timestamp: number): Date {
  const date = new Date(timestamp);
  return new Date(date.toLocaleString('en-US', { timeZone: 'America/New_York' }));
}

// Check if timestamp is during market hours
function isDuringMarketHours(timestamp: number, symbol: string): boolean {
  const est = toEST(timestamp);
  const hour = est.getHours();
  const dayOfWeek = est.getDay(); // 0 = Sunday, 6 = Saturday

  // Weekend check
  if (dayOfWeek === 0 || dayOfWeek === 6) return false;

  // Regular trading hours for ES/NQ (9:30 AM - 4:00 PM EST)
  if (symbol === 'ES' || symbol === 'NQ') {
    return hour >= 9.5 && hour < 16;
  }

  return false;
}

// Get session start time
function getSessionStart(date: Date, symbol: string): Date {
  const est = new Date(date.toLocaleString('en-US', { timeZone: 'America/New_York' }));

  // ES/NQ regular session starts at 9:30 AM EST
  if (symbol === 'ES' || symbol === 'NQ') {
    est.setHours(9, 30, 0, 0);
  }

  return est;
}

Common Patterns

Pattern: Displacement

Definition: Rapid, strong price movement indicating institutional activity.

Detection:

typescript
interface Displacement {
  startIndex: number;
  endIndex: number;
  direction: 'up' | 'down';
  strength: number; // Magnitude of move
}

function detectDisplacement(
  candles: Candle[],
  minConsecutive: number = 3,
  minPercentMove: number = 0.005 // 0.5%
): Displacement[] {
  const displacements: Displacement[] = [];

  for (let i = minConsecutive; i < candles.length; i++) {
    const window = candles.slice(i - minConsecutive, i);

    // Bullish displacement: all bullish candles in window
    const allBullish = window.every(c => c.close > c.open);
    if (allBullish) {
      const move = (window[window.length - 1].close - window[0].open) / window[0].open;
      if (Math.abs(move) >= minPercentMove) {
        displacements.push({
          startIndex: i - minConsecutive,
          endIndex: i,
          direction: 'up',
          strength: move
        });
      }
    }

    // Bearish displacement: all bearish candles in window
    const allBearish = window.every(c => c.close < c.open);
    if (allBearish) {
      const move = (window[0].open - window[window.length - 1].close) / window[0].open;
      if (Math.abs(move) >= minPercentMove) {
        displacements.push({
          startIndex: i - minConsecutive,
          endIndex: i,
          direction: 'down',
          strength: move
        });
      }
    }
  }

  return displacements;
}

Pattern: Consolidation

Definition: Price trading in a tight range, building energy for next move.

Detection:

typescript
interface Consolidation {
  startIndex: number;
  endIndex: number;
  high: number;
  low: number;
  range: number;
}

function detectConsolidation(
  candles: Candle[],
  minCandles: number = 10,
  maxRangePercent: number = 0.02 // 2% max range
): Consolidation[] {
  const consolidations: Consolidation[] = [];

  for (let i = minCandles; i < candles.length; i++) {
    const window = candles.slice(i - minCandles, i);

    const high = Math.max(...window.map(c => c.high));
    const low = Math.min(...window.map(c => c.low));
    const range = high - low;
    const rangePercent = range / low;

    if (rangePercent <= maxRangePercent) {
      consolidations.push({
        startIndex: i - minCandles,
        endIndex: i,
        high,
        low,
        range
      });
    }
  }

  return consolidations;
}

After Using This Skill

When implementing strategies:

  • Import these types and utilities into your strategy files
  • Use standardized data models (Candle, Trade, Order)
  • Reference common patterns (displacement, consolidation) instead of reimplementing

For consistency:

  • All strategies should use same Candle interface
  • All prices should be rounded to tick size using roundToTick()
  • All time-based logic should use EST timezone via toEST()

For utilities:

  • Use resampleCandles() to convert between timeframes
  • Use calculateATR() for dynamic stop-loss placement
  • Use findSwingPoints() for market structure analysis

Related Skills

  • ict-strategy: Uses swing points, displacement, market structure
  • amt-strategy: Uses timeframe conversion, volume aggregation
  • trading-bot-development: Uses data models, utility functions

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