Python implementation of the paper:
"Incorporating I Ching Knowledge Into Prediction Task via Data Mining" Liu et al., Journal of Database Management, Volume 34, Issue 3 DOI: 10.4018/JDM.322097
The model combines traditional I Ching divination with modern machine learning to predict stock price trends (Positive / Neutral / Negative). It outperforms SVM, XGBoost, RF, GRU, and LSTM-Att in the paper's experiments (Macro F1: 63.33% vs. best baseline 61.08%).
All Features
│
▼
Random Forest (Gini importance)
│ top-30 features
▼
Three Vitals Mapping
Ten (天) — macro/environment
Chi (地) — size & flow
Jin (人) — fundamentals
│ top-2 per Vital = 6 features
▼
┌─────────────────────────────┐ ┌──────────────────────┐
│ Hexagram Generator │ │ Decoder │
│ │ │ │
│ Four Signs (sliding k=8) │──────▶│ Hexagram Explainer │
│ LaoYin=6 / ShaoYin=8 │ │ (Algorithm Two) │
│ ShaoYang=7 / LaoYang=9 │ │ │
│ ↓ │ │ Sentiment Classifier │
│ 6-bit Original Hexagram │ └──────────────────────┘
│ 6-bit Changed Hexagram │ │
└─────────────────────────────┘ ▼
Positive / Neutral / Negative
I Ching's framework for categorising all influencing factors:
| Vital | Chinese | Domain | Stock examples |
|---|---|---|---|
| Ten | 天 (Heaven) | Macro / external | Market index, sector ETF, policy |
| Chi | 地 (Earth) | Size & flow | Market cap, volume, fund flow |
| Jin | 人 (Human) | Fundamentals | Profit margin, debt ratio, ROE |
Each of the 6 feature values is mapped to a Four Signs code via a sliding window of size 8:
| Code | Name | Yao | Moving? | Boundary |
|---|---|---|---|---|
| 6 | LaoYin (老阴) | 0 (Yin) | Yes → Yang | [min, min+A) |
| 8 | ShaoYin (少阴) | 0 (Yin) | No | [min+A, min+A+B) |
| 7 | ShaoYang (少阳) | 1 (Yang) | No | [min+A+B, min+A+2B) |
| 9 | LaoYang (老阳) | 1 (Yang) | Yes → Yin | [min+A+2B, max] |
Where A = (1/8) × range, B = (3/8) × range.
- 6 Yao values (0/1) form a 6-bit binary string (bottom to top)
- The Vital ordering (which Vital maps to which Yao positions) is selected by trying all 6 permutations of [Ten, Jin, Chi] during
fit()and keeping the one with the best macro F1 - Original hexagram: raw Yao values
- Changed hexagram: LaoYang (9) flips 1→0, LaoYin (6) flips 0→1
Selects the I Ching explanation based on the number of moving Yaos (YbNum):
| YbNum | Explanation source |
|---|---|
| 0 | Original hexagram Gua text |
| 1 | Yao text of the single moving Yao |
| 2 | Yao text of the higher-position moving Yao |
| 3 | Original hexagram Gua text |
| 4 | Yao text of the lowest stable Yao |
| 5 | Yao text of the single stable Yao |
| 6 | Changed hexagram Gua text |
Huber regression is fit over a rolling window of closing prices. The slope coefficient a is classified using mean ± std thresholds derived from the normal distribution of all slopes across the dataset:
- Positive (rising):
a > mean + std - Neutral (stable):
mean − std ≤ a ≤ mean + std(~68% of samples) - Negative (falling):
a < mean − std
This matches the paper's statement: "the distribution of a is counted, and finally the label category is obtained according to the distribution interval of a."
Huber loss is used for robustness to outliers:
L(y, f(x)) = 0.5*(y-f(x))² if |y-f(x)| ≤ δ
δ*(|y-f(x)| - 0.5*δ) otherwise
iching/
├── iching_data.py # 64 hexagram dict, 384 Yao explanations, sentiment scores
├── iching_wilhelm_translation.js # Local copy of adamblvck/iching-wilhelm-dataset (MIT)
├── import_wilhelm.py # One-shot importer: regenerates iching_data.py from dataset
├── label_construction.py # Huber regression → {Positive, Neutral, Negative} labels
├── feature_selection.py # RF Gini importance + Three Vitals mapping → 6 features
├── hexagram_generator.py # Algorithm One: Four Signs + hexagram generation
├── hexagram_decoder.py # Algorithm Two: Hexagram Explainer + classifier
├── iching_model.py # Algorithm Three: full pipeline (IChingModel class)
├── baselines.py # SVM, XGBoost, RF, KNN, GRU, LSTM-Att models
├── benchmark.py # Train all 7 models and print comparison table
└── demo.py # End-to-end demo (AAPL via yfinance or synthetic data)
pip install numpy pandas scikit-learn xgboost torch yfinancefrom iching_model import IChingModel
# Define which features belong to each Vital
model = IChingModel(
tian_label=["market_index_return", "sector_etf_return"], # Ten / macro
di_label=["volume", "turnover_rate", "fund_flow"], # Chi / flow
ren_label=["net_profit_margin", "debt_ratio", "roe"], # Jin / fundamentals
label_window=60, # rolling window for Huber label construction
window_size=8, # Four Signs sliding window (paper default)
n_top_total=30, # RF top-N feature selection
n_top_per_vital=2, # features per Vital (→ 6 total)
)
model.fit(feature_df, close_series)
# Predict with I Ching explanations
results = model.predict_with_explanation(feature_df)
print(results[["label", "explanation"]].tail())
# Evaluate
metrics = model.evaluate(feature_df, close_series)
print(f"F1: {metrics['f1']:.4f}")
print(metrics["report"])cd iching
python demo.pyFetches AAPL data from yfinance (2015–2024) and runs the full pipeline. Falls back to synthetic data if yfinance is unavailable.
cd iching
python benchmark.py # AAPL via yfinance
python benchmark.py --ticker TSLA
python benchmark.py --synthetic # force synthetic dataSix comparison algorithms are implemented in baselines.py, matching the paper's Table 2 setup:
| Class | Algorithm | Notes |
|---|---|---|
SVMModel |
Support Vector Machine | RBF kernel, probability calibration, balanced class weights |
XGBoostModel |
eXtreme Gradient Boosting | 200 estimators, depth 6 |
RFModel |
Random Forest | 200 estimators, balanced class weights |
KNNModel |
K-Nearest Neighbours | k=5, distance-weighted, Euclidean |
GRUModel |
Gated Recurrent Unit | 2-layer, hidden=64, 20-day look-back, attention-free |
LSTMAttModel |
LSTM with Attention | 2-layer, hidden=64, 20-day look-back, additive attention |
All share a unified interface:
from baselines import SVMModel, XGBoostModel, RFModel, KNNModel, GRUModel, LSTMAttModel
model = SVMModel()
model.fit(X_train, y_train) # y in {-1, 0, 1}
preds = model.predict(X_test) # returns np.ndarray of {-1, 0, 1}Chronological 80/20 train/test split · 2515 trading days (2015–2024) · 11 features · macro-averaged metrics.
Test label distribution: 97 Negative / 195 Neutral / 200 Positive
| Model | Precision | Recall | F1 |
|---|---|---|---|
| GRU | 66.36% | 42.83% | 37.95% |
| LSTM-Att | 65.18% | 39.94% | 36.50% |
| I Ching | 34.26% | 36.28% | 32.25% |
| KNN | 33.26% | 32.09% | 29.80% |
| XGBoost | 48.73% | 33.02% | 28.15% |
| SVM | 26.86% | 35.61% | 27.78% |
| RF | 39.44% | 30.07% | 26.70% |
Test label distribution: 36 Negative / 261 Neutral / 195 Positive
| Model | Precision | Recall | F1 |
|---|---|---|---|
| LSTM-Att | 29.32% | 31.65% | 27.52% |
| SVM | 27.40% | 27.55% | 27.31% |
| XGBoost | 30.18% | 32.26% | 24.87% |
| GRU | 26.28% | 30.15% | 24.83% |
| KNN | 24.35% | 31.24% | 24.26% |
| I Ching | 29.27% | 31.08% | 23.93% |
| RF | 17.36% | 31.93% | 22.49% |
All models cluster tightly (23–28% F1) on the index. The Neutral-heavy label distribution (53% of test samples) reflects that the S&P 500 spends most of its time drifting sideways. No model reliably catches Negative (bear) periods — only 36 test samples — because a single-ticker feature set lacks the cross-stock signals needed to separate sharp drawdowns from sideways drift.
| Model | Precision | Recall | F1 |
|---|---|---|---|
| I Ching | 65.41% | 61.51% | 63.33% |
| LSTM-Att | 61.85% | 60.33% | 61.08% |
| GRU | 60.15% | 60.71% | 60.43% |
| RF | 46.46% | 68.16% | 55.26% |
| XGBoost | 46.31% | 68.05% | 55.11% |
| KNN | 52.00% | 56.77% | 54.05% |
| SVM | 45.51% | 67.46% | 54.35% |
Note on differences from the paper: The paper uses 3000 Chinese stocks with full quarterly fundamentals and daily money-flow data. This benchmark uses a single ticker with 11 features, 4 of which are synthetic (fundamentals not available from yfinance). The I Ching model's advantage is strongest in the multi-stock, industry-segmented setting — see Table 3 where industry-specific F1 reaches 76.94% (Power and Energy).
A full audit against the paper's three algorithms identified and fixed three critical bugs.
Paper: del sortedvec[m] removes the oldest element by its tracked index position m in the sorted array.
Was: sortedvec.remove(old_val) — removes the first occurrence by value, silently producing wrong results when duplicate values exist in the window.
Fix: Uses bisect.bisect_left to locate the exact index of the oldest value, then del sortedvec[m] by position. Also switched to bisect.insort for O(log n) insertion instead of re-sorting each step.
Paper: "the distribution of a is counted, and finally the label category is obtained according to the distribution interval of a" — mean ± std from the normal distribution.
Was: np.percentile(slopes, 33.33) and np.percentile(slopes, 66.67) — always forced exactly ⅓ of samples into each class regardless of the actual distribution shape.
Fix: Thresholds are now mean ± n_std × std (default n_std=1.0), producing a Neutral-heavy label distribution (~68% within 1σ) consistent with the paper's normal distribution assumption.
Paper: All 6 permutations of the Three Vitals ordering are evaluated; the best-performing Yao sequence arrangement is kept.
Was: The permutation loop regenerated hexagrams from the same fixed six_features_ order on every iteration — all 6 iterations were identical, so the "best" ordering was never actually searched.
Fix: Each permutation now reorders the 6 features by Vital before generating hexagrams. The best ordering is saved as best_six_features_ and used by predict() and predict_with_explanation().
| Method | Description |
|---|---|
fit(feature_df, close) |
Train: build labels, select features, find best Vital ordering |
predict(feature_df) |
Return pd.Series of {1, 0, -1} predictions |
predict_with_explanation(feature_df) |
Return pd.DataFrame with prediction, label, and I Ching text |
evaluate(feature_df, close) |
Return dict with precision, recall, F1, classification report |
| Function | Module | Description |
|---|---|---|
build_labels(close, window, delta, n_std) |
label_construction |
Huber regression labels (mean±n_std thresholds) |
select_features(X, y, ...) |
feature_selection |
RF → Three Vitals → 6 features |
four_signs_generator(vec, k) |
hexagram_generator |
Algorithm One |
build_hexagram_sequences(df, features, k) |
hexagram_generator |
Full feature→hexagram pipeline |
hexagram_explainer(orig, changed, moving) |
hexagram_decoder |
Algorithm Two |
decode_sequence(hex_records) |
hexagram_decoder |
Batch decode to predictions |
Liu, W., Chen, S., Huang, G., Lu, L., Li, H., & Sun, G. (2023). Incorporating I Ching Knowledge Into Prediction Task via Data Mining. Journal of Database Management, 34(3). https://doi.org/10.4018/JDM.322097