본문으로 건너뛰기
cccskills
무료GitHub에 공개

ml-strategy

Build machine-learning trading signals from price and volume data. The workflow checks inputs, creates predictive features, and repeatedly trains on available historical data to produce bounded signals indicating predicted direction and strength.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

원문원문 설명 보기

Machine-learning predictive strategy based on sklearn walk-forward training, feature engineering, and signal generation. Suitable for any OHLCV data.

설치 방법 보기

이럴 때 유용합니다

  • Building trading signals from price and volume data
  • Comparing machine-learning models
  • Testing expanding and sliding training windows
  • Handling missing values in signal generation

포함된 파일(1)

  • SKILL.md12.4 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Machine-Learning Predictive Strategy

Purpose

Use sklearn machine-learning models (RandomForest / GradientBoosting / Ridge) to predict the direction of future returns and generate trading signals. Walk-forward training is used to avoid future data leakage, and feature engineering extracts useful factors from OHLCV data.

Signal Logic

  1. Validate input: check OHLCV columns, minimum row count, NaN ratio — skip symbols that fail
  2. Feature engineering: build multi-dimensional factors from raw OHLCV data (momentum, volatility, RSI, moving-average ratios, volume ratio, and more). All features are sanitized (inf removed, division-by-zero guarded)
  3. Label construction: future N-day return > 0 is the positive class (1), < 0 is the negative class (0)
  4. Walk-forward training: use an expanding or sliding window, train on historical data only, and roll forward day by day for prediction
  5. Signal generation: map predict_proba[:, 1] to [-1.0, 1.0], or use discrete signals from predict in {-1, 0, 1}. Output is guaranteed clean (no NaN, clipped to range)

Complete SignalEngine Example

This is the recommended full pipeline. Copy and customise — safety is built in.

import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler


def validate_data(df: pd.DataFrame, min_rows: int = 300) -> bool:
    """Check that OHLCV data meets minimum quality for ML training.

    Args:
        df: DataFrame with DatetimeIndex.
        min_rows: Minimum number of rows required.

    Returns:
        True if data is usable.
    """
    required = {"open", "high", "low", "close", "volume"}
    if not required.issubset(df.columns):
        return False
    if len(df) < min_rows:
        return False
    if df["close"].isnull().mean() > 0.2:
        return False
    return True


def build_features(df: pd.DataFrame) -> pd.DataFrame:
    """Build a machine-learning feature matrix from OHLCV data.

    All features are guarded against division-by-zero and sanitized
    (inf replaced with NaN) so downstream code never sees inf values.

    Args:
        df: DataFrame containing open, high, low, close, and volume columns.

    Returns:
        DataFrame with feature columns prefixed by 'f_'.
    """
    c = df["close"]
    v = df["volume"]
    ret = c.pct_change(fill_method=None)

    features = pd.DataFrame(index=df.index)
    features["f_ret_5d"] = c.pct_change(5, fill_method=None)
    features["f_ret_20d"] = c.pct_change(20, fill_method=None)
    features["f_vol_20d"] = ret.rolling(20).std()
    features["f_ma_ratio"] = c / c.rolling(20).mean()
    features["f_volume_ratio"] = v / v.rolling(20).mean()

    # RSI(14) — guard: loss=0 in zero-volatility periods produces inf
    delta = c.diff()
    gain = delta.clip(lower=0).rolling(14).mean()
    loss = (-delta.clip(upper=0)).rolling(14).mean()
    rs = gain / loss.replace(0, np.nan)
    features["f_rsi_14"] = 100 - (100 / (1 + rs))

    # Bollinger Band position — guard: bb_upper == bb_lower when std=0
    ma20 = c.rolling(20).mean()
    std20 = c.rolling(20).std()
    bb_upper = ma20 + 2 * std20
    bb_lower = ma20 - 2 * std20
    bb_range = (bb_upper - bb_lower).replace(0, np.nan)
    features["f_bb_position"] = (c - bb_lower) / bb_range

    # Intraday features
    features["f_high_low_ratio"] = (df["high"] - df["low"]) / c
    features["f_close_open_ratio"] = (c - df["open"]) / df["open"]
    features["f_skew_20d"] = ret.rolling(20).skew()

    # Sanitize: replace all inf with NaN (NaN handled by walk-forward)
    features = features.replace([np.inf, -np.inf], np.nan)
    return features


def walk_forward_predict(
    features: pd.DataFrame,
    labels: pd.Series,
    min_train_size: int = 252,
    retrain_freq: int = 20,
    model_type: str = "random_forest",
    window_type: str = "expanding",
    sliding_size: int = 504,
    prediction_horizon: int = 5,
) -> pd.Series:
    """Walk-forward training and prediction to avoid future data leakage.

    Args:
        features: Feature matrix aligned with labels by row index.
        labels: Binary labels (0/1), representing the direction of future N-day returns.
        min_train_size: Minimum training-set size in trading days.
        retrain_freq: Retrain the model every N days.
        model_type: One of "random_forest" / "gradient_boosting" / "ridge".
        window_type: "expanding" uses all history; "sliding" uses a fixed lookback.
        sliding_size: Lookback window size when window_type is "sliding".
        prediction_horizon: Number of bars each target label looks ahead.

    Returns:
        Predicted signal series with range [-1.0, 1.0], no NaN values.
    """
    predictions = pd.Series(0.0, index=features.index)
    model = None
    scaler = None

    if prediction_horizon < 1:
        raise ValueError("prediction_horizon must be >= 1")

    for i in range(min_train_size, len(features)):
        # Retrain every retrain_freq days
        if model is None or (i - min_train_size) % retrain_freq == 0:
            # A label at row t is observable only once t + horizon <= i.
            train_stop = max(0, i - prediction_horizon + 1)
            start = (
                max(0, train_stop - sliding_size)
                if window_type == "sliding"
                else 0
            )
            X_train = features.iloc[start:train_stop].values
            y_train = labels.iloc[start:train_stop].values

            # Drop rows with NaN
            valid = ~(np.isnan(X_train).any(axis=1) | np.isnan(y_train))
            X_train = X_train[valid]
            y_train = y_train[valid]

            # Too few rows, or a single class (a sustained one-directional
            # trend, which crashes fit()/predict_proba() on every model type
            # below): skip the retrain. The previous model keeps serving; before
            # the first model there is no prediction.
            if len(X_train) >= 50 and len(np.unique(y_train)) >= 2:
                # Standardization: fit only on training set
                scaler = StandardScaler()
                X_train = scaler.fit_transform(X_train)

                # Build the model
                if model_type == "random_forest":
                    model = RandomForestClassifier(
                        n_estimators=100, max_depth=5, random_state=42,
                    )
                elif model_type == "gradient_boosting":
                    model = GradientBoostingClassifier(
                        n_estimators=100, max_depth=3, learning_rate=0.05,
                        random_state=42,
                    )
                elif model_type == "ridge":
                    model = LogisticRegression(penalty="l2", C=1.0, random_state=42)
                else:
                    raise ValueError(f"Unsupported model_type: {model_type}")

                model.fit(X_train, y_train)

        if model is None:
            continue

        # Predict today
        X_today = features.iloc[i : i + 1].values
        if np.isnan(X_today).any():
            predictions.iloc[i] = 0.0
            continue

        X_today = scaler.transform(X_today)

        if hasattr(model, "predict_proba"):
            prob = model.predict_proba(X_today)[0, 1]
            predictions.iloc[i] = prob * 2 - 1  # [0,1] -> [-1,1]
        else:
            predictions.iloc[i] = float(model.predict(X_today)[0])

    # Output contract: no NaN, clipped to [-1, 1]
    predictions = predictions.fillna(0.0).clip(-1.0, 1.0)
    return predictions


class SignalEngine:
    """Complete ML strategy with built-in data validation and safety."""

    def generate(self, data_map: dict) -> dict:
        """Generate signals for each symbol.

        Args:
            data_map: code -> OHLCV DataFrame.

        Returns:
            code -> signal Series in [-1.0, 1.0].
        """
        signals = {}
        for code, df in data_map.items():
            if not validate_data(df):
                print(f"[WARN] {code}: data quality insufficient, skipping")
                continue

            features = build_features(df)
            prediction_horizon = 5
            future_returns = (
                df["close"].shift(-prediction_horizon) / df["close"] - 1
            )
            labels = (future_returns > 0).astype(float).where(future_returns.notna())
            signal = walk_forward_predict(
                features,
                labels,
                prediction_horizon=prediction_horizon,
            )
            signals[code] = signal

        return signals

Feature Engineering Reference

The table below lists all default features. Add or remove features as needed — build_features() is the customisation point.

Feature NameFormulaMeaning
ret_5dclose.pct_change(5, fill_method=None)Past 5-day return (short-term momentum)
ret_20dclose.pct_change(20, fill_method=None)Past 20-day return (medium-term momentum)
vol_20dreturns.rolling(20).std()20-day volatility
rsi_14See RSI formula in codeRelative Strength Index (division-by-zero guarded)
ma_ratioclose / close.rolling(20).mean()Degree of deviation from the 20-day moving average
volume_ratiovolume / volume.rolling(20).mean()Volume ratio (current volume vs 20-day average)
bb_position(close - bb_lower) / (bb_upper - bb_lower)Bollinger Band position (zero-bandwidth guarded)
high_low_ratio(high - low) / closeIntraday range ratio
close_open_ratio(close - open) / openIntraday return
skew_20dreturns.rolling(20).skew()Return skewness

Model Selection Guide

ModelAdvantagesDisadvantagesApplicable Scenario
RandomForestClassifierHard to overfit, robust to hyperparameters, can output feature importanceWeaker at capturing trend-style featuresDefault first-choice model, medium data size
GradientBoostingClassifierHigh accuracy, captures complex nonlinear relationshipsEasy to overfit, slow to train, requires careful tuningSufficient data and tuning experience
Ridge / LogisticRegressionFast training, interpretable, difficult to overfitCaptures only linear relationshipsFast baseline, few features, small dataset

Parameters

ParameterDefaultDescription
model_type"random_forest"Model type: random_forest / gradient_boosting / ridge
min_train_size252Minimum training-set size (starting length of the expanding window)
retrain_freq20Retraining frequency (every N trading days)
prediction_horizon5Prediction horizon (future N-day return)
n_estimators100Number of trees for tree-based models
max_depth5Maximum tree depth (prevents overfitting)
threshold0.0Signal filtering threshold (abs(signal) < threshold is set to 0)
window_type"expanding"Training window: expanding (all history) or sliding (fixed lookback)
sliding_size504Lookback size for sliding window (2 years of trading days)

Common Pitfalls

The pipeline code above already handles data leakage, standardization leakage, inf/NaN propagation, and retraining frequency. The following pitfalls still require your judgement:

  1. Overfitting: trees that are too deep (max_depth > 10), too many features, or too small a training set. Keep max_depth=3~5 and feature count < 15
  2. Class imbalance: in bull markets the up/down ratio may be 7:3, so the model may prefer predicting the majority class. Use class_weight="balanced" or SMOTE if needed
  3. Look-ahead bias (non-leakage form): computing features from today's close and predicting today's signal. Make sure features use only data from T-1 and earlier

Dependencies

pip install scikit-learn pandas numpy

Signal Convention

  • predict_proba[:, 1] mapped through prob * 2 - 1 to [-1.0, 1.0] (continuous-strength signal)
  • Or discrete signals from predict() in {-1, 0, 1} (short, neutral, long)
  • Positive values = bullish direction, negative values = bearish direction, absolute value = confidence strength
  • Output is guaranteed: no NaN, no inf, clipped to [-1.0, 1.0]

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Compare a Chinese company's US depositary receipts, Hong Kong shares, and mainland shares after adjusting for exchange rates and share ratios. Analyze pricing gaps, valuation differences, and delisting risks to support research into its different listings.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Compare A-share and H-share pricing gaps
  • Measure ADR premiums against Hong Kong shares
  • Review US delisting risks and Hong Kong listing alternatives
HKUDS/Vibe-Trading3.6만2026년 10월 21일 업데이트

akshare

무료

Retrieve stock prices, futures quotes, exchange rates, and economic indicators with the free Python library AKShare. The skill explains market-specific functions, symbol formats, and data preparation for analysis and testing strategies against historical data.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Retrieve historical prices for Chinese, US, and Hong Kong stocks
  • Collect Chinese GDP and consumer price data
  • Check futures quotes and foreign exchange rates
HKUDS/Vibe-Trading3.6만2026년 10월 21일 업데이트

alpha-zoo

무료

Explore bundled libraries of investment factors, signals used to compare securities, and inspect individual formulas and metadata. Evaluate a factor or an entire library across a chosen universe and period, producing aggregate performance statistics and an HTML report.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Find bundled momentum or reversal factors
  • Inspect a named factor's formula and metadata
  • Compare a factor library across a chosen universe and period
HKUDS/Vibe-Trading3.6만2026년 10월 21일 업데이트

Assess whether a Chinese A-share company may receive ST or *ST risk warnings next financial year. Review financial forecasts, dividends, audit opinions, and regulatory penalties, then report risk severity separately from confidence in the prediction.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Reviewing a Chinese A-share company's future ST risk
  • Assessing escalation risk for an existing ST or *ST stock
  • Checking revenue, equity, and dividend warning signs
HKUDS/Vibe-Trading3.6만2026년 10월 21일 업데이트

Plan portfolio allocations across assets using risk, correlations, return forecasts, and trading costs. Compare five optimization methods, produce settings for config.json, and define rebalancing rules that adjust holdings back toward their target weights.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Comparing allocations across stocks, bonds, and gold
  • Choosing an optimizer based on return forecasts and correlations
  • Preparing portfolio settings for config.json
HKUDS/Vibe-Trading3.6만2026년 10월 21일 업데이트

Diagnose failed or abnormal backtests, which test trading strategies on historical data. Inspect result files, strategy code, and configuration to identify bugs, make targeted repairs, rerun tests, and verify the updated metrics.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Investigating backtest execution errors
  • Finding why a strategy produces no trades
  • Checking delayed trades or low capital usage
HKUDS/Vibe-Trading3.6만2026년 10월 21일 업데이트

HKUDS의 스킬 모두 보기

이 스킬의 문제 신고