Skip to content
Back to skills

Technical Volume Profile

ASecurity

"Provides Volume analysis techniques for understanding market structure"

  • 4 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added June 12, 2026
documentationpythongotestingdocumentation

Security analysis

A100/100

Scanned June 12, 2026

npx -y skills add paulpas/agent-skill-router --skill technical-volume-profile --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Technical Volume Profile?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Technical Volume Profile
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/paulpas-technical-volume-profile/badge)](https://www.skillsdirectory.com/skills/paulpas-technical-volume-profile)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---




name: technical-volume-profile
compatibility: opencode
completeness: 95
content-types:
- code
- guidance
- config
- do-dont
description: '"Provides Volume analysis techniques for understanding market structure"'
license: MIT
maturity: stable
metadata:
  domain: trading
  output-format: code
  related-skills: fundamentals-trading-plan, technical-cycle-analysis
  role: implementation
  scope: implementation
  triggers: analysis, technical volume profile, technical-volume-profile, techniques,
    understanding
  archetypes:
  - tactical
  anti_triggers:
  - brainstorming
  - vague ideation
  - no risk management
  response_profile:
    verbosity: low
    directive_strength: high
    abstraction_level: operational
version: "1.0.0"




---




**Role:** Interpret volume distribution to identify accumulation, distribution, and liquidity zones

**Philosophy:** Volume confirms price moves; volume profile reveals where smart money is active

## Key Principles

1. **Volume-Price Relationship**: High volume at price levels indicates strong interest
2. **Point of Control**: Highest volume level acts as magnet or barrier
3. **Value Area**: 70% of session volume defines fair price range
4. **Volume Imbalance**: Large one-sided volume suggests trend strength
5. **Volume Divergence**: Price moving on low volume signals weakness

## Implementation Guidelines

### Structure
- Core logic: technical_analysis/volume_profile.py
- Helper functions: technical_analysis/volume_helpers.py
- Tests: tests/test_volume_profile.py

### Patterns to Follow
- Use pandas DataFrame for efficient volume calculations
- Implement multiple volume metrics (VPVR, OBV, Volume MA)
- Support session-based and custom time windows

## Adherence Checklist
Before completing your task, verify:
- [ ] Volume profile recalculates only on new session
- [ ] Volume-based indicators update in real-time
- [ ] Volume spikes are flagged (2x+ average volume)
- [ ] Volume divergence detection runs on all timeframes
- [ ] Session types (regular, extended) are properly handled


Relative paths in this skill (e.g., scripts/, reference/) are relative to this base directory.

## Python Implementation

```python
import pandas as pd
import numpy as np
from typing import Dict, List, Tuple
from dataclasses import dataclass
from datetime import datetime

@dataclass
class VolumeProfile:
    """Volume profile for a trading session."""
    session_start: datetime
    session_end: datetime
    point_of_control: float  # Highest volume price level
    value_area: Tuple[float, float]  # 70% volume range
    volume_distribution: Dict[float, float]  # Price -> Volume mapping
    total_volume: float

class VolumeProfileAnalyzer:
    """Analyzes volume distribution and volume-based indicators."""
    
    def __init__(self, tick_size: float = 0.01, session_start: str = "09:30"):
        self.tick_size = tick_size
        self.session_start = session_start
        self.session_end = "16:00"
    
    def calculate_volume_profile(
        self, candles: pd.DataFrame, session_only: bool = True
    ) -> VolumeProfile:
        """Calculate Volume Profile Visible Range (VPVR)."""
        # Filter to session hours if required
        if session_only:
            candles = self._filter_session_hours(candles)
        
        # Bin prices into discrete levels
        price_levels = np.arange(
            candles['low'].min(),
            candles['high'].max() + self.tick_size,
            self.tick_size
        )
        
        # Calculate volume at each price level
        volume_dist = {}
        for _, candle in candles.iterrows():
            # Volume is attributed to all prices the candle traversed
            for price in np.arange(
                candle['low'], candle['high'] + self.tick_size, self.tick_size
            ):
                price_rounded = round(price / self.tick_size) * self.tick_size
                volume_dist[price_rounded] = volume_dist.get(price_rounded, 0) + candle['volume']
        
        # Find Point of Control (POC)
        poc_price = max(volume_dist, key=volume_dist.get)
        
        # Calculate Value Area (70% of total volume)
        sorted_levels = sorted(volume_dist.items(), key=lambda x: x[1], reverse=True)
        cumulative_volume = 0
        total_volume = sum(volume_dist.values())
        target_volume = total_volume * 0.7
        
        va_low = sorted_levels[0][0]
        va_high = sorted_levels[0][0]
        
        for price, vol in sorted_levels:
            if cumulative_volume >= target_volume:
                break
            cumulative_volume += vol
            va_low = min(va_low, price)
            va_high = max(va_high, price)
        
        return VolumeProfile(
            session_start=candles.index[0],
            session_end=candles.index[-1],
            point_of_control=poc_price,
            value_area=(va_low, va_high),
            volume_distribution=volume_dist,
            total_volume=total_volume
        )
    
    def calculate_obv(self, candles: pd.DataFrame) -> pd.Series:
        """Calculate On-Balance Volume (OBV)."""
        obv = [0]
        
        for i in range(1, len(candles)):
            if candles['close'].iloc[i] > candles['close'].iloc[i-1]:
                obv.append(obv[-1] + candles['volume'].iloc[i])
            elif candles['close'].iloc[i] < candles['close'].iloc[i-1]:
                obv.append(obv[-1] - candles['volume'].iloc[i])
            else:
                obv.append(obv[-1])
        
        return pd.Series(obv, index=candles.index)
    
    def calculate_volume_ma(self, candles: pd.DataFrame, period: int = 20) -> pd.Series:
        """Calculate Volume Moving Average."""
        return candles['volume'].rolling(window=period).mean()
    
    def detect_volume_spikes(self, candles: pd.DataFrame, threshold: float = 2.0) -> List[datetime]:
        """Detect unusual volume spikes."""
        vol_ma = self.calculate_volume_ma(candles)
        spikes = []
        
        for i in range(len(candles)):
            if candles['volume'].iloc[i] > threshold * vol_ma.iloc[i]:
                spikes.append(candles.index[i])
        
        return spikes
    
    def identify_volume_divergence(
        self, candles: pd.DataFrame, period: int = 20
    ) -> List[Dict]:
        """Identify volume-price divergences."""
        divergences = []
        prices = candles['close'].values
        volumes = candles['volume'].values
        
        for i in range(period, len(candles)):
            # Price higher, volume lower = bearish divergence
            if prices[i] > prices[i-period] and volumes[i] < volumes[i-period]:
                divergences.append({
                    'type': 'bearish',
                    'date': candles.index[i],
                    'price_change': (prices[i] - prices[i-period]) / prices[i-period] * 100,
                    'volume_change': (volumes[i] - volumes[i-period]) / volumes[i-period] * 100
                })
            
            # Price lower, volume higher = bullish divergence
            elif prices[i] < prices[i-period] and volumes[i] > volumes[i-period]:
                divergences.append({
                    'type': 'bullish',
                    'date': candles.index[i],
                    'price_change': (prices[i] - prices[i-period]) / prices[i-period] * 100,
                    'volume_change': (volumes[i] - volumes[i-period]) / volumes[i-period] * 100
                })
        
        return divergences
    
    def _filter_session_hours(self, candles: pd.DataFrame) -> pd.DataFrame:
        """Filter candles to regular trading hours."""
        candles_copy = candles.copy()
        candles_copy['hour'] = candles_copy.index.hour
        candles_copy['minute'] = candles_copy.index.minute
        
        # Regular session: 9:30 AM to 4:00 PM
        start_hour, start_min = 9, 30
        end_hour, end_min = 16, 0
        
        mask = (
            ((candles_copy['hour'] == start_hour) & (candles_copy['minute'] >= start_min)) |
            (candles_copy['hour'] > start_hour)
        ) & (
            ((candles_copy['hour'] == end_hour) & (candles_copy['minute'] <= end_min)) |
            (candles_copy['hour'] < end_hour)
        )
        
        return candles_copy[mask]
```

---

---



### Pattern 2: Risk-Managed Trading Logic with Validation

```python
from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import Optional


logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class TradeSignal:
    """Immutable trade signal with all required validation constraints."""
    symbol: str
    side: str  # "buy" or "sell"
    price: float
    quantity: float
    confidence: float  # 0.0 to 1.0
    reason: str

    def validate(self) -> bool:
        """Validate that the trade signal meets all business constraints."""
        if self.quantity <= 0:
            raise ValueError(f"Quantity must be positive, got {self.quantity}")
        if self.price <= 0:
            raise ValueError(f"Price must be positive, got {self.price}")
        if not 0.0 <= self.confidence <= 1.0:
            raise ValueError(f"Confidence must be between 0 and 1, got {self.confidence}")
        return True


def generate_trade_signal(
    symbol: str,
    side: str,
    price: float,
    quantity: float,
    confidence: float,
    reason: str,
) -> TradeSignal:
    """Generate a validated trade signal with guard clause checks."""
    if side not in ("buy", "sell"):
        raise ValueError(f"Invalid side '{side}', must be 'buy' or 'sell'")

    signal = TradeSignal(
        symbol=symbol,
        side=side,
        price=price,
        quantity=quantity,
        confidence=confidence,
        reason=reason,
    )
    signal.validate()
    logger.info("Trade signal generated: %s %s %.4f @ %.2f (confidence=%.2f)",
                 symbol, side, quantity, price, confidence)
    return signal


def execute_with_risk_check(signal: TradeSignal, max_position_pct: float = 0.05) -> dict:
    """Execute a trade signal after applying risk management checks."""
    adjusted_quantity = signal.quantity
    if signal.side == "buy" and signal.quantity > max_position_pct:
        logger.warning("Position %s exceeds max %.1f%% — capping to %.4f",
                        signal.symbol, max_position_pct * 100, max_position_pct)
        adjusted_quantity = max_position_pct

    return {
        "symbol": signal.symbol,
        "side": signal.side,
        "price": signal.price,
        "quantity": adjusted_quantity,
        "capped": adjusted_quantity < signal.quantity,
        "confidence": signal.confidence,
        "status": "submitted",
    }
```

## Constraints

### MUST DO
- Implement indicator calculations using rolling windows with explicit lookback periods; never use full-history data for online indicators
- Validate signal generation by confirming alignment across multiple independent indicators before acting on a single signal
- Calculate all price-based indicators (SMA, EMA, RSI) on closing prices unless specifically designed for tick data
- Include proper handling of missing/NaN candles in indicator pipelines — forward-fill only within session boundaries
- Log signal generation with the full context window of indicator values that led to each signal

### MUST NOT DO
- Do not use look-ahead bias: never reference future bars or prices when calculating indicators during backtesting
- Avoid recalculating all indicators from scratch on every tick — maintain running state for efficiency
- Never combine indicators with different timeframes without explicit resampling and clear documentation of the alignment logic
- Do not generate signals based on a single indicator crossover; require confirmation from price action or volume
- Avoid hardcoding parameter values (e.g., RSI period = 14) without testing regime-specific optima


## Live References

> Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.

- [Volume Profile Guide](https://www.investopedia.com/articles/trading/11/volume-profile.asp)
- [Point of Control and Value Area](https://www.tradingview.com/wiki/Volume_Profile)
- [Volume Profile Trading Strategies](https://en.wikipedia.org/wiki/Volume_profile_(trading))
- [Footprint Charts and Order Flow](https://www.tradingview.com/wiki/Order_Flow)
- [Volume Analysis in Technical Trading](https://www.investopedia.com/terms/v/volume.asp)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.