← Back to list
💰

ohlcv-processing Checked

Market data preparation including OHLCV resampling, gap handling, anomaly detection, normalizatio…
💰 Finance & Trading skills By agiprolabs Version v1.0.0 Added 2026-09-20 Updated 2026-09-20 Source ↗
8.5Overall rating
2Views
7Downloads
366Stars

Installation

🤖 Install via AI

Copy the prompt below and send it to your AI assistant (e.g. Claude Code) — it will follow the instructions and install automatically.

Install the "ohlcv-processing" skill by following the instructions at https://skill123.me/install/ohlcv-processing.
⌨️ Command line install

Run in your terminal — downloads and installs to ~/.claude/skills/.

curl -fsSL https://skill123.me/install/ohlcv-processing.sh | bash
📦 Download ZIP

Download the zip and extract it into your skills directory (e.g. ~/.claude/skills/), then restart your session.

⬇ Download v1.0.0 · 22 KB
📊 Static rating
8.5/10
Trigger
6.0
Structure
8.0
Workflow
8.8
Content
9.0
Engineering
10.0
Security
10.0

Six-dimension rubric with reviewer rationale.

View scorecard ↓
🛡 Dynamic test
—

Not live-tested yet — this skill is rated on static analysis only.

About this skill

Market data preparation including OHLCV resampling, gap handling, anomaly detection, normalization, and multi-source merging

OHLCV Processing — Market Data Preparation

Clean, consistent OHLCV data is the foundation of every trading analysis. Garbage in, garbage out — a single anomalous candle can trigger false signals, corrupt indicator calculations, and produce misleading backtest results. This skill covers the full data preparation pipeline: validation, cleaning, resampling, normalization, and multi-source merging.

**Why this matters**: Crypto OHLCV data is messier than traditional markets. 24/7 trading means no official close, DEX aggregators disagree on prices, low-liquidity tokens produce impossible candles, and API outages create gaps. Every analysis workflow should start with this pipeline.

Documentation

Market data preparation including OHLCV resampling, gap handling, anomaly detection, normalization, and multi-source merging

OHLCV Processing — Market Data Preparation

Clean, consistent OHLCV data is the foundation of every trading analysis. Garbage in, garbage out — a single anomalous candle can trigger false signals, corrupt indicator calculations, and produce misleading backtest results. This skill covers the full data preparation pipeline: validation, cleaning, resampling, normalization, and multi-source merging.

**Why this matters**: Crypto OHLCV data is messier than traditional markets. 24/7 trading means no official close, DEX aggregators disagree on prices, low-liquidity tokens produce impossible candles, and API outages create gaps. Every analysis workflow should start with this pipeline.

Quick Start

### 1. Install Dependencies


uv pip install pandas numpy httpx

### 2. Standard OHLCV DataFrame Format

All processing functions expect this canonical format:


import pandas as pd

# Canonical OHLCV DataFrame
# - DatetimeIndex in UTC
# - Columns: open, high, low, close, volume (lowercase)
# - Sorted ascending by timestamp
# - No duplicate timestamps

df = pd.DataFrame({
    "open": [1.10, 1.12, 1.11],
    "high": [1.15, 1.14, 1.13],
    "low": [1.08, 1.10, 1.09],
    "close": [1.12, 1.11, 1.12],
    "volume": [50000, 48000, 52000],
}, index=pd.to_datetime([
    "2025-01-01 00:00:00",
    "2025-01-01 00:01:00",
    "2025-01-01 00:02:00",
], utc=True))
df.index.name = "timestamp"

### 3. Full Processing Pipeline


import pandas as pd
import numpy as np

def process_ohlcv(df: pd.DataFrame) -> pd.DataFrame:
    """Run complete OHLCV processing pipeline."""
    df = standardize_columns(df)
    df = validate_ohlcv(df)
    df = handle_gaps(df, method="ffill")
    df = detect_and_flag_anomalies(df)
    return df

Data Validation

### Column Checks


REQUIRED_COLUMNS = {"open", "high", "low", "close", "volume"}

