Skip to content

Commit 0b73ca2

Browse files
committed
feat(backtest): add weekly/monthly rebalancing (W/M)
- Apply rebalance_freq in run() - Add tests for W/M and invalid freq - Update README with usage Fixes #4
1 parent 24dd645 commit 0b73ca2

22 files changed

+291
-26
lines changed

.coverage

0 Bytes
Binary file not shown.

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ QuantResearchStarter provides a clean, well-documented starting point for quanti
2222

2323
* **Data management** — download market data or generate synthetic price series for experiments.
2424
* **Factor library** — example implementations of momentum, value, size, and volatility factors.
25-
* **Vectorized backtesting engine** — supports transaction costs, slippage, and portfolio constraints.
25+
* **Vectorized backtesting engine** — supports configurable rebalancing frequencies (daily, weekly, monthly), transaction costs, slippage, and portfolio constraints.
2626
* **Risk & performance analytics** — returns, drawdowns, Sharpe, turnover, and other risk metrics.
2727
* **CLI & scripts** — small tools to generate data, compute factors, and run backtests from the terminal.
2828
* **Production-ready utilities** — type hints, tests, continuous integration, and documentation scaffolding.
@@ -80,17 +80,29 @@ streamlit run src/quant_research_starter/dashboard/streamlit_app.py
8080
## Minimal example
8181

8282
```python
83-
from quant_research_starter.backtest import Backtester
83+
from quant_research_starter.backtest import VectorizedBacktest
8484
from quant_research_starter.data import load_prices
8585
from quant_research_starter.factors import Momentum
8686

8787
prices = load_prices("data_sample/sample_prices.csv")
8888
factor = Momentum(window=63)
8989
scores = factor.compute(prices)
9090

91-
bt = Backtester(prices, signals=scores, capital=1_000_000)
92-
results = bt.run()
93-
print(results.performance.summary())
91+
# Daily rebalancing (default)
92+
bt_daily = VectorizedBacktest(prices, signals=scores, capital=1_000_000)
93+
results_daily = bt_daily.run()
94+
95+
# Weekly rebalancing
96+
bt_weekly = VectorizedBacktest(prices, signals=scores, capital=1_000_000, rebalance_freq="W")
97+
results_weekly = bt_weekly.run()
98+
99+
# Monthly rebalancing
100+
bt_monthly = VectorizedBacktest(prices, signals=scores, capital=1_000_000, rebalance_freq="M")
101+
results_monthly = bt_monthly.run()
102+
103+
print(f"Daily: {results_daily['total_return']:.2%}")
104+
print(f"Weekly: {results_weekly['total_return']:.2%}")
105+
print(f"Monthly: {results_monthly['total_return']:.2%}")
94106
```
95107

96108
> See the `examples/` directory for fully working notebooks and scripts.

src/quant_research_starter.egg-info/PKG-INFO

Lines changed: 152 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,43 @@ Dynamic: license-file
4343

4444
# QuantResearchStarter
4545

46-
A modular, open-source quantitative research and backtesting framework designed for clarity and extensibility. Perfect for researchers, students, and developers interested in quantitative finance.
47-
48-
![Python Version](https://img.shields.io/badge/python-3.10%2B-blue)
49-
![License](https://img.shields.io/badge/license-MIT-green)
46+
[![Python Version](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
47+
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
5048
[![CI](https://github.com/username/QuantResearchStarter/actions/workflows/ci.yml/badge.svg)](https://github.com/username/QuantResearchStarter/actions)
5149

50+
A modular, open-source quantitative research and backtesting framework built for clarity, reproducibility, and extensibility. Ideal for researchers, students, and engineers building and testing systematic strategies.
51+
52+
---
53+
54+
## Why this project
55+
56+
QuantResearchStarter provides a clean, well-documented starting point for quantitative research and backtesting. Its priorities are:
57+
58+
* **Readability**: idiomatic Python, type hints, and small modules you can read and change quickly.
59+
* **Testability**: deterministic vectorized backtests with unit tests and CI.
60+
* **Extensibility**: plugin-friendly factor & data adapters so you can try new ideas fast.
61+
62+
---
63+
5264
## Features
5365

54-
- **Data Management**: Download real data or generate synthetic data for testing
55-
- **Factor Library**: Implement momentum, value, size, and volatility factors
56-
- **Backtesting Engine**: Vectorized backtester with transaction costs and constraints
57-
- **Risk Metrics**: Comprehensive performance and risk analytics
58-
- **Modular Design**: Easy to extend with new factors and strategies
59-
- **Production Ready**: Type hints, tests, CI/CD, and documentation
66+
* **Data management** — download market data or generate synthetic price series for experiments.
67+
* **Factor library** — example implementations of momentum, value, size, and volatility factors.
68+
* **Vectorized backtesting engine** — supports transaction costs, slippage, and portfolio constraints.
69+
* **Risk & performance analytics** — returns, drawdowns, Sharpe, turnover, and other risk metrics.
70+
* **CLI & scripts** — small tools to generate data, compute factors, and run backtests from the terminal.
71+
* **Production-ready utilities** — type hints, tests, continuous integration, and documentation scaffolding.
6072

61-
## Quick Start
73+
---
6274

63-
### Installation
75+
## Quick start
76+
77+
### Requirements
78+
79+
* Python 3.10+
80+
* pip
81+
82+
### Install locally
6483

6584
```bash
6685
# Clone the repository
@@ -70,5 +89,125 @@ cd QuantResearchStarter
7089
# Install package in development mode
7190
pip install -e .
7291

73-
# Install development dependencies
92+
# Install development dependencies (tests, linters, docs)
7493
pip install -e ".[dev]"
94+
95+
# Optional UI dependencies
96+
pip install streamlit plotly
97+
```
98+
99+
### Demo (one-line)
100+
101+
```bash
102+
make demo
103+
```
104+
105+
### Step-by-step demo
106+
107+
```bash
108+
# generate synthetic sample price series
109+
qrs generate-data -o data_sample/sample_prices.csv -s 5 -d 365
110+
111+
# compute example factors
112+
qrs compute-factors -d data_sample/sample_prices.csv -f momentum -f value -o output/factors.csv
113+
114+
# run a backtest
115+
qrs backtest -d data_sample/sample_prices.csv -s output/factors.csv -o output/backtest_results.json
116+
117+
# optional: start the Streamlit dashboard
118+
streamlit run src/quant_research_starter/dashboard/streamlit_app.py
119+
```
120+
121+
---
122+
123+
## Minimal example
124+
125+
```python
126+
from quant_research_starter.backtest import Backtester
127+
from quant_research_starter.data import load_prices
128+
from quant_research_starter.factors import Momentum
129+
130+
prices = load_prices("data_sample/sample_prices.csv")
131+
factor = Momentum(window=63)
132+
scores = factor.compute(prices)
133+
134+
bt = Backtester(prices, signals=scores, capital=1_000_000)
135+
results = bt.run()
136+
print(results.performance.summary())
137+
```
138+
139+
> See the `examples/` directory for fully working notebooks and scripts.
140+
141+
---
142+
143+
## CLI reference
144+
145+
Run `qrs --help` or `qrs <command> --help` for full usage. Main commands include:
146+
147+
* `qrs generate-data` — create synthetic price series or download data from adapters
148+
* `qrs compute-factors` — calculate and export factor scores
149+
* `qrs backtest` — run the vectorized backtest and export results
150+
151+
---
152+
153+
## Project structure (overview)
154+
155+
```
156+
QuantResearchStarter/
157+
├─ src/quant_research_starter/
158+
│ ├─ data/ # data loaders & adapters
159+
│ ├─ factors/ # factor implementations
160+
│ ├─ backtest/ # backtester & portfolio logic
161+
│ ├─ analytics/ # performance and risk metrics
162+
│ ├─ cli/ # command line entry points
163+
│ └─ dashboard/ # optional Streamlit dashboard
164+
├─ examples/ # runnable notebooks & example strategies
165+
├─ tests/ # unit + integration tests
166+
└─ docs/ # documentation source
167+
```
168+
169+
---
170+
171+
## Tests & CI
172+
173+
Run unit tests locally with:
174+
175+
```bash
176+
pytest -q
177+
```
178+
179+
CI runs linting (ruff), formatting checks (black), and unit tests across supported Python versions. The workflow is defined in `.github/workflows/ci.yml`.
180+
181+
---
182+
183+
## Contributing
184+
185+
Contributions are welcome. Please follow these steps:
186+
187+
1. Fork the repository
188+
2. Create a descriptive branch (feature or fix)
189+
3. Add tests for new behavior
190+
4. Open a pull request with a clear description and rationale
191+
192+
Before submitting, ensure your tests pass and formatting/linting checks succeed.
193+
194+
---
195+
196+
## AI policy (short & practical)
197+
198+
**Yes — you may use AI tools** (ChatGPT, Copilot, etc.) to help write or review code and documentation. Please follow these guidelines:
199+
200+
* **Disclose** substantial AI assistance in the PR or commit message (e.g., "Generated with ChatGPT; reviewed and adapted by @your-username").
201+
* **Review thoroughly** all AI-generated code for correctness, security, numerical stability, and licensing concerns.
202+
* **Add tests** for AI-generated logic when applicable.
203+
* **Respect licenses**: do not paste or rely on large verbatim copyrighted snippets without appropriate permission or attribution.
204+
205+
This policy encourages fast iteration while maintaining quality and transparency.
206+
207+
---
208+
209+
## License
210+
211+
This project is available under the MIT License — see the `LICENSE` file for details.
212+
213+
---

src/quant_research_starter.egg-info/SOURCES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ src/quant_research_starter.egg-info/requires.txt
1212
src/quant_research_starter.egg-info/top_level.txt
1313
src/quant_research_starter/backtest/__init__.py
1414
src/quant_research_starter/backtest/vectorized.py
15+
src/quant_research_starter/dashboard/streamlit_app.py
1516
src/quant_research_starter/data/__init__.py
1617
src/quant_research_starter/data/downloaders.py
1718
src/quant_research_starter/data/init.py
847 Bytes
Binary file not shown.
Binary file not shown.
Binary file not shown.

src/quant_research_starter/backtest/vectorized.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
"""Vectorized backtesting engine."""
22

33
from typing import Dict, Optional
4-
4+
import numpy as np
55
import pandas as pd
66

7-
87
class VectorizedBacktest:
98
"""
109
Vectorized backtester for quantitative strategies.
1110
1211
Features:
13-
- Daily rebalancing with position sizing
12+
- Configurable rebalancing frequency (daily, weekly, monthly)
1413
- Transaction costs (fixed and proportional)
1514
- Portfolio constraints (leverage, concentration)
1615
- Realistic market dynamics (slippage, execution)
@@ -23,9 +22,21 @@ def __init__(
2322
initial_capital: float = 1_000_000,
2423
transaction_cost: float = 0.001, # 10 bps
2524
max_leverage: float = 1.0,
26-
min_position_size: float = 0.001, # 0.1% of portfolio
25+
min_position_size: float = 0.001,
2726
rebalance_freq: str = "D",
2827
):
28+
"""
29+
Initialize vectorized backtester.
30+
31+
Args:
32+
prices: DataFrame with price data (symbols as columns, dates as index)
33+
signals: DataFrame with trading signals (symbols as columns, dates as index)
34+
initial_capital: Starting capital amount
35+
transaction_cost: Transaction cost as fraction of trade value
36+
max_leverage: Maximum portfolio leverage
37+
min_position_size: Minimum position size as fraction of portfolio
38+
rebalance_freq: Rebalancing frequency ("D"=daily, "W"=weekly, "M"=monthly)
39+
"""
2940
self.prices = prices
3041
self.signals = signals
3142
self.initial_capital = initial_capital
@@ -64,7 +75,7 @@ def run(self, weight_scheme: str = "rank") -> Dict:
6475
"""
6576
print("Running backtest...")
6677

67-
# Vectorized returns-based backtest with daily rebalancing
78+
# Vectorized returns-based backtest with configurable rebalancing frequency
6879
returns_df = self.prices.pct_change().dropna()
6980
aligned_signals = self.signals.loc[returns_df.index]
7081

@@ -75,6 +86,16 @@ def run(self, weight_scheme: str = "rank") -> Dict:
7586
# Ensure full DataFrame with same columns order
7687
weights = weights.reindex(columns=self.prices.columns).fillna(0.0)
7788

89+
# Apply rebalancing frequency - only rebalance on specified dates
90+
rebalance_mask = weights.index.map(self._should_rebalance)
91+
# Broadcast mask to match DataFrame shape
92+
rebalance_mask_df = pd.DataFrame(
93+
np.tile(rebalance_mask.values.reshape(-1, 1), (1, len(weights.columns))),
94+
index=weights.index,
95+
columns=weights.columns
96+
)
97+
weights = weights.where(rebalance_mask_df, weights.shift(1).fillna(0.0))
98+
7899
# Previous day weights for PnL calculation
79100
weights_prev = weights.shift(1).fillna(0.0)
80101

@@ -106,9 +127,16 @@ def run(self, weight_scheme: str = "rank") -> Dict:
106127

107128
def _should_rebalance(self, date: pd.Timestamp) -> bool:
108129
"""Check if we should rebalance on given date."""
109-
# Simple daily rebalancing for now
110-
# Could be extended for weekly/monthly rebalancing
111-
return True
130+
if self.rebalance_freq == "D":
131+
return True
132+
elif self.rebalance_freq == "W":
133+
# Rebalance on Mondays (week start)
134+
return date.weekday() == 0
135+
elif self.rebalance_freq == "M":
136+
# Rebalance on first trading day of month
137+
return date.day == 1
138+
else:
139+
raise ValueError(f"Unsupported rebalance frequency: {self.rebalance_freq}")
112140

113141
def _calculate_weights(self, signals: pd.Series, scheme: str) -> pd.Series:
114142
"""Convert signals to portfolio weights."""
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)