When to Use
Use this skill when building quantitative models to predict highly asymmetric, rare events (e.g., flash crashes, limit-up/limit-down halts, or rare alpha signals). Financial datasets for rare events are often 99% noise and 1% signal. Standard models trained on this data will optimize for "Accuracy" by predicting 0 (noise) every time, completely ignoring the signal. This utility enforces class balancing to force the model to learn the minority class.
When NOT to Use
- The classes are only mildly imbalanced (say, better than 1:4). Reweighting and undersampling both distort the class prior; below a real imbalance problem they cost calibration and buy nothing.
- The model's probability output feeds expected-value sizing and you will not recalibrate. Both weighting and undersampling shift predicted probabilities away from the true event rate. If you cannot apply
correct_undersampling_bias(or an equivalent calibration step), leave the prior alone and move the decision threshold instead. - The minority class is small in absolute terms, not just in proportion. With a few dozen positive examples, rebalancing amplifies noise; the constraint is sample count, not class ratio.
- You are balancing a validation or test split. Out-of-sample data must retain the market's real event rate — see Common Pitfalls.
Prerequisites
- A binary or multi-class integer target array (
y) representing the rare event.compute_class_weightshandles multi-class;random_undersampleandcompute_scale_pos_weightare binary-only. - A feature matrix (
X) whose rows align withyalong the first axis. - NumPy >= 1.21 (repository
requirements.txt); no scikit-learn or imbalanced-learn dependency is required by the helper itself.
Workflow
- Evaluation Setup: Before attempting to balance the data, set validation metrics to Precision-Recall AUC (PR-AUC) or F1-Score. ROC-AUC and Accuracy are misleading under heavy imbalance because the large true-negative count keeps the false-positive rate small even when precision is poor.
- Split First, Balance Second: Perform the time-aware train/validation split before touching the class distribution. Balancing is a training-set-only transformation.
- Cost-Sensitive Learning (Recommended): This approach discards no data. Match the helper to the estimator's parameter shape:
- scikit-learn estimators taking a
class_weightmapping (e.g.RandomForestClassifier,LogisticRegression): passImbalanceHandler.compute_class_weights(y_train). - Gradient-boosting libraries taking the scalar
scale_pos_weight(XGBoost, LightGBM): passImbalanceHandler.compute_scale_pos_weight(y_train). This isnegatives / positives, a different quantity from the weight dict — the two are not interchangeable.
- scikit-learn estimators taking a
- Undersampling (Alternative): If the dataset is too large to train on, use
ImbalanceHandler.random_undersample(X_train, y_train, majority_ratio=...)to reduce the majority class to a chosen multiple of the minority count (1.0= parity). Recordbeta = kept_majority / original_majorityfrom the log line — step 5 needs it. - Recalibrate Probabilities Before Trading On Them: A model trained on undersampled data reports probabilities inflated towards the minority class. Pass
predict_probaoutput throughImbalanceHandler.correct_undersampling_bias(p, beta)before it drives position sizing, expected value, or any absolute probability threshold. Ranking metrics (PR-AUC, ROC-AUC) are unaffected by the correction; monetary decisions are not. - Score on the Untouched Split: Predict on the unmodified validation set, which still carries the market's real event rate, and evaluate with Precision, Recall, F1, and the confusion matrix.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Data Leakage via Resampling Validation: Applying SMOTE or Undersampling to the entire dataset before doing a train-test split. This severely biases validation results because the validation set is no longer representative of the true market distribution.
- Using Accuracy as a Metric: A model predicting "No Crash" every day achieves 99.9% accuracy but is completely useless for trading.
- Overusing Oversampling (SMOTE) in Finance: Financial data is incredibly noisy. Generating synthetic financial samples via interpolation (SMOTE) often creates unrealistic market states that confuse the model.
- Confusing the Weight Dict With
scale_pos_weight:compute_class_weightsreturns{label: weight}for scikit-learn'sclass_weight; XGBoost'sscale_pos_weightis a single scalar (negatives / positives). Passing the dict where a scalar is expected fails loudly; passing the minority weightn / (2 * minority_count)as ascale_pos_weightfails silently with a different, incorrect amount of upweighting. - Trading On Uncalibrated Post-Balancing Probabilities: After 1:1 undersampling of a 1% event, a model that has learned nothing beyond the base rate outputs ~0.5. Treating that as "50% chance of a crash" oversizes positions by two orders of magnitude. Correct for the retention rate before any probability is spent as money.
- Reseeding the Global RNG: A resampling helper that calls
np.random.seed()silently reseeds the caller's process-wide generator, making later splits, model initialisation, and Monte Carlo runs deterministic functions of the resampler's seed.random_undersampleuses a localnp.random.default_rngfor this reason. - Balancing Across the Purge/Embargo Boundary: Undersampling picks majority rows at random, so it does not remove overlapping-label leakage. Purge and embargo first, then balance what survives.
Verification
- Generate an imbalanced dataset (99% class 0, 1% class 1).
compute_class_weightsmust return{0: n/(2*990...), 1: n/(2*10...)}with the class-1 weight ~99x the class-0 weight, and the per-sample weights must sum back ton_samples.compute_scale_pos_weightmust return 99.0 for the same array. - Run
random_undersampleand verify a 50/50 distribution, that returned rows stay in the original chronological order, and thatnp.random.get_state()is unchanged afterwards. - Feed an already-balanced array (equal class counts) to
random_undersampleand verify both classes survive intact. correct_undersampling_bias(0.5, beta)withbeta = minority/majoritymust return the original event rate.- Run
python -m unittest discover -s skills/class-imbalance-handling-for-rare-signal-events/scripts(33 tests), orpython tools/run_all_tests.pyfor the full repository suite.