def standardize_columns(df: pd.DataFrame) -> pd.DataFrame:
    """Normalize column names to lowercase standard."""
    df.columns = df.columns.str.lower().str.strip()
    # Common renames
    rename_map = {"vol": "volume", "v": "volume", "o": "open",
                  "h": "high", "l": "low", "c": "close"}
    df = df.rename(columns=rename_map)
    missing = REQUIRED_COLUMNS - set(df.columns)
    if missing:
        raise ValueError(f"Missing columns: {missing}")
    return df[["open", "high", "low", "close", "volume"]]

### Structural Validation


def validate_ohlcv(df: pd.DataFrame) -> pd.DataFrame:
    """Validate OHLCV structural integrity."""
    # Ensure DatetimeIndex in UTC
    if not isinstance(df.index, pd.DatetimeIndex):
        df.index = pd.to_datetime(df.index, utc=True)
    if df.index.tz is None:
        df.index = df.index.tz_localize("UTC")
View full documentation

Sort and deduplicate

df = df.sort_index()

dupes = df.index.duplicated(keep="last")

if dupes.any():

print(f"Warning: Removed {dupes.sum()} duplicate timestamps")

df = df[~dupes]

Type enforcement

for col in ["open", "high", "low", "close", "volume"]:

df[col] = pd.to_numeric(df[col], errors="coerce")

return df



### Impossible Candle Detection

def find_impossible_candles(df: pd.DataFrame) -> pd.DataFrame:

"""Find candles that violate OHLC constraints."""

issues = pd.DataFrame(index=df.index)

issues["high_lt_low"] = df["high"] < df["low"]

issues["high_lt_open"] = df["high"] < df["open"]

issues["high_lt_close"] = df["high"] < df["close"]

issues["low_gt_open"] = df["low"] > df["open"]

issues["low_gt_close"] = df["low"] > df["close"]

issues["negative_price"] = (df[["open", "high", "low", "close"]] < 0).any(axis=1)

issues["negative_volume"] = df["volume"] < 0

issues["any_issue"] = issues.any(axis=1)

return issues[issues["any_issue"]]



## Gap Handling

Crypto trades 24/7, but gaps still occur from API outages, low liquidity, or aggregator downtime.

### Detect Gaps

def detect_gaps(df: pd.DataFrame, expected_freq: str = "1min") -> pd.Series:

"""Find missing timestamps based on expected frequency."""

full_index = pd.date_range(

start=df.index.min(), end=df.index.max(), freq=expected_freq, tz="UTC"

)

missing = full_index.difference(df.index)

return missing



### Fill Gaps

def handle_gaps(

df: pd.DataFrame,

freq: str = "1min",

method: str = "ffill",

max_gap: int = 5,

) -> pd.DataFrame:

"""Fill gaps in OHLCV data.

Args:

df: OHLCV DataFrame with DatetimeIndex.

freq: Expected bar frequency.

method: 'ffill' (forward fill) or 'interpolate'.

max_gap: Maximum consecutive bars to fill. Larger gaps are left as NaN.

"""

full_index = pd.date_range(

start=df.index.min(), end=df.index.max(), freq=freq, tz="UTC"

)

df = df.reindex(full_index)

df.index.name = "timestamp"

Mark which bars were filled

df["is_filled"] = df["close"].isna()

if method == "ffill":

Forward fill OHLC (flat candle), zero volume

df[["open", "high", "low", "close"]] = (

df[["open", "high", "low", "close"]].ffill(limit=max_gap)

)

df["volume"] = df["volume"].fillna(0)

elif method == "interpolate":

df[["open", "high", "low", "close"]] = (

df[["open", "high", "low", "close"]].interpolate(

method="time", limit=max_gap

)

)

df["volume"] = df["volume"].fillna(0)

return df



## Anomaly Detection

See `references/data_quality.md` for the complete anomaly taxonomy.

### Price Spike Detection

def detect_price_spikes(

df: pd.DataFrame, window: int = 20, threshold: float = 3.0

) -> pd.Series:

