quantmine 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. quantmine-0.2.0/.airflowignore +10 -0
  2. quantmine-0.2.0/.gitignore +40 -0
  3. quantmine-0.2.0/.python-version +1 -0
  4. quantmine-0.2.0/LICENSE +21 -0
  5. quantmine-0.2.0/PKG-INFO +222 -0
  6. quantmine-0.2.0/README.md +200 -0
  7. quantmine-0.2.0/airflow.cfg.example +18 -0
  8. quantmine-0.2.0/config.example.yaml +33 -0
  9. quantmine-0.2.0/pipelines/DAG_pipeline.py +76 -0
  10. quantmine-0.2.0/pipelines/task_1.py +70 -0
  11. quantmine-0.2.0/pipelines/task_2.py +45 -0
  12. quantmine-0.2.0/pipelines/task_3.py +59 -0
  13. quantmine-0.2.0/pipelines/task_retry.py +43 -0
  14. quantmine-0.2.0/pyproject.toml +33 -0
  15. quantmine-0.2.0/quantmine/__init__.py +97 -0
  16. quantmine-0.2.0/quantmine/back_testing.py +381 -0
  17. quantmine-0.2.0/quantmine/config.py +115 -0
  18. quantmine-0.2.0/quantmine/data_acquisition.py +326 -0
  19. quantmine-0.2.0/quantmine/datareader.py +119 -0
  20. quantmine-0.2.0/quantmine/factor_attribution.py +70 -0
  21. quantmine-0.2.0/quantmine/factor_mining.py +192 -0
  22. quantmine-0.2.0/quantmine/factor_register.py +73 -0
  23. quantmine-0.2.0/quantmine/ic_calculator.py +543 -0
  24. quantmine-0.2.0/quantmine/load_config.py +16 -0
  25. quantmine-0.2.0/task_2.py +45 -0
  26. quantmine-0.2.0/test/README.md +51 -0
  27. quantmine-0.2.0/test/conftest.py +89 -0
  28. quantmine-0.2.0/test/test_calculate_all_factors.py +107 -0
  29. quantmine-0.2.0/test/test_config.py +80 -0
  30. quantmine-0.2.0/test/test_constituents_source.py +65 -0
  31. quantmine-0.2.0/test/test_cs_ic_golden.py +82 -0
  32. quantmine-0.2.0/test/test_factor_registry.py +102 -0
  33. quantmine-0.2.0/test/test_factors_golden.py +99 -0
  34. quantmine-0.2.0/test/test_forward_returns_golden.py +67 -0
  35. quantmine-0.2.0/test/test_market_data.py +31 -0
  36. quantmine-0.2.0/test/test_quantile_backtest.py +119 -0
  37. quantmine-0.2.0/test/test_turnover_cost.py +81 -0
  38. quantmine-0.2.0/tests/.gitkeep +0 -0
  39. quantmine-0.2.0/uv.lock +5000 -0
