Summary
CryptoBacktestEngine hardcodes periods_per_day=24 (assuming hourly bars), but the system's own download_crypto_data() function downloads daily data (timeframe="1d"). This internal inconsistency causes annualized returns to be inflated by orders of magnitude when using the standard data pipeline.
This is not a user-error scenario — it's a mismatch between two modules in the same system:
auto_download.py:153 — download_crypto_data(timeframe="1d") — downloads daily candles
crypto_backtest.py:45 — CryptoBacktestEngine(periods_per_day=24) — assumes hourly candles
auto_evolve.py:141 — WalkForwardConfig(bars_per_year=8760) — assumes hourly candles
Steps to Reproduce
pip install stratevo ccxt # v6.0.5
stratevo evolve --market crypto --generations 30 --population 20 --seed 42
# (auto-downloads daily data via download_crypto_data, then backtests assuming hourly)
If ccxt can connect to an exchange, download_crypto_data fetches daily candles (timeframe="1d") and feeds them to CryptoBacktestEngine which processes them as if they were hourly — producing walk-forward window returns like:
Window 0: +462.0% (66 trades) ✅
Window 1: +1666.1% (63 trades) ✅ ⚠️
Window 3: +149193.8% (67 trades) ✅ ⚠️
Root Cause
The annualization formula in crypto_backtest.py:594-597:
periods_per_year = 365 * self.periods_per_day # 8760 (but data is daily, should be 365)
years = periods_used / periods_per_year # 194 daily bars → 0.022 years (should be 0.53)
annual_return = ((1 + total_return) ** (1/years) - 1) * 100 # exponential explosion
194 daily bars are treated as 194 hourly bars (~8 days). A 50% return over 194 days becomes: (1.5)^(1/0.022) - 1 = 8,939,994,666%.
Same mismatch affects hold_periods (inflated 12×), Sharpe ratio (~6× inflated), and walk-forward window CAGR chaining.
Controlled Experiment (seed=42, same daily data, same config)
| Metric |
Crypto BUG (ppd=24) |
Crypto FIXED (ppd=1) |
US Stocks baseline |
| Annual Return |
374.3% |
37.3% |
39.2% |
| Sharpe |
2.45 |
0.59 |
1.74 |
| Max Drawdown |
17.7% |
45.1% |
7.5% |
| Win Rate |
10.8% |
42.5% |
56.7% |
| OOS windows |
+462% ~ +149,194% |
-11% ~ +89% |
+21% ~ +53% |
| Held-out |
+149.8% PASSED |
-15.7% FAILED |
+28.1% PASSED |
"FIXED" = set periods_per_day=1 to match the daily data that download_crypto_data actually provides.
Suggested Fix
Either:
A. Make the download match the engine — change download_crypto_data default to timeframe="1h"
B. Make the engine match the download — auto-detect data frequency from date intervals after loading:
# Infer from date gaps: median interval >= 12h → daily
dates = data[first_symbol]["date"][:50]
intervals = [(dates[i+1] - dates[i]) for i in range(len(dates)-1)]
median_hours = sorted(intervals)[len(intervals)//2].total_seconds() / 3600
periods_per_day = 1 if median_hours >= 12 else 24
Then propagate to CryptoBacktestEngine.periods_per_day, WalkForwardConfig.bars_per_year, and CAGR chaining in auto_evolve.py:1313.
Option B is more robust since users may provide data at any frequency via --data-dir.
Environment
- stratevo 6.0.5 (PyPI)
- Python 3.12.13, macOS Darwin 25.5.0
Summary
CryptoBacktestEnginehardcodesperiods_per_day=24(assuming hourly bars), but the system's owndownload_crypto_data()function downloads daily data (timeframe="1d"). This internal inconsistency causes annualized returns to be inflated by orders of magnitude when using the standard data pipeline.This is not a user-error scenario — it's a mismatch between two modules in the same system:
auto_download.py:153—download_crypto_data(timeframe="1d")— downloads daily candlescrypto_backtest.py:45—CryptoBacktestEngine(periods_per_day=24)— assumes hourly candlesauto_evolve.py:141—WalkForwardConfig(bars_per_year=8760)— assumes hourly candlesSteps to Reproduce
If ccxt can connect to an exchange,
download_crypto_datafetches daily candles (timeframe="1d") and feeds them toCryptoBacktestEnginewhich processes them as if they were hourly — producing walk-forward window returns like:Root Cause
The annualization formula in
crypto_backtest.py:594-597:194 daily bars are treated as 194 hourly bars (~8 days). A 50% return over 194 days becomes:
(1.5)^(1/0.022) - 1 = 8,939,994,666%.Same mismatch affects
hold_periods(inflated 12×), Sharpe ratio (~6× inflated), and walk-forward window CAGR chaining.Controlled Experiment (seed=42, same daily data, same config)
"FIXED" = set
periods_per_day=1to match the daily data thatdownload_crypto_dataactually provides.Suggested Fix
Either:
A. Make the download match the engine — change
download_crypto_datadefault totimeframe="1h"B. Make the engine match the download — auto-detect data frequency from date intervals after loading:
Then propagate to
CryptoBacktestEngine.periods_per_day,WalkForwardConfig.bars_per_year, and CAGR chaining inauto_evolve.py:1313.Option B is more robust since users may provide data at any frequency via
--data-dir.Environment