WenqiDing-CompFin maintains this educational Python project for auditable, monthly cross-sectional equity research.
This is the flagship research repository in the portfolio. The separate Quant Factor Lab is its interactive demo; use only this repository as the primary application or CV link. It contains the formal methodology, failure register, tests, real-data validation path, and reproducible artifacts.
Synthetic-data boundary: the default dataset is simulated and deliberately links returns to latent value, quality, and momentum structure. The included performance and IC values validate the research pipeline; they are not historical-market evidence, market alpha, or an investable result. Nothing in this repository is investment advice.
| Evidence track | Data | What it establishes | What it does not establish |
|---|---|---|---|
| Stock-level research pipeline | Deterministic synthetic panel | Timing, ranking, portfolio accounting, transaction costs, failure behavior, and reproducibility | Historical alpha or an investable strategy |
| Public factor validation | Official Kenneth French U.S. factor portfolios | Real market factor regimes, long-history summary statistics, dependence-aware inference, and subperiod stability | Performance of this repository's composite stock strategy |
| Characteristic-sorted portfolios | Official value-weighted U.S. momentum deciles | Whether prior-return sorting remains monotonic and statistically stable across fixed reporting periods | A security-level reconstruction, feasible execution, or prospective discovery |
The repository now includes an official current-return snapshot and a reproducible downloader for the Kenneth French Data Library. As reported by the source for May 2026:
| Factor | May 2026 | Last 3 months | Last 12 months |
|---|---|---|---|
| Market excess | 4.90% | 9.42% | 25.63% |
Size (SMB) |
-2.77% | -1.89% | 6.14% |
Value (HML) |
-2.15% | 0.42% | 14.53% |
Profitability (RMW) |
-8.42% | -15.98% | -27.50% |
Investment (CMA) |
-1.39% | -5.28% | -2.92% |
The mixed signs are intentional evidence, not a presentation problem: factor returns are regime-dependent. Run the full official monthly-history validation:
python run_public_factor_validation.py --start-date 1990-01-01The script records source URLs and archive SHA-256 hashes and reports fixed subperiods plus a six-lag Newey-West t-statistic. See the public real-data protocol for methodology and claim boundaries.
The same command also downloads the official value-weighted portfolios formed on NYSE prior (2-12) return decile breakpoints. It reports a P10-minus-P1 research spread and cross-decile monotonicity over fixed retrospective partitions:
| Period | Annualized mean spread | Annualized volatility | Newey-West t-stat | Mean-return rank correlation |
|---|---|---|---|---|
| Development, 1927-1989 | 15.59% | 26.51% | 5.11 | 1.00 |
| Validation, 1990-2014 | 11.60% | 28.40% | 1.89 | 0.93 |
| Holdout, 2015-May 2026 | 8.72% | 30.36% | 1.05 | 0.37 |
The latest period is the important result: the average spread remains positive, but dependence-aware evidence and cross-decile monotonicity weaken materially. Only four of nine adjacent mean-return steps are positive in the holdout. This is reported as instability, not reframed as a successful trading strategy. The partitions are fixed for reporting but are not claimed to be historically untouched by the broader momentum literature.
The repository implements an end-to-end research baseline:
- deterministic generation of 100 synthetic stocks over 120 monthly observations;
- validation of required columns, valid dates, unique
(date, ticker)keys, positive prices, and consecutive monthly observations within each ticker; - 12-to-1 momentum, book-to-market value, ROE quality, and trailing low-volatility signals;
- a conservative three-month demo lag for fundamental variables;
- monthly cross-sectional 1st/99th percentile winsorization and z-score normalization;
- an equal-weight composite requiring all four factor scores;
- factor Rank IC, IC standard deviation, valid-observation positive-IC ratio, ICIR, and five-quantile return diagnostics;
- a top-20% equal-weight long-only portfolio and equal-weight eligible-universe benchmark;
- weight-based one-way turnover and configurable transaction costs;
- 0/5/10/20/50 bps cost-sensitivity analysis;
- portfolio return, volatility, zero-risk-free-rate Sharpe ratio, drawdown, hit-rate, turnover, and cost outputs;
- official five-factor and momentum archive parsing, provenance hashes, fixed-subperiod analysis, and Newey-West mean inference;
- twenty-one automated tests covering the synthetic pipeline, risk controls, and offline public-data parsing and statistics;
- reproducible CSV results and an equity-curve chart written to
results/.
Can a transparent combination of momentum, value, quality, and low-volatility characteristics produce a stable cross-sectional ranking after explicit timing, turnover, benchmark, and transaction-cost controls?
The stock-level pipeline answers this question only inside a controlled synthetic experiment. The public module adds real constructed factor-portfolio evidence, but moving to a stock-selection conclusion still requires historical security data with real publication timestamps, delisting returns, and documented universe membership.
| Factor | Implementation at signal month t |
Direction |
|---|---|---|
| Momentum | close[t-1] / close[t-12] - 1 |
Higher is better |
| Value | book_to_market shifted by 3 monthly rows per ticker |
Higher is better |
| Quality | roe shifted by 3 monthly rows per ticker |
Higher is better |
| Low volatility | Negative trailing 12-month return volatility ending at t-1 |
Higher is better |
The momentum signal skips the most recent month. The three-month fundamental lag is a transparent demo convention, not a substitute for actual filing publication timestamps. A real-data study must use the information that was genuinely available on each decision date.
- Generate the fixed-seed synthetic panel or load an external monthly CSV.
- Validate the data contract and sort each security chronologically.
- Build one-month future returns without crossing ticker boundaries.
- Construct the four lagged signals.
- Winsorize and standardize each signal within each month.
- Average all four z-scores into a composite; incomplete rows remain ineligible.
- Calculate monthly factor Rank IC against next-month returns and split each raw factor into five equal-count rank quantiles.
- Export each quantile's equal-weight next-month return and the top-minus-bottom spread.
- Select the top 20% of composite-score names and assign equal weights.
- Drift prior holdings by their realized returns, then calculate one-way turnover as
0.5 * sum(abs(target_weight - pretrade_weight)); initial investment turnover is 100%. - Deduct
turnover * cost_bps / 10,000from portfolio return. - Compare net portfolio return with the gross equal-weight return of the same eligible universe.
- Export portfolio, factor, quantile, cost-sensitivity, metric, and chart artifacts.
Rank IC is computed as the correlation between cross-sectional factor ranks and next-month return ranks. The positive-IC ratio uses only non-missing monthly IC observations. The reported ICIR is mean Rank IC divided by its sample standard deviation; it is not annualized. Quantile assignment ranks ties deterministically, applies qcut to create five groups, and defines the spread as Q5 minus Q1.
quant-factor-research/
|-- README.md
|-- environment.yml
|-- pyproject.toml
|-- requirements.txt
|-- requirements-dev.txt
|-- run_research.py
|-- run_public_factor_validation.py
|-- data/
| |-- README.md
| `-- tushare_data_loader.py
|-- docs/
| |-- factor_research_report.md
| |-- failure_analysis.md
| |-- public_factor_validation.md
| |-- project_introduction.md
| |-- resume_bullets.md
| `-- logs/
| |-- daily_report_template.md
| `-- weekly_report_template.md
|-- examples/
| `-- run_real_data_backtest.py
|-- results/
| |-- cost_sensitivity.csv
| |-- equity_curve.png
| |-- factor_ic.csv
| |-- factor_ic_summary.csv
| |-- factor_quantile_returns.csv
| |-- factor_quantile_summary.csv
| |-- metrics.csv
| |-- monthly_returns.csv
| `-- public_factors/
| `-- official_current_snapshot.csv
|-- src/
| `-- quant_factor/
| |-- __init__.py
| |-- backtest.py
| |-- data.py
| |-- factors.py
| |-- metrics.py
| |-- public_data.py
| `-- risk.py
`-- tests/
|-- test_research.py
|-- test_public_data.py
`-- test_risk.py
From the repository root:
python -m venv .venvActivate the environment, then install the pinned dependency ranges:
python -m pip install -r requirements.txtRequired packages are NumPy, pandas, Matplotlib, SciPy, and bottleneck. Python 3.10 or later is recommended.
Run the full pipeline from the repository root:
python run_research.pyRun the tests:
python -m pytest -qExpected test summary:
21 passed
Change the transaction-cost assumption or synthetic seed:
python run_research.py --cost-bps 20 --seed 11Write outputs to a separate experiment directory:
python run_research.py --output-dir results/seed_11 --seed 11python run_research.py --input data/public_monthly_panel.csvThe input CSV must contain:
| Column | Type | Requirement |
|---|---|---|
date |
date | Monthly observation date; cannot be missing |
ticker |
string | Stable security identifier; cannot be missing |
close |
float | Positive when present; consistently adjusted |
book_to_market |
float | Fundamental value proxy |
roe |
float | Fundamental quality proxy |
Each (date, ticker) pair must be unique, and each ticker's observations must advance by exactly one calendar month. A missing month raises an error because row-based lags would otherwise change the intended return horizon. The loader checks schema and basic integrity, but the user remains responsible for corporate-action adjustment, publication timing, delisting returns, survivorship, universe construction, and redistribution rights.
Do not commit employer data, paid vendor data, confidential code, client information, API keys, proprietary parameters, or internal screenshots.
python run_research.py writes:
| File | Contents |
|---|---|
results/monthly_returns.csv |
Signal date, gross/net/benchmark/excess returns, turnover, cost, and universe counts |
results/factor_ic.csv |
Monthly Rank IC for each raw factor |
results/factor_ic_summary.csv |
Mean Rank IC, IC standard deviation, positive ratio, observation count, and ICIR |
results/factor_quantile_returns.csv |
Monthly Q1-Q5 next-month returns and top-minus-bottom spread by factor |
results/factor_quantile_summary.csv |
Time-series mean of each factor's quantile returns and spread |
results/metrics.csv |
Net portfolio summary metrics and synthetic-data flag |
results/cost_sensitivity.csv |
Performance at 0/5/10/20/50 bps |
results/equity_curve.png |
Growth of one dollar for the portfolio and benchmark |
results/run_manifest.json |
Machine-readable data provenance, factor specification, portfolio rule, and costs |
results/public_factors/official_current_snapshot.csv |
Official May 2026 real factor-return snapshot with source URL |
results/public_factors/latest/factor_summary.csv |
Full-history real factor metrics with Newey-West inference |
results/public_factors/latest/momentum_decile_summary.csv |
Fixed-period momentum spread and monotonicity evidence |
results/public_factors/latest/source_metadata.json |
Source URLs, archive members, SHA-256 hashes, and claim boundaries |
The committed artifacts currently report the following 10 bps synthetic baseline:
| Metric | Synthetic result |
|---|---|
| Evaluated months | 106 |
| Annualized return | 10.22% |
| Annualized volatility | 4.54% |
| Sharpe ratio, zero risk-free rate | 2.17 |
| Maximum drawdown | -3.67% |
| Positive-month ratio | 74.53% |
| Average one-way turnover | 25.63% |
The synthetic generator explicitly loads returns on latent value and quality characteristics and includes short-term return persistence. These numbers are therefore expected pipeline behavior, not evidence that the factor combination works in a real market. Do not use them as performance claims in a resume, application essay, or interview.
The factor diagnostics reinforce that boundary: in the default simulation, mean Rank IC is approximately 0.0016 for momentum, 0.0248 for value, 0.0427 for quality, and 0.0111 for low volatility. They describe this specific simulation only.
Momentum remains in the registered four-factor baseline despite its near-zero synthetic IC. Removing an inconvenient pre-specified signal after seeing the result would create selection bias. A real-data stage should compare pre-declared 6-1, 9-1, and 12-1 variants on development data, select at most one on validation, and report the frozen variant once on the final test period.
The pipeline reruns the complete portfolio calculation at five cost assumptions. For the committed synthetic seed, annualized return declines from 10.55% at 0 bps to 8.88% at 50 bps. This is a mechanical sensitivity check, not a calibrated estimate of real commissions, spread, impact, taxes, or capacity.
The twenty-one tests verify that:
- synthetic generation is deterministic for a fixed seed;
- future returns do not leak across ticker boundaries;
- momentum uses only the intended prior prices;
- initial one-way turnover equals full investment;
- annualized return matches a hand-checkable constant-return case;
- drawdown includes a negative first month;
- the composite remains unavailable until all four factor values exist;
- a missing future return among eligible names raises an error instead of being silently dropped;
- a missing monthly row within a ticker is rejected before row-based lags are calculated;
- an intermediate month with no future returns raises instead of being skipped;
- turnover compares target weights with return-drifted pretrade weights.
- an undersized eligible universe after the backtest starts raises instead of silently skipping a holding period.
- the public-data parser reads the monthly block, converts percentages to decimals, and stops before annual summaries;
- real factor summaries produce finite dependence-aware statistics over the declared period.
- the named value-weighted monthly portfolio block is selected without mixing equal-weighted or annual sections.
- fixed momentum reporting partitions and cross-decile monotonicity statistics are well formed.
- volatility targeting scales a raw weight toward the target volatility;
- volatility targeting respects the maximum single-name weight cap;
- volatility targeting returns zero for missing or non-positive realized volatility;
- the drawdown stop latches to zero after a drawdown breach;
- a corrupt cached public-data archive is deleted and redownloaded automatically.
The tests reduce implementation risk but do not validate the economic hypothesis or prove the absence of every form of leakage.
- Stock-level strategy results remain synthetic-only; the committed real-data artifact contains official aggregate factor portfolios, not security selections.
- The simulation has no delistings, index changes, trading halts, corporate actions, liquidity limits, or market-capacity constraints.
- A uniform three-month accounting lag is only a demo approximation to real publication timing.
- Signals use information ending at
t-1or earlier, but the monthly model assumes rebalancing at thetclose and does not simulate next-session prices, spread, or slippage. - The equal-weight benchmark is gross of its own rebalancing costs, while portfolio excess return uses the net strategy return.
- No industry, size, beta, or country neutralization is applied.
- There is no train/validation/test split because the current dataset is a pipeline simulation, not a fitted predictive model.
- Costs are linear in turnover and omit spread, nonlinear impact, borrow, tax, and capacity effects.
- Stock-level IC inference does not yet include Newey-West errors or multiple-testing correction; the public factor module adds Newey-West mean inference and fixed regime summaries only for aggregate factor portfolios.
The synthetic engine and public factor validation are complete for their stated scope. The next empirical stock-level stage is to:
- source legally usable historical data with point-in-time fundamentals and delisting returns;
- define an investable universe and explicit next-session execution rule;
- add statistical inference, stability checks, and cost attribution for factor-quantile spreads, plus benchmark-cost diagnostics;
- measure industry, size, beta, and liquidity exposures;
- lock train, validation, and test periods before parameter selection;
- test subperiod, regime, parameter, and capacity sensitivity;
- document failed hypotheses alongside successful ones.
The ordered closure criteria and evidence required for each step are maintained
in docs/failure_analysis.md. Items remain labeled
open until code, data provenance, tests, and regenerated outputs support closure.
This repository is for education and portfolio demonstration only. It is not financial advice, a solicitation, or a claim of live trading performance.
The research engine consumes a standardized monthly panel through the data layer. The default pipeline calls generate_synthetic_panel(). To use real data, replace only the data layer and load a CSV that satisfies the documented schema.
Conceptual minimum fields required by the research layer:
| Field | Type | Requirement |
|---|---|---|
date |
datetime | Monthly observation date |
asset_id |
str | Stable security identifier |
return |
float | One-month return for the asset |
factor_value |
float | Point-in-time factor input |
The current quant_factor.data.validate_panel contract expects the columns date, ticker, close, book_to_market, and roe. A real-data adapter should therefore map vendor fields to this existing schema; the backtest and factor layers do not need to change.
Suggested data sources:
| Source | Market | Access | Notes |
|---|---|---|---|
| Tushare | China A-shares | Token required | Prices, fundamentals, calendars; prefer point-in-time fields where available |
| Yahoo Finance | US equities | Public API | Prices and basic fundamentals; publication timing and delisting handling must be verified |
The data adapter should handle corporate actions, missing months, delisting returns, and publication timing before calling load_panel.
Minimal pseudocode:
def load_real_panel(source):
# 1. fetch raw data from source
# 2. clean and adjust for corporate actions
# 3. map vendor fields to date, ticker, close, book_to_market, roe
# 4. return a DataFrame that passes validate_panelTushare Pro A-share daily data is the first real data source; see the Real-World A-share Data Pipeline section.
The current framework already provides:
- Deterministic chronological train/validation/test splits
- Validation-based alpha selection
- Fixed calendar partitions for public factor data
- Held-out test evaluation opened once
Common risks in multi-factor research:
- Factor decay: predictive power weakens after publication or implementation.
- Overfitting: complex factor combinations fit noise rather than economic structure.
- Data snooping: repeatedly testing many factors on the same history inflates apparent significance.
- Regime dependence: factor spreads and monotonicity can vary across market states.
- Survivorship and universe bias: excluding delisted or newly listed names distorts returns.
Current limitation: the primary stock-level pipeline still uses simulated data, so its out-of-sample statistics validate the research process, not a tradable edge.
Planned improvements:
- Walk-forward and rolling-origin evaluation
- Point-in-time fundamental data
- Delisting return handling
- Multiple-testing correction or deflated Sharpe
- Factor stability and turnover diagnostics across subperiods
The planned protocol is walk-forward evaluation with an expanding training origin, one final held-out period, parameters frozen before the test period, Newey-West t-statistics, and multiple-testing controls.
This section is intentionally a research log, not a results showcase.
| Experiment | Observation | Lesson |
|---|---|---|
| Technical factor screens such as volume/liquidity proxies | Rank IC was not statistically significant in the tested sample | Factors need an economic rationale plus multiple-testing awareness |
| Simple sum composite vs winsorized z-score composite | Direct summation underperformed standardized combination | Standardization and outlier treatment materially affect factor combination |
| Factor behavior in high-volatility regimes | Some factors lost monotonicity or spread stability | Regime dependence should be reported explicitly rather than averaged away |
Detailed sample statistics will be added after a token-based Tushare run. The qualitative lessons above are reported without fabricated performance numbers.
The repository now includes a data-layer adapter for real China A-share data through Tushare Pro. The adapter lives in data/tushare_data_loader.py and does not change the existing factor or backtest logic.
pip install tushareexport TUSHARE_TOKEN=your_token_hereNever hard-code the token in source code.
python examples/run_real_data_backtest.py \
--data real \
--start-date 20230101 \
--end-date 20231231 \
--sample-size 100The example computes factor IC, quantile returns, a long-only backtest, and an equity-curve chart using the existing pipeline functions. Results are written to:
results/real_data_ashare/
The simulated dataset remains fully functional:
python examples/run_real_data_backtest.py --data simulated- Tushare Pro requires an account, token, and sufficient API points.
- Delisted stocks are excluded by
list_status="L", so survivorship bias remains a risk. - Suspended trading days are filtered using zero-volume observations where available.
- The adapter currently covers the A-share market only.
sample_sizelimits the number of tickers to reduce API usage; results depend on the selected sample.- Data latency, corporate-action handling, and point-in-time fundamentals must be validated before any production use.
Real-data experiments may be limited by API quota. Results are for academic research only and are not investment advice. A-share data may contain survivorship bias, and the current adapter does not reconstruct delisting returns or fully point-in-time fundamentals.