@@ -0,0 +1,10 @@
1
+ .venv
2
+ .venv-win
3
+ tmp
4
+ notebooks
5
+ __pycache__
6
+ airflow
7
+ quantmine
8
+ scripts
9
+ tests
10
+ data
@@ -0,0 +1,40 @@
1
+ # --- market data & run artifacts (never committed: size + data licensing) ---
2
+ data/
3
+ tmp/
4
+ analysis/
5
+ output.txt
6
+ run.log
7
+ *.parquet
8
+ *.pkl
9
+ *.xlsx
10
+
11
+ # --- airflow runtime (airflow.cfg contains secret keys; db/logs are local state) ---
12
+ airflow/
13
+ *.db
14
+ *.db-shm
15
+ *.db-wal
16
+
17
+ # --- python ---
18
+ __pycache__/
19
+ *.pyc
20
+ .venv/
21
+ .venv-win/
22
+ .ipynb_checkpoints/
23
+
24
+ # --- build & test artifacts ---
25
+ dist/
26
+ build/
27
+ *.egg-info/
28
+ .pytest_cache/
29
+ .coverage
30
+ htmlcov/
31
+
32
+ # --- local scratch / superseded scripts ---
33
+ scripts/
34
+
35
+ # --- generated dashboards (frontend being rebuilt) ---
36
+ factor_analysis_dashboard.html
37
+
38
+ # --- editor/os noise ---
39
+ .DS_Store
40
+ Thumbs.db
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 b00831205-web
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,222 @@
1
+ Metadata-Version: 2.4
2
+ Name: quantmine
3
+ Version: 0.2.0
4
+ Summary: Equity factor research library with honest statistics: Newey-West IC tests, multiple-testing control, point-in-time universe, turnover-based costs, Carhart attribution
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.13
8
+ Requires-Dist: numpy>=2.4
9
+ Requires-Dist: pandas>=3.0
10
+ Requires-Dist: pyarrow>=24.0
11
+ Requires-Dist: pyyaml>=6.0
12
+ Requires-Dist: scipy>=1.17
13
+ Requires-Dist: statsmodels>=0.14
14
+ Provides-Extra: data
15
+ Requires-Dist: beautifulsoup4>=4.14; extra == 'data'
16
+ Requires-Dist: curl-cffi>=0.15; extra == 'data'
17
+ Requires-Dist: pandas-datareader>=0.11; extra == 'data'
18
+ Requires-Dist: yfinance>=1.4; extra == 'data'
19
+ Provides-Extra: viz
20
+ Requires-Dist: plotly>=6.8; extra == 'viz'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # quantmine
24
+
25
+ An equity factor research library built around one principle: **statistical
26
+ honesty**. Every step that commonly inflates backtest results — survivorship
27
+ bias, overlapping-return autocorrelation, multiple testing, look-ahead in
28
+ orthogonalization, unrealistic transaction costs — is explicitly addressed,
29
+ and conclusions are reported with their uncertainty, not just their point
30
+ estimates.
31
+
32
+ The repo doubles as a full S&P 500 research case study: the library
33
+ (`quantmine/`), the daily Airflow pipeline (`pipelines/`), and the findings
34
+ below were produced by the same code you can `pip install`.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install quantmine # library only
40
+ pip install "quantmine[data]" # + yfinance download stack
41
+ ```
42
+
43
+ For development (repo checkout, Python ≥ 3.13, [uv](https://docs.astral.sh/uv/)):
44
+
45
+ ```bash
46
+ uv sync
47
+ python -m pytest test
48
+ ```
49
+
50
+ ## Quick start
51
+
52
+ Bring your own price data — any wide DataFrame (index = trading days,
53
+ columns = tickers) works; nothing is hard-wired to yfinance or the S&P 500:
54
+
55
+ ```python
56
+ import pandas as pd
57
+ import quantmine as qf
58
+
59
+ # 1) Wrap your data (or use qf.ParquetSource / qf.YFinanceSource)
60
+ data = qf.MarketData(close=close_df, volume=volume_df)
61
+
62
+ # 2) Register a custom factor — built-in factors register automatically
63
+ @qf.factor_register("my_reversal")
64
+ def my_reversal(close: pd.DataFrame, tickers: list) -> pd.DataFrame:
65
+ return -close[tickers].pct_change(5)
66
+
67
+ # 3) Compute all registered factors (dependencies resolve automatically)
68
+ pool = qf.build_param_pool(data, day=5, halflife=10, period=20)
69
+ failed, factors = qf.calculate_all_factors(pool)
70
+
71
+ # 4) Cross-sectional IC with Newey-West t-stats and multiple-testing control
72
+ fwd = qf.forward_return(data.close, periods=[1, 5, 20])
73
+ cs_ic = qf.CS_Information_Correlation(factors, fwd, output_path="cs_ic.parquet")
74
+ report = qf.multiple_testing(qf.newey_west_summary(cs_ic))
75
+
76
+ # 5) Quantile backtest on a point-in-time universe, turnover-based costs
77
+ universe = qf.MembershipTableSource(membership_df) # or qf.StaticUniverse([...])
78
+ results, history = qf.quantile_backtest(universe, factors, ["my_reversal"], fwd)
79
+ daily = qf.expand_all_to_daily_returns(history, data.close)
80
+
81
+ # 6) Carhart four-factor attribution of the long-short returns (daily, HAC)
82
+ french = qf.load_french_factors("ff3_daily.csv", "momentum_daily.csv")
83
+ model = qf.carhart_attribution(daily[("my_reversal", 20)]["long_short"], french)
84
+ ```
85
+
86
+ ### Extension points
87
+
88
+ | Protocol / hook | Purpose | Ships with |
89
+ |---|---|---|
90
+ | `DataSource.load(...) -> MarketData` | plug in any price/volume source | `ParquetSource`, `CSVSource`, `ExcelSource`, `YFinanceSource` |
91
+ | `ConstituentsSource.get_constituents(date) -> set` | point-in-time universe from any provider | `MembershipTableSource` (interval table), `StaticUniverse` |
92
+ | `@factor_register(name)` | add factors; params injected by name, factor-on-factor dependencies resolved | 8 built-in factors |
93
+ | `config.example.yaml` | every pipeline parameter as validated dataclasses via `qf.load_configs` | defaults documented in-file |
94
+
95
+ ## Headline result (S&P 500 case study)
96
+
97
+ The 20-day average volume factor (`TwentyDayAvgVol`) is the only candidate
98
+ that survives the full testing gauntlet:
99
+
100
+ | Stage | Result |
101
+ |---|---|
102
+ | Train IC (2015–2023, Newey-West) | t = 3.78 (20d holding), passes Bonferroni & Benjamini-Hochberg across all 18 factor × horizon tests |
103
+ | Out-of-sample quintiles (2024–2026) | Monotonic (Spearman 0.9); long-short gross ~14 %/yr (Sharpe ~1.5), ~1.4 Sharpe net of turnover-based costs |
104
+ | Carhart 4-factor attribution (daily, HAC, net of costs) | Market beta 0.24 (significant), large-cap tilt (SMB −0.13); momentum & value loadings insignificant |
105
+ | Net alpha | ~10 %/yr net of costs (t = 1.78, p ≈ 0.07, n = 603 daily obs) — economically meaningful, **marginally short of the 5 % significance bar** on 2.4 years of out-of-sample data |
106
+
107
+ The honest conclusion: the factor's IC is robustly significant in-sample under
108
+ conservative testing; its out-of-sample net alpha is economically meaningful
109
+ but does not clear conventional significance. Live verification over a longer
110
+ window is required — and that is exactly what a research report should say.
111
+ (A low-power period-level regression, n ≈ 30, says nothing either way —
112
+ p ≈ 0.38 with a confidence interval wide enough to hold any conclusion. Test
113
+ power and test bookkeeping move the verdict as much as the signal does.)
114
+
115
+ The remaining seven candidate factors (momentum, short-term reversal,
116
+ volatility, downside volatility, volume-price correlation, …) fail the
117
+ corrected significance tests. Documenting *why* they fail is part of the
118
+ point.
119
+
120
+ ## Methodology highlights
121
+
122
+ - **Survivorship-bias correction** — the universe is rebuilt from historical
123
+ S&P 500 membership (764 tickers over 2015–2026); 569 were recoverable via
124
+ yfinance, and the residual gap is disclosed rather than hidden.
125
+ - **Point-in-time universe** — each backtest cross-section only contains
126
+ stocks that were actually index members on that date.
127
+ - **Newey-West IC tests** — daily ICs on overlapping k-day forward returns are
128
+ autocorrelated; plain `t = IR·√n` overstates significance several-fold. NW
129
+ (Bartlett kernel, lag = 2(k−1)) uses all daily observations while correcting
130
+ the standard error. A down-sampled IID test is kept as a robustness control.
131
+ - **Multiple-testing control** — Bonferroni and Benjamini-Hochberg across all
132
+ factor × holding-period combinations.
133
+ - **Train/test split with embargo** — factors are selected and the
134
+ orthogonalization is fit on 2015–2023 only; a gap of one month before the
135
+ test window prevents overlapping forward returns from leaking across the
136
+ split.
137
+ - **Expanding-window orthogonalization** — correlated factors are residualized
138
+ with betas estimated on data available up to each date (no full-sample
139
+ look-ahead).
140
+ - **Turnover-based transaction costs** — costs are charged on actual
141
+ membership turnover per rebalance, not on a flat 100 %-turnover assumption.
142
+ - **Sanity checks** — factor displacement and cross-sectional shuffling tests
143
+ confirm the backtest machinery itself is not the source of the returns.
144
+ - **Tested** — the research chain is covered by a unit + golden-value test
145
+ suite (`test/`), including hand-computed backtest fixtures, determinism
146
+ checks, and point-in-time universe edge cases.
147
+
148
+ ## Pipeline
149
+
150
+ ```
151
+ yfinance (batch download, retry, blacklist, checkpoints)
152
+ │ quantmine/data_acquisition.py · pipelines/task_1.py
153
+
154
+ cleaning & merge (ffill, dedup) Airflow DAG: pipelines/DAG_pipeline.py
155
+ │ pipelines/task_2.py
156
+
157
+ factor computation (registry-driven, vectorized pandas)
158
+ │ quantmine/factor_mining.py · pipelines/task_3.py
159
+
160
+ IC testing: cross-sectional & time-series IC, NW t, BH/Bonferroni,
161
+ train/test split, orthogonalization quantmine/ic_calculator.py
162
+
163
+ quintile backtest: PIT universe, monotonicity, turnover costs,
164
+ displacement/shuffle sanity tests quantmine/back_testing.py
165
+
166
+ Carhart 4-factor attribution (daily, HAC) quantmine/factor_attribution.py
167
+ ```
168
+
169
+ Repository layout:
170
+
171
+ ```
172
+ quantmine/ research library (importable package)
173
+ pipelines/ Airflow DAG + daily CLI tasks
174
+ test/ pytest suite (unit + golden-value)
175
+ config.example.yaml all pipeline parameters, documented defaults
176
+ ```
177
+
178
+ ## Reproducing the case study
179
+
180
+ **Market data is not included** (Yahoo Finance terms of service do not permit
181
+ redistribution). To reproduce:
182
+
183
+ 1. Historical S&P 500 membership: provide a CSV with `ticker`, `start_date`,
184
+ `end_date` columns and point `SP500_MEMBERSHIP_CSV` at it.
185
+ 2. Prices/volumes: `python pipelines/task_1.py --date <ds> --batch manual`
186
+ downloads in batches with checkpointing, then `pipelines/task_2.py` cleans
187
+ and merges.
188
+ 3. Fama-French factors: download the daily FF3 and momentum CSVs from the
189
+ [Ken French Data Library](https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html)
190
+ into `tmp/ff3/`.
191
+ 4. Run the research chain: `python pipelines/task_3.py ...` for factors, then
192
+ the IC → backtest → attribution steps as in the quick start (or
193
+ `python -m quantmine.ic_calculator` for the packaged train/test workflow).
194
+
195
+ For the Airflow DAG, set `QUANT_PROJECT_ROOT` and `QUANT_PYTHON_BIN` (see
196
+ `DAG_pipeline.py` docstring) and copy `airflow.cfg.example` keys into your own
197
+ config — never commit a real `airflow.cfg`.
198
+
199
+ ## Known limitations
200
+
201
+ - ~195 of 764 historical members could not be recovered from yfinance (mostly
202
+ true delistings/acquisitions), so a residual survivorship bias remains and
203
+ likely flatters the results slightly.
204
+ - The out-of-sample window (2024–2026) covers a single market regime.
205
+ - The long-short portfolio carries a significant 0.24 market beta; a
206
+ beta-hedged variant is on the roadmap.
207
+ - Transaction cost model is a flat per-turnover rate; no market-impact or
208
+ borrow-cost modeling.
209
+
210
+ ## Roadmap
211
+
212
+ - [ ] Migrate storage from parquet files to PostgreSQL/DuckDB
213
+ - [ ] REST API + MCP server exposing the research chain as agent-callable tools
214
+ - [ ] RAG-based automated research reports (ChromaDB + LLM)
215
+ - [ ] Extend the Airflow DAG to cover IC testing → backtest → reporting
216
+ - [ ] Rebuild the analytics dashboard (frontend rewrite in progress)
217
+ - [ ] Scale the data layer (full US market) with PySpark
218
+ - [ ] Beta-hedged long-short variant; GARCH volatility targeting
219
+
220
+ ## License
221
+
222
+ MIT
@@ -0,0 +1,200 @@
1
+ # quantmine
2
+
3
+ An equity factor research library built around one principle: **statistical
4
+ honesty**. Every step that commonly inflates backtest results — survivorship
5
+ bias, overlapping-return autocorrelation, multiple testing, look-ahead in
6
+ orthogonalization, unrealistic transaction costs — is explicitly addressed,
7
+ and conclusions are reported with their uncertainty, not just their point
8
+ estimates.
9
+
10
+ The repo doubles as a full S&P 500 research case study: the library
11
+ (`quantmine/`), the daily Airflow pipeline (`pipelines/`), and the findings
12
+ below were produced by the same code you can `pip install`.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install quantmine # library only
18
+ pip install "quantmine[data]" # + yfinance download stack
19
+ ```
20
+
21
+ For development (repo checkout, Python ≥ 3.13, [uv](https://docs.astral.sh/uv/)):
22
+
23
+ ```bash
24
+ uv sync
25
+ python -m pytest test
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ Bring your own price data — any wide DataFrame (index = trading days,
31
+ columns = tickers) works; nothing is hard-wired to yfinance or the S&P 500:
32
+
33
+ ```python
34
+ import pandas as pd
35
+ import quantmine as qf
36
+
37
+ # 1) Wrap your data (or use qf.ParquetSource / qf.YFinanceSource)
38
+ data = qf.MarketData(close=close_df, volume=volume_df)
39
+
40
+ # 2) Register a custom factor — built-in factors register automatically
41
+ @qf.factor_register("my_reversal")
42
+ def my_reversal(close: pd.DataFrame, tickers: list) -> pd.DataFrame:
43
+ return -close[tickers].pct_change(5)
44
+
45
+ # 3) Compute all registered factors (dependencies resolve automatically)
46
+ pool = qf.build_param_pool(data, day=5, halflife=10, period=20)
47
+ failed, factors = qf.calculate_all_factors(pool)
48
+
49
+ # 4) Cross-sectional IC with Newey-West t-stats and multiple-testing control
50
+ fwd = qf.forward_return(data.close, periods=[1, 5, 20])
51
+ cs_ic = qf.CS_Information_Correlation(factors, fwd, output_path="cs_ic.parquet")
52
+ report = qf.multiple_testing(qf.newey_west_summary(cs_ic))
53
+
54
+ # 5) Quantile backtest on a point-in-time universe, turnover-based costs
55
+ universe = qf.MembershipTableSource(membership_df) # or qf.StaticUniverse([...])
56
+ results, history = qf.quantile_backtest(universe, factors, ["my_reversal"], fwd)
57
+ daily = qf.expand_all_to_daily_returns(history, data.close)
58
+
59
+ # 6) Carhart four-factor attribution of the long-short returns (daily, HAC)
60
+ french = qf.load_french_factors("ff3_daily.csv", "momentum_daily.csv")
61
+ model = qf.carhart_attribution(daily[("my_reversal", 20)]["long_short"], french)
62
+ ```
63
+
64
+ ### Extension points
65
+
66
+ | Protocol / hook | Purpose | Ships with |
67
+ |---|---|---|
68
+ | `DataSource.load(...) -> MarketData` | plug in any price/volume source | `ParquetSource`, `CSVSource`, `ExcelSource`, `YFinanceSource` |
69
+ | `ConstituentsSource.get_constituents(date) -> set` | point-in-time universe from any provider | `MembershipTableSource` (interval table), `StaticUniverse` |
70
+ | `@factor_register(name)` | add factors; params injected by name, factor-on-factor dependencies resolved | 8 built-in factors |
71
+ | `config.example.yaml` | every pipeline parameter as validated dataclasses via `qf.load_configs` | defaults documented in-file |
72
+
73
+ ## Headline result (S&P 500 case study)
74
+
75
+ The 20-day average volume factor (`TwentyDayAvgVol`) is the only candidate
76
+ that survives the full testing gauntlet:
77
+
78
+ | Stage | Result |
79
+ |---|---|
80
+ | Train IC (2015–2023, Newey-West) | t = 3.78 (20d holding), passes Bonferroni & Benjamini-Hochberg across all 18 factor × horizon tests |
81
+ | Out-of-sample quintiles (2024–2026) | Monotonic (Spearman 0.9); long-short gross ~14 %/yr (Sharpe ~1.5), ~1.4 Sharpe net of turnover-based costs |
82
+ | Carhart 4-factor attribution (daily, HAC, net of costs) | Market beta 0.24 (significant), large-cap tilt (SMB −0.13); momentum & value loadings insignificant |
83
+ | Net alpha | ~10 %/yr net of costs (t = 1.78, p ≈ 0.07, n = 603 daily obs) — economically meaningful, **marginally short of the 5 % significance bar** on 2.4 years of out-of-sample data |
84
+
85
+ The honest conclusion: the factor's IC is robustly significant in-sample under
86
+ conservative testing; its out-of-sample net alpha is economically meaningful
87
+ but does not clear conventional significance. Live verification over a longer
88
+ window is required — and that is exactly what a research report should say.
89
+ (A low-power period-level regression, n ≈ 30, says nothing either way —
90
+ p ≈ 0.38 with a confidence interval wide enough to hold any conclusion. Test
91
+ power and test bookkeeping move the verdict as much as the signal does.)
92
+
93
+ The remaining seven candidate factors (momentum, short-term reversal,
94
+ volatility, downside volatility, volume-price correlation, …) fail the
95
+ corrected significance tests. Documenting *why* they fail is part of the
96
+ point.
97
+
98
+ ## Methodology highlights
99
+
100
+ - **Survivorship-bias correction** — the universe is rebuilt from historical
101
+ S&P 500 membership (764 tickers over 2015–2026); 569 were recoverable via
102
+ yfinance, and the residual gap is disclosed rather than hidden.
103
+ - **Point-in-time universe** — each backtest cross-section only contains
104
+ stocks that were actually index members on that date.
105
+ - **Newey-West IC tests** — daily ICs on overlapping k-day forward returns are
106
+ autocorrelated; plain `t = IR·√n` overstates significance several-fold. NW
107
+ (Bartlett kernel, lag = 2(k−1)) uses all daily observations while correcting
108
+ the standard error. A down-sampled IID test is kept as a robustness control.
109
+ - **Multiple-testing control** — Bonferroni and Benjamini-Hochberg across all
110
+ factor × holding-period combinations.
111
+ - **Train/test split with embargo** — factors are selected and the
112
+ orthogonalization is fit on 2015–2023 only; a gap of one month before the
113
+ test window prevents overlapping forward returns from leaking across the
114
+ split.
115
+ - **Expanding-window orthogonalization** — correlated factors are residualized
116
+ with betas estimated on data available up to each date (no full-sample
117
+ look-ahead).
118
+ - **Turnover-based transaction costs** — costs are charged on actual
119
+ membership turnover per rebalance, not on a flat 100 %-turnover assumption.
120
+ - **Sanity checks** — factor displacement and cross-sectional shuffling tests
121
+ confirm the backtest machinery itself is not the source of the returns.
122
+ - **Tested** — the research chain is covered by a unit + golden-value test
123
+ suite (`test/`), including hand-computed backtest fixtures, determinism
124
+ checks, and point-in-time universe edge cases.
125
+
126
+ ## Pipeline
127
+
128
+ ```
129
+ yfinance (batch download, retry, blacklist, checkpoints)
130
+ │ quantmine/data_acquisition.py · pipelines/task_1.py
131
+
132
+ cleaning & merge (ffill, dedup) Airflow DAG: pipelines/DAG_pipeline.py
133
+ │ pipelines/task_2.py
134
+
135
+ factor computation (registry-driven, vectorized pandas)
136
+ │ quantmine/factor_mining.py · pipelines/task_3.py
137
+
138
+ IC testing: cross-sectional & time-series IC, NW t, BH/Bonferroni,
139
+ train/test split, orthogonalization quantmine/ic_calculator.py
140
+
141
+ quintile backtest: PIT universe, monotonicity, turnover costs,
142
+ displacement/shuffle sanity tests quantmine/back_testing.py
143
+
144
+ Carhart 4-factor attribution (daily, HAC) quantmine/factor_attribution.py
145
+ ```
146
+
147
+ Repository layout:
148
+
149
+ ```
150
+ quantmine/ research library (importable package)
151
+ pipelines/ Airflow DAG + daily CLI tasks
152
+ test/ pytest suite (unit + golden-value)
153
+ config.example.yaml all pipeline parameters, documented defaults
154
+ ```
155
+
156
+ ## Reproducing the case study
157
+
158
+ **Market data is not included** (Yahoo Finance terms of service do not permit
159
+ redistribution). To reproduce:
160
+
161
+ 1. Historical S&P 500 membership: provide a CSV with `ticker`, `start_date`,
162
+ `end_date` columns and point `SP500_MEMBERSHIP_CSV` at it.
163
+ 2. Prices/volumes: `python pipelines/task_1.py --date <ds> --batch manual`
164
+ downloads in batches with checkpointing, then `pipelines/task_2.py` cleans
165
+ and merges.
166
+ 3. Fama-French factors: download the daily FF3 and momentum CSVs from the
167
+ [Ken French Data Library](https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html)
168
+ into `tmp/ff3/`.
169
+ 4. Run the research chain: `python pipelines/task_3.py ...` for factors, then
170
+ the IC → backtest → attribution steps as in the quick start (or
171
+ `python -m quantmine.ic_calculator` for the packaged train/test workflow).
172
+
173
+ For the Airflow DAG, set `QUANT_PROJECT_ROOT` and `QUANT_PYTHON_BIN` (see
174
+ `DAG_pipeline.py` docstring) and copy `airflow.cfg.example` keys into your own
175
+ config — never commit a real `airflow.cfg`.
176
+
177
+ ## Known limitations
178
+
179
+ - ~195 of 764 historical members could not be recovered from yfinance (mostly
180
+ true delistings/acquisitions), so a residual survivorship bias remains and
181
+ likely flatters the results slightly.
182
+ - The out-of-sample window (2024–2026) covers a single market regime.
183
+ - The long-short portfolio carries a significant 0.24 market beta; a
184
+ beta-hedged variant is on the roadmap.
185
+ - Transaction cost model is a flat per-turnover rate; no market-impact or
186
+ borrow-cost modeling.
187
+
188
+ ## Roadmap
189
+
190
+ - [ ] Migrate storage from parquet files to PostgreSQL/DuckDB
191
+ - [ ] REST API + MCP server exposing the research chain as agent-callable tools
192
+ - [ ] RAG-based automated research reports (ChromaDB + LLM)
193
+ - [ ] Extend the Airflow DAG to cover IC testing → backtest → reporting
194
+ - [ ] Rebuild the analytics dashboard (frontend rewrite in progress)
195
+ - [ ] Scale the data layer (full US market) with PySpark
196
+ - [ ] Beta-hedged long-short variant; GARCH volatility targeting
197
+
198
+ ## License
199
+
200
+ MIT
@@ -0,0 +1,18 @@
1
+ # Minimal Airflow settings for this project.
2
+ # Copy the relevant keys into your own $AIRFLOW_HOME/airflow.cfg.
3
+ # NEVER commit the real airflow.cfg: it contains fernet_key / secret_key.
4
+
5
+ [core]
6
+ # Point Airflow at this repo so it picks up DAG_pipeline.py
7
+ dags_folder = /path/to/quant-factor-mining
8
+
9
+ # Generate your own key:
10
+ # python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
11
+ fernet_key = REPLACE_ME
12
+
13
+ [database]
14
+ sql_alchemy_conn = sqlite:////path/to/airflow/airflow.db
15
+
16
+ [api]
17
+ # Generate your own random secret, e.g.: openssl rand -base64 16
18
+ secret_key = REPLACE_ME
@@ -0,0 +1,33 @@
1
+ # quantmine 配置示例 —— 复制为 config.yaml 后按需修改。
2
+ # 所有 key 和字段都可省略, 省略即使用默认值(下方标注的即默认值)。
3
+
4
+ data_acquisition:
5
+ checkpoint_dir: tmp/checkpoint
6
+ max_retries: 3
7
+ wait: 60 # 批次下载失败后的重试等待秒数
8
+
9
+ momentum:
10
+ day: 5 # 动量回看天数(5 = 5日动量)
11
+
12
+ forward_return:
13
+ periods: [1, 5, 20] # 持有期(交易日)
14
+
15
+ newey_west:
16
+ lag_multiplier: 2 # NW修正的lag = lag_multiplier * (持有期-1)
17
+
18
+ orthogonalize:
19
+ threshold: 0.03 # 平均|IR|低于该值的因子被剔除
20
+ min_period: 60 # expanding beta 的最小估计窗口
21
+
22
+ time_series_stationary_test:
23
+ rolling_period: 126 # 滚动IC窗口(约半年)
24
+ periods: [1, 5, 20] # ACF的滞后阶
25
+
26
+ backtest:
27
+ part: 5 # 分位组数(5 = 五分组)
28
+
29
+ transaction_cost:
30
+ cost_per_trade: 0.001 # 单边费率(按实际换手计费)
31
+
32
+ carhart_attribution:
33
+ maxlags: 20 # HAC标准误的最大滞后
@@ -0,0 +1,76 @@
1
+ """Airflow DAG for the daily data-download / clean / factor-calculation loop.
2
+
3
+ Configuration comes from environment variables so the DAG file itself is
4
+ portable across machines:
5
+
6
+ QUANT_PROJECT_ROOT Absolute path of this project. Defaults to the parent
7
+ of the directory containing this file (repo root).
8
+ QUANT_PYTHON_BIN Python interpreter used to run the task scripts.
9
+ Defaults to ``python``. On a WSL-hosted Airflow that
10
+ drives a Windows venv, point it at
11
+ ``.venv-win/Scripts/python.exe`` (WSL interop launches
12
+ Windows executables directly, no PowerShell wrapper
13
+ needed).
14
+ """
15
+ import os
16
+ from datetime import datetime, timedelta
17
+ from airflow.providers.standard.operators.bash import BashOperator
18
+ from airflow.utils.trigger_rule import TriggerRule
19
+ from airflow.sdk import DAG
20
+
21
+ PROJECT_ROOT = os.environ.get("QUANT_PROJECT_ROOT",
22
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
23
+ PYTHON_BIN = os.environ.get("QUANT_PYTHON_BIN", "python")
24
+
25
+
26
+ def task_command(script: str) -> str:
27
+ """Build the bash command that runs one pipeline script.
28
+
29
+ Args:
30
+ script: Script filename inside the ``pipelines/`` directory.
31
+
32
+ Returns:
33
+ A bash command string with the Airflow ``ds``/``run_id`` templates.
34
+
35
+ Notes:
36
+ The command runs from the repo root so that relative data paths
37
+ (``tmp/``, ``data/``) resolve correctly.
38
+ """
39
+ return (f'cd "{PROJECT_ROOT}" && "{PYTHON_BIN}" pipelines/{script} '
40
+ '--date {{ ds }} --batch {{ run_id }}')
41
+
42
+
43
+ with DAG("quant_factor_mining",
44
+ default_args={
45
+ "retries": 1,
46
+ "retry_delay": timedelta(minutes=5)
47
+ },
48
+ description="quant_factor_mining pipeline version 0.1, using S&P 500",
49
+ schedule=timedelta(days=1),
50
+ start_date=datetime(2020, 1, 1),
51
+ catchup=False,
52
+ tags=['quant_factor_mining'],
53
+ ) as dag:
54
+ t1 = BashOperator(
55
+ task_id="data_downloading",
56
+ bash_command=task_command("task_1.py"),
57
+ )
58
+ t2 = BashOperator(
59
+ task_id="data_cleaning",
60
+ depends_on_past=True,
61
+ bash_command=task_command("task_2.py"),
62
+ trigger_rule=TriggerRule.ALL_DONE
63
+ )
64
+ t3 = BashOperator(
65
+ task_id="factor_calculation",
66
+ bash_command=task_command("task_3.py"),
67
+ )
68
+ task_retry = BashOperator(
69
+ task_id="retry_downloading",
70
+ bash_command=task_command("task_retry.py"),
71
+ trigger_rule=TriggerRule.ALL_FAILED
72
+ )
73
+
74
+
75
+ t1 >> t2 >> t3
76
+ t1 >> task_retry