A lightweight, high-performance backtesting engine written in C++17 that simulates a Simple Moving Average (SMA) crossover strategy on historical OHLCV stock data — from raw CSV ingestion through signal generation, trade execution, and full performance analytics.
- Overview
- Features
- Project Structure
- How It Works
- Getting Started
- Sample Output
- Results & Interpretation
- Key Learnings
- Roadmap
This project simulates a classic Golden Cross / Death Cross trading strategy against historical stock data. It reads OHLCV data from a CSV file, computes two moving averages (a fast and a slow window), detects crossovers between them, executes simulated trades, and reports risk-adjusted performance metrics — all without look-ahead bias.
The goal wasn't to build a strategy that beats the market, but to build a correct, reusable backtesting framework that any strategy could be plugged into.
- 📂 CSV Data Loading — parses OHLCV-formatted historical price data
- 📈 SMA Calculation — configurable short and long moving average windows
- 🔀 Crossover Detection — identifies Golden Cross (buy) and Death Cross (sell) signals
- 💰 Trade Simulation — executes simulated buys/sells with full position sizing
- 📊 Equity Tracking — maintains a running equity curve across the backtest
- 📉 Performance Metrics — total return, max drawdown, win rate, and average profit per trade
StockBacktester/
├── data/
│ └── nvda_us_d.csv # Historical stock data
├── src/
│ ├── sma_backtester.cpp # Main backtester source
│ └── sma_backtester.exe # Compiled executable
├── .gitignore
└── README.md
1. Data Loading
Closing prices are parsed from a CSV file (Date, Open, High, Low, Close, Volume) into a std::vector<double>.
2. SMA Calculation A lambda computes the moving average for any given day and window size:
auto calculateSMA = [&](int currentDay, int window) -> double {
if (currentDay < window - 1) return 0.0;
double sum = 0.0;
for (int i = currentDay - window + 1; i < currentDay + 1; i++) {
sum += prices[i];
}
return sum / window;
};3. Signal Generation Each day, the short-window SMA is compared to the long-window SMA:
- Golden Cross — short SMA crosses above long SMA → BUY
- Death Cross — short SMA crosses below long SMA → SELL
4. Trade Execution On a buy signal, all available cash is converted into shares at the current price. On a sell signal, the full position is liquidated and profit/loss is recorded.
5. Performance Analysis After the simulation completes, the engine calculates:
| Metric | Formula |
|---|---|
| Total Return | (Final Equity − Initial Capital) / Initial Capital × 100% |
| Max Drawdown | Largest peak-to-trough decline in the equity curve |
| Win Rate | (Winning Trades / Total Trades) × 100% |
| Avg Profit/Trade | Total Profit / Number of Trades |
No look-ahead bias: every calculation on day i only uses price data from day i and earlier — the single most important correctness property of a real backtester.
- A C++17-compatible compiler (
g++,clang++, or MSVC)
# Navigate to the source directory
cd src
# Compile (Windows)
g++ -std=c++17 -O2 sma_backtester.cpp -o sma_backtester.exe
.\sma_backtester.exe
# Compile (Linux / macOS)
g++ -std=c++17 -O2 sma_backtester.cpp -o backtester
./backtesterAdjust the strategy parameters directly in sma_backtester.cpp:
int shortWindow = 20; // Try 10, 30, 50
int longWindow = 50; // Try 30, 100, 200
double cash = 10000.0; // Starting capitalTo backtest a different stock, replace data/nvda_us_d.csv with any CSV file in the same OHLCV format:
Date,Open,High,Low,Close,Volume
2025-07-21,172.75,173.38,171.00,171.38,123126136
2025-07-22,171.34,171.39,164.58,167.03,193114327
Successfully read 250 closing prices.
BUY executed at $187.62 on day 53
SELL executed at $181.46 on day 94
BUY executed at $187.05 on day 124
SELL executed at $182.48 on day 154
BUY executed at $177.69 on day 158
SELL executed at $184.76 on day 160
BUY executed at $202.50 on day 190
SELL executed at $199.00 on day 233
=== Performance Summary ===
Final Equity: $9,647.16
Total Return: -3.53%
Max Drawdown: -19.31%
Total Trades: 4
Winning Trades: 1
Losing Trades: 3
Win Rate: 25.00%
Total Profit: -$352.84
Avg Profit/Trade: -$88.21
| Trade | Buy Day | Buy Price | Sell Day | Sell Price | Profit/Loss |
|---|---|---|---|---|---|
| 1 | 53 | $187.62 | 94 | $181.46 | −$326.48 |
| 2 | 124 | $187.05 | 154 | $182.48 | −$242.21 |
| 3 | 158 | $177.69 | 160 | $184.76 | +$374.71 |
| 4 | 190 | $202.50 | 233 | $199.00 | −$158.86 |
The 20/50 SMA crossover strategy lost money on NVDA over this test period:
- High risk — a max drawdown of −19.31% signals significant volatility exposure
- Low consistency — a 25% win rate shows the strategy struggled to catch clean trends
- Lagging indicator problem — SMA crossover strategies react to price after the move has already happened, which hurts performance on fast-moving, high-growth stocks like NVDA
This isn't a failure of the engine — it's exactly the kind of insight a backtester is supposed to surface.
- The framework matters more than the strategy. The engine itself is strategy-agnostic and reusable — the SMA logic is just one plug-in.
- Eliminating look-ahead bias is the #1 priority. It's the most common mistake in beginner backtesters and silently invalidates results if done wrong.
- Risk-adjusted performance beats raw returns. A −3.53% return paired with a −19.31% drawdown is a bad trade-off, and professional evaluation always weighs both.
- Data quality underpins everything. Any properly formatted OHLCV CSV can be dropped in and tested.
- Simple strategies are the right place to start. SMA crossover is easy to reason about, implement, and debug — a solid foundation before tackling RSI, MACD, or Bollinger Bands.
- NVDA is a high-growth, high-volatility stock
- SMA crossover is inherently a lagging indicator
- 20/50-day windows are too slow to react to NVDA's rapid price swings
- The strategy got whipsawed repeatedly in choppy conditions
- Sharpe Ratio calculation
- Export equity curve to CSV for external plotting
- Command-line argument support
- Additional strategies (RSI, MACD, Bollinger Bands)
- Transaction costs and slippage modeling
- Parameter optimization via grid search
- Multi-stock / portfolio-level backtesting
- Live data ingestion from a market data API
- Web dashboard for visualizing results
Built as a hands-on exploration of C++, quantitative finance, and algorithmic trading fundamentals.