Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .coverage
Binary file not shown.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ QuantResearchStarter aims to provide a clean, well-documented starting point for

* **Data management** — download market data or generate synthetic price series for experiments.
* **Factor library** — example implementations of momentum, value, size, and volatility factors.
* **Vectorized backtesting engine** — supports transaction costs, slippage, and portfolio constraints.
* **Vectorized backtesting engine** — supports transaction costs, slippage, portfolio constraints, and configurable rebalancing frequencies (daily, weekly, monthly).
* **Risk & performance analytics** — returns, drawdowns, Sharpe, turnover, and other risk metrics.
* **CLI & scripts** — small tools to generate data, compute factors, and run backtests from the terminal.
* **Production-ready utilities** — type hints, tests, continuous integration, and documentation scaffolding.
Expand Down Expand Up @@ -93,6 +93,30 @@ results = bt.run()
print(results.performance.summary())
```

### Rebalancing Frequency

The backtester supports different rebalancing frequencies to match your strategy needs:

```python
from quant_research_starter.backtest import VectorizedBacktest

# Daily rebalancing (default)
bt_daily = VectorizedBacktest(prices, signals, rebalance_freq="D")

# Weekly rebalancing (reduces turnover and transaction costs)
bt_weekly = VectorizedBacktest(prices, signals, rebalance_freq="W")

# Monthly rebalancing (lowest turnover)
bt_monthly = VectorizedBacktest(prices, signals, rebalance_freq="M")

results = bt_monthly.run()
```

Supported frequencies:
- `"D"`: Daily rebalancing (default)
- `"W"`: Weekly rebalancing (rebalances when the week changes)
- `"M"`: Monthly rebalancing (rebalances when the month changes)

> The code above is illustrative—see `examples/` for fully working notebooks and scripts.

---
Expand Down
26 changes: 25 additions & 1 deletion src/quant_research_starter.egg-info/PKG-INFO
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ QuantResearchStarter aims to provide a clean, well-documented starting point for

* **Data management** — download market data or generate synthetic price series for experiments.
* **Factor library** — example implementations of momentum, value, size, and volatility factors.
* **Vectorized backtesting engine** — supports transaction costs, slippage, and portfolio constraints.
* **Vectorized backtesting engine** — supports transaction costs, slippage, portfolio constraints, and configurable rebalancing frequencies (daily, weekly, monthly).
* **Risk & performance analytics** — returns, drawdowns, Sharpe, turnover, and other risk metrics.
* **CLI & scripts** — small tools to generate data, compute factors, and run backtests from the terminal.
* **Production-ready utilities** — type hints, tests, continuous integration, and documentation scaffolding.
Expand Down Expand Up @@ -136,6 +136,30 @@ results = bt.run()
print(results.performance.summary())
```

### Rebalancing Frequency

The backtester supports different rebalancing frequencies to match your strategy needs:

```python
from quant_research_starter.backtest import VectorizedBacktest

# Daily rebalancing (default)
bt_daily = VectorizedBacktest(prices, signals, rebalance_freq="D")

# Weekly rebalancing (reduces turnover and transaction costs)
bt_weekly = VectorizedBacktest(prices, signals, rebalance_freq="W")

# Monthly rebalancing (lowest turnover)
bt_monthly = VectorizedBacktest(prices, signals, rebalance_freq="M")

results = bt_monthly.run()
```

Supported frequencies:
- `"D"`: Daily rebalancing (default)
- `"W"`: Weekly rebalancing (rebalances when the week changes)
- `"M"`: Monthly rebalancing (rebalances when the month changes)

> The code above is illustrative—see `examples/` for fully working notebooks and scripts.

---
Expand Down
69 changes: 57 additions & 12 deletions src/quant_research_starter/backtest/vectorized.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,30 @@ def run(self, weight_scheme: str = "rank") -> Dict:
"""
print("Running backtest...")

# Vectorized returns-based backtest with daily rebalancing
# Vectorized returns-based backtest with configurable rebalancing
returns_df = self.prices.pct_change().dropna()
aligned_signals = self.signals.loc[returns_df.index]

# Compute daily target weights from signals
weights = aligned_signals.apply(
lambda row: self._calculate_weights(row, weight_scheme), axis=1
)
# Ensure full DataFrame with same columns order
weights = weights.reindex(columns=self.prices.columns).fillna(0.0)
# Track rebalancing
prev_rebalance_date = None
current_weights = pd.Series(0.0, index=self.prices.columns)

# Compute daily weights from signals (rebalance only on rebalance dates)
weights_list = []
for date in returns_df.index:
if self._should_rebalance(date, prev_rebalance_date):
# Rebalance: compute new target weights
current_weights = self._calculate_weights(
aligned_signals.loc[date], weight_scheme
)
prev_rebalance_date = date

# Append current weights (maintain between rebalances)
weights_list.append(current_weights)

weights = pd.DataFrame(
weights_list, index=returns_df.index, columns=self.prices.columns
).fillna(0.0)

# Previous day weights for PnL calculation
weights_prev = weights.shift(1).fillna(0.0)
Expand Down Expand Up @@ -104,11 +118,42 @@ def run(self, weight_scheme: str = "rank") -> Dict:

return self._generate_results()

def _should_rebalance(self, date: pd.Timestamp) -> bool:
"""Check if we should rebalance on given date."""
# Simple daily rebalancing for now
# Could be extended for weekly/monthly rebalancing
return True
def _should_rebalance(
self, date: pd.Timestamp, prev_rebalance_date: Optional[pd.Timestamp] = None
) -> bool:
"""Check if we should rebalance on given date.

Args:
date: Current date to check
prev_rebalance_date: Last rebalance date (None for first rebalance)

Returns:
True if should rebalance, False otherwise
"""
# Always rebalance on first date
if prev_rebalance_date is None:
return True

if self.rebalance_freq == "D":
# Daily rebalancing
return True
elif self.rebalance_freq == "W":
# Weekly rebalancing - rebalance if week changed
return (
date.isocalendar()[1] != prev_rebalance_date.isocalendar()[1]
or date.year != prev_rebalance_date.year
)
elif self.rebalance_freq == "M":
# Monthly rebalancing - rebalance if month changed
return (
date.month != prev_rebalance_date.month
or date.year != prev_rebalance_date.year
)
else:
raise ValueError(
f"Unsupported rebalance frequency: {self.rebalance_freq}. "
f"Supported frequencies: 'D' (daily), 'W' (weekly), 'M' (monthly)"
)

def _calculate_weights(self, signals: pd.Series, scheme: str) -> pd.Series:
"""Convert signals to portfolio weights."""
Expand Down
38 changes: 19 additions & 19 deletions src/quant_research_starter/factors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
"""Factors module public API."""
from .base import Factor
from .bollinger import BollingerBandsFactor
from .momentum import CrossSectionalMomentum, MomentumFactor
from .size import SizeFactor
from .value import ValueFactor
from .volatility import IdiosyncraticVolatility, VolatilityFactor
__all__ = [
"Factor",
"BollingerBandsFactor",
"CrossSectionalMomentum",
"MomentumFactor",
"SizeFactor",
"ValueFactor",
"IdiosyncraticVolatility",
"VolatilityFactor",
]
"""Factors module public API."""

from .base import Factor
from .bollinger import BollingerBandsFactor
from .momentum import CrossSectionalMomentum, MomentumFactor
from .size import SizeFactor
from .value import ValueFactor
from .volatility import IdiosyncraticVolatility, VolatilityFactor

__all__ = [
"Factor",
"BollingerBandsFactor",
"CrossSectionalMomentum",
"MomentumFactor",
"SizeFactor",
"ValueFactor",
"IdiosyncraticVolatility",
"VolatilityFactor",
]
Loading
Loading