"""Flag bars where return exceeds threshold * rolling std."""

returns = df["close"].pct_change()

rolling_std = returns.rolling(window, min_periods=5).std()

spike = returns.abs() > (threshold * rolling_std)

return spike.fillna(False)



### Zero Volume Detection

def detect_zero_volume(df: pd.DataFrame, min_volume: float = 0) -> pd.Series:

"""Flag bars with zero or below-minimum volume."""

return df["volume"] <= min_volume



### Composite Anomaly Flagging

def flag_anomalies(df: pd.DataFrame) -> pd.DataFrame:

"""Add anomaly flag columns to DataFrame."""

df["anomaly_spike"] = detect_price_spikes(df)

df["anomaly_zero_vol"] = detect_zero_volume(df)

impossible = find_impossible_candles(df)

df["anomaly_impossible"] = False

if not impossible.empty:

df.loc[impossible.index, "anomaly_impossible"] = True

df["anomaly_any"] = (

df["anomaly_spike"] | df["anomaly_zero_vol"] | df["anomaly_impossible"]

)

return df



## Resampling

See `references/resampling_guide.md` for detailed guidance.

### Standard Resample

OHLCV_RESAMPLE_RULES = {

"open": "first",

"high": "max",

"low": "min",

"close": "last",

"volume": "sum",

}

def resample_ohlcv(df: pd.DataFrame, target_freq: str) -> pd.DataFrame:

"""Resample OHLCV to a coarser timeframe.

Args:

df: OHLCV DataFrame (must be finer than target_freq).

target_freq: Pandas frequency string ('5min', '15min', '1h', '4h', '1D').

Returns:

Resampled OHLCV DataFrame with no NaN rows.

"""

ohlcv_cols = ["open", "high", "low", "close", "volume"]

resampled = df[ohlcv_cols].resample(target_freq).agg(OHLCV_RESAMPLE_RULES)

return resampled.dropna(subset=["close"])



### Common Timeframe Ladder

TIMEFRAME_LADDER = ["1min", "5min", "15min", "1h", "4h", "1D"]

def resample_ladder(df: pd.DataFrame) -> dict[str, pd.DataFrame]:

"""Resample 1-minute data to all standard timeframes."""

results = {"1min": df.copy()}

for tf in TIMEFRAME_LADDER[1:]:

results[tf] = resample_ohlcv(df, tf)

return results



### VWAP Calculation

def compute_vwap(df: pd.DataFrame) -> pd.Series:

"""Compute

Score breakdown

Trigger
6.0
Description enumerates pipeline stages with good keyword coverage (resampling, gap handling, anomaly detection, normalization, merging); no Use-when or boundary statement, no trigger variants.
Structure
8.0
Two well-scoped references (data quality taxonomy, resampling guide) plus two scripts; 388-line SKILL.md carries many inline function definitions duplicating script content; Files section annotates contents.
Workflow
8.8
Excellent pipeline design with ordered stages, explicit guards (missing-column ValueError, max_gap fill limit, impossible-candle constraints), and a quality_report verification loop; --demo mode generates synthetic data with intentional anomalies to test the detector — a genuine self-verification design.
Content
9.0
Code-forward, imperative, every function copy-runnable with typed args and docstrings.
Engineering
10.0
Frontmatter valid; shebangs, dependencies, and env vars documented; all referenced paths exist.
Security
10.0
6.0 manifest: sole network destination is https://public-api.birdeye.so (official public market-data API, declared in script header, opt-in via --token); BIRDEYE_API_KEY from env only; no file writes beyond stdout, no credentials, no persistence. Capabilities match declared scope. No advice content, so no disclaimer needed.
Complete market-data preparation pipeline: validation (impossible-candle checks), gap handling with fill limits, composite anomaly flagging, correct OHLCV resample rules, normalization, and multi-source merging with conflict resolution. Scripts default to synthetic demo data containing intentional anomalies for self-testing. Deductions: description lacks When/When-Not; SKILL.md duplicates many function bodies that also live in scripts.