mcpField
C
custom-indicator

custom-indicator

github.com/marketcallsData & databases20571 installsUpdated 20w ago
Repository

About custom-indicator

custom-indicator is an agent skill for building custom technical indicators with openalgo's Rust-core ta primitives and vectorized NumPy. It checks whether an indicator already exists, then guides creation of its implementation, chart, and benchmark.

Key features

  1. 01Check the existing openalgo.ta catalog
  2. 02Compose Rust-core ta primitives
  3. 03Use vectorized NumPy operations
  4. 04Create indicator and Plotly chart files
  5. 05Generate performance benchmark scripts

Use cases

  1. 01Build a z-score indicator from rolling statistics
  2. 02Create a squeeze or VWAP bands indicator
  3. 03Prototype a mean-reversion or range-filter signal

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/marketcalls/openalgo-indicator-skills --skill custom-indicator

After installing, type/custom-indicatorin Claude Code to confirm it loaded.

Details

From the project README

Create a custom technical indicator by composing openalgo's Rust-core ta primitives with vectorized NumPy.

Arguments

  • $0 = indicator name (e.g., zscore, squeeze, vwap-bands, custom-rsi, mean-reversion). Required.

If no arguments, ask the user what indicator they want to build.

Instructions

  1. Read the indicator-expert rules, especially:
    • rules/custom-indicators.md — NumPy + ta-primitive patterns and templates
    • rules/performance.md — Rust core performance, O(n) guarantees, benchmarking
    • rules/indicator-catalog.md — Check if indicator already exists in openalgo.ta
  2. Check first: If the indicator already exists in openalgo.ta (100+ indicators), tell the user and show the existing API
  3. Create custom_indicators/{indicator_name}/ directory (on-demand)
  4. Create {indicator_name}.py with:
File Structure
"""
{Indicator Name} — Custom Indicator
Description: {what it measures}
Category: {trend/momentum/volatility/volume/oscillator}
"""
import numpy as np
import pandas as pd
from openalgo import ta

# --- Core Computation (vectorized NumPy on ta primitives) ---
def _compute_{name}(arr: np.ndarray, period: int) -> np.ndarray:
    """Vectorized core computation built on Rust-core primitives."""
    n = len(arr)
    result = np.full(n, np.nan)
    # Compose from ta primitives (they run in Rust):
    # mean = ta.sma(arr, period); std = ta.stdev(arr, period); ...
    # then combine with NumPy array math (np.where, masks)
    return result

# --- Public API ---
def {name}(data, period=20):
    """
    {Indicator Name}

    Args:
        data: Close prices (numpy array, pandas Series, or list)
        period: Lookback period (default: 20)

    Returns:
        Same type as input with indicator values
    """
    if isinstance(data, pd.Series):
        idx = data.index
        result = _compute_{name}(data.values.astype(np.float64), period)
        return pd.Series(result, index=idx, name="{Name}({period})")
    arr = np.asarray(data, dtype=np.float64)
    return _compute_{name}(arr, period)
  1. Create chart.py for visualization:
"""Chart the custom indicator with Plotly."""
import os
from pathlib import Path
from datetime import datetime, timedelta
from dotenv import find_dotenv, load_dotenv
from openalgo import api, ta
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from {indicator_name} import {name}

# ... fetch data, compute indicator, create chart ...
  1. Create benchmark.py for performance testing (no warmup needed — the Rust core runs at full speed from the first call):
"""Benchmark the custom indicator."""
import numpy as np
import time
from {indicator_name} import {name}

for size in [10_000, 100_000, 500_000]:
    data = np.random.randn(size).cumsum() + 1000
    t0 = time.perf_counter()
    _ = {name}(data, 20)
    elapsed = (time.perf_counter() - t0) * 1000
    print(f"{size:>10,} bars: {elapsed:>8.2f}ms")

NumPy Rules (CRITICAL)

MUST DO
  • Compose from ta primitives wherever possible — they run in the Rust core
  • np.full(n, np.nan) to initialize output arrays
  • Vectorize with array expressions, np.where, and boolean masks
  • Guard divisions: np.errstate(invalid="ignore", divide="ignore") plus a safe denominator mask
  • Respect NaN warm-up periods from the primitives (mask on ~np.isnan(...))
  • Float64 for all numeric arrays
  • O(n) algorithms only
MUST NOT
  • Never reimplement an indicator that already exists in openalgo.ta
  • Never write per-bar Python loops over large arrays — vectorize instead
  • Never divide without masking zero/NaN denominators
  • If the indicator is genuinely path-dependent (sequential state no primitive covers), check whether ta.ema / Wilder-style primitives already provide the recursion first; a plain Python loop is a last resort — keep it O(n) and document the trade-off
Available Building Blocks

Public ta methods that run in the Rust core:

from openalgo import ta

# Rolling math:    ta.sma, ta.ema, ta.wma, ta.stdev, ta.highest, ta.lowest
# Price action:    ta.true_range, ta.atr, ta.change, ta.roc
# Bands/channels:  ta.bbands, ta.keltner, ta.donchian
# Signals:         ta.crossover, ta.crossunder, ta.exrem, ta.rising, ta.falling

Common Custom Indicator Patterns

PatternImplementation
Z-Score(value - rolling_mean) / rolling_stdev
SqueezeBollinger inside Keltner channel
VWAP BandsVWAP + N * rolling stdev of (close - vwap)
Momentum ScoreWeighted sum of RSI + MACD + ADX conditions
Mean ReversionDistance from SMA as % + threshold
Range FilterATR-based dynamic filter on close
Trend StrengthADX + directional movement composite

Example Usage

/custom-indicator zscore /custom-indicator squeeze-momentum /custom-indicator vwap-bands /custom-indicator range-filter

Comments

Markdown supported