Downloads · 30 days
23
4% of all-time downloads
jc-builds/stockprediction-ai
stockprediction-ai is a tabular regression model from jc-builds. Use it for the tabular regression task on the model card, and read the license before you ship it in a product. It is set up for lightgbm. The card lists the license as mit.
I reverse-engineered a hedge fund's trading strategy and open-sourced the model.
Downloads · 30 days
23
4% of all-time downloads
All-time downloads
657
Public
Repo size
1 MB
Likes
5
Public
Click a slice to open those files.
.txt3.1 MB · 72%
From the Hugging Face model README
I reverse-engineered a hedge fund's trading strategy and open-sourced the model.
A LightGBM regressor that predicts next-day log returns for 150 US stocks, paired with a one-line regime rule that decides whether to act on the prediction. Trained on a MacBook M1. No cloud GPU, no paid data beyond a single FMP API subscription.
Author: @jc_builds

| Regime | Strategy vs B&H | Strategy vs SPY |
|---|---|---|
| 2010 bull | −0.1% | +10.9% ✓ |
| 2018 bear | +11.9% ✓ | +18.1% ✓ |
| 2020 COVID | +22.0% ✓ | +38.8% ✓ |
| 2022 bear | −0.0% | −3.8% |
| Live 2025–26 | −3.2% | −6.2% |
| 5-split avg | +6.1% | +11.6% |
The model beats buy-and-hold in crashes (2018, 2020) and is roughly flat in bulls and grinding bears (2010, 2022, live). It is a crash shield, not a stock picker.

The whole system is five boring parts:
t to predict day t+1.No shorts. No leverage. No options. No intraday.

The raw model was already decent, but the thing that actually moved metrics was a one-line regime gate:
# Pseudocode — see src/strategy.py for the real version
bull = spy_price > spy_200_day_moving_average
threshold = bull_threshold if bull else bear_threshold # bull: -0.003, bear: tuned
position = 1 if pred > threshold else 0
That one if statement lifted the 2018 split from flat to +11.85% vs buy-and-hold.

The training is a classic walk-forward: for each test year Y, train on all data from 2005 through Y-1, never touching Y. For the live split, training ran through 2025-04-22 and testing ran forward from there.
Where it won (crashes):
Where it barely won or lost (bulls and grinding bears):
Average across 675 stock-years tested: +11.57% excess vs SPY, +6.11% excess vs per-stock buy-and-hold.

Eight model versions were tried. Seven regressed. One shipped. The champion isn't clever — it's the simplest thing that works plus one regime rule.
| Version | Idea | Outcome |
|---|---|---|
| v1 | Plain LightGBM, MSE, val-tuned threshold | baseline that works |
| v2 | Asymmetric y-weighted loss | regressed — early-stopped at iter 1 |
| v3 | Magnitude-weighted training | regressed — averaged away crash calls |
| v4 | v1 + regime-aware threshold | champion — shipped |
| v4b | v4 with fixed threshold | slight regression |
| v5 | 5-seed ensemble | regressed — averaged away high-conviction crash picks |
| v6 | P(big-down) classifier | regressed — target too rare |
| v7 | MLX MLP on Apple GPU | regressed — calibration collapsed |
| v8 | v4 trained on 2015+ only | regressed — best_iter 120 → 5, lost regularization |
| v9 | Multi-modal top-10 rotation | ships separately, feast-or-famine profile |

This repository contains:
stockprediction-ai/
├── README.md this file
├── config.json hyperparameters, feature columns, regime thresholds, splits
├── results.json per-split metrics (strat final, B&H final, excess)
├── boosters/
│ ├── 2010.txt LightGBM text-format booster, trained on 2005–2009
│ ├── 2018.txt trained on 2005–2017
│ ├── 2020.txt trained on 2005–2019
│ ├── 2022.txt trained on 2005–2021
│ └── live.txt trained on 2005-01-01 through 2025-04-22
└── images/ charts used in this card
Boosters are plain LightGBM text files — load them with any LightGBM runtime, no Python required.
import json
import lightgbm as lgb
import numpy as np
import pandas as pd
from huggingface_hub import hf_hub_download
REPO = "jc-builds/stockprediction-ai"
# 1. Grab the live booster and config
booster_path = hf_hub_download(REPO, "boosters/live.txt")
config_path = hf_hub_download(REPO, "config.json")
booster = lgb.Booster(model_file=booster_path)
config = json.loads(open(config_path).read())
feature_cols = config["feature_columns"]
# 2. Build a feature row for one stock on one trading day
# (see github.com/jc-builds/stockprediction/blob/main/src/features.py
# for the full 47-feature builder — the code is open-source).
features: pd.DataFrame = build_features_for_today(symbol="AAPL")
x = features[feature_cols].astype(float).values
# 3. Predict next-day log return
pred = booster.predict(x)[0]
# 4. Apply the regime rule
bull = spy_close_today > spy_200_day_ma_today
thr_bull = config["regime_rule"]["bull_threshold"] # -0.003
thr_bear = config["splits"]["live"]["bear_threshold"]
threshold = thr_bull if bull else thr_bear
position = 1 if pred > threshold else 0 # 1=long, 0=cash
best_iter=1 on v4 means the model "gave up" — it returned a near-constant prediction. Any real deployment would need to detect this (e.g., refuse to trade when val loss is flat) rather than fall through to always-long.git clone https://github.com/jc-builds/stockprediction
cd stockprediction
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Set FMP_API_KEY in .env, then:
PYTHONPATH=src python src/run.py --model v4
If this was useful in your own work or teaching, please cite:
@misc{jcbuilds_stockprediction_2026,
author = {Jared Cassoutt (@jc_builds)},
title = {Stock Prediction AI: Regime-Aware LightGBM},
year = {2026},
howpublished = {\url{https://huggingface.co/jc-builds/stockprediction-ai}},
}
MIT. Do whatever you want, but don't sue me when you lose money.