marketcalls/openalgo-indicator-skills

custom-indicator

Create a custom technical indicator using vectorized NumPy on top of openalgo's Rust-core ta primitives.

소스 보기

사용 방법

  1. 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
  2. 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
  3. 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/marketcalls/openalgo-indicator-skills --skill custom-indicator

시작 전 확인

  • 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
  • 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 Skill 문서

원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.

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
  1. Check first: If the indicator already exists in openalgo.ta (100+ indicators), tell the user and show the existing API
  2. Create custom_indicators/{indicator_name}/ directory (on-demand)
  3. Create {indicator_name}.py with:

File Structure

python
"""
{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:
python
"""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):
python
"""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:

python
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

관련 작업