Summary
src/backtesting/performance/metrics.py accepts a trading_days parameter in its constructor but never uses it. annualized_return is hardcoded to divide by 365.25 regardless of what was configured. The parameter is dead code and the metric is silently wrong for anyone who set trading_days=252 (the standard for equity markets).
Location
src/backtesting/performance/metrics.py:
# Constructor accepts trading_days
def __init__(self, ..., trading_days: int = 252):
self.trading_days = trading_days # stored but never read
# annualized_return uses hardcoded 365.25
annualized_return = (1 + total_return) ** (365.25 / trading_days_held) - 1
# ^^^^^^ should be self.trading_days
Impact
- Annualized return is always calculated on a calendar-day basis, not a trading-day basis
- For a 252 trading-day year, calendar days inflate annualized returns by a factor of ~1.45x vs trading-day convention
- The
trading_days=252 constructor argument a user passes does nothing, so callers think they have control they don't
- Related:
test_sharpe_ratio_calculation asserts annualized_return > 10 (interpreted as >1000%), which passes only because this inflation produces nonsense numbers that satisfy the assertion by accident
Correct fix
annualized_return = (1 + total_return) ** (self.trading_days / trading_days_held) - 1
Summary
src/backtesting/performance/metrics.pyaccepts atrading_daysparameter in its constructor but never uses it.annualized_returnis hardcoded to divide by365.25regardless of what was configured. The parameter is dead code and the metric is silently wrong for anyone who settrading_days=252(the standard for equity markets).Location
src/backtesting/performance/metrics.py:Impact
trading_days=252constructor argument a user passes does nothing, so callers think they have control they don'ttest_sharpe_ratio_calculationassertsannualized_return > 10(interpreted as >1000%), which passes only because this inflation produces nonsense numbers that satisfy the assertion by accidentCorrect fix