Agent skill
trading-foundations
Common trading concepts, terminology, data structures, and utility functions shared across all trading strategies (ICT, AMT, etc.)
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:
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:
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):
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:
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:
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
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
// 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
// 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
// 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:
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:
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
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
daily-summary
Use when preparing daily standups or status reports - automates PR summary generation with categorization, metrics, and velocity analysis; eliminates manual report compilation and ensures consistent format
wolf-scripts-core
Core automation scripts for archetype selection, evidence validation, quality scoring, and safe bash execution
wolf
Master skill for Wolf Agents institutional knowledge and behavioral patterns (v1.1.0 with skill-chaining)
wolf-archetypes
Behavioral archetypes for automatic agent adaptation based on work type
ict-strategy
Inner Circle Trader (ICT) methodology - smart money concepts, liquidity pools, fair value gaps, order blocks, and market structure for trading bot implementation
databento
Use when working with ES/NQ futures market data, before calling any Databento API - follow mandatory four-step workflow (cost check, availability check, fetch, validate); prevents costly API errors and ensures data quality
Didn't find tool you were looking for?