Getting Started
tzutrader is a C++ backtesting library for testing trading strategies. This guide will get you up and running.
Prerequisites
Technical requirements:
- C++11 or later compiler (GCC, Clang, or MSVC)
- CMake 3.10 or later
- Git
Knowledge requirements:
- Comfortable with C++ (classes, templates, STL)
- Basic understanding of trading concepts
- Familiarity with command-line tools
If you're new to C++, this library will be challenging. Python backtesting libraries (like Backtrader or VectorBT) are more beginner-friendly. tzutrader is for developers who want to understand low-level implementation details or need the performance and systems programming skills that C++ provides.
See Design Philosophy for more on who this is for.
Installation
git clone https://codeberg.org/jailop/tzutrader
cd tzutrader
Clone the Repository
mkdir build
cd build
cmake ..
cmake --build .
This compiles the library and examples. The compiled binaries will be in the build/ directory.
Build the Library
Create a Simple Strategy
Create a file my_backtest.cpp:
#include <iostream>
#include "tzu.h"
using namespace tzu;
int main() {
// Create an RSI strategy
RSIStrat strategy(14, 30, 70); // 14-period RSI, buy < 30, sell > 70
// Create portfolio with $100k initial capital
BasicPortfolio portfolio(
100000.0, // initial cash
0.001, // 0.1% transaction costs
0.10, // 10% stop-loss
0.20 // 20% take-profit
);
// Create data streamer from stdin
Csv<Ohlcv> csv(std::cin);
// Create runner
BasicRunner<BasicPortfolio, RSIStrat, Csv<Ohlcv>> runner(
portfolio, strategy, csv
);
// Run backtest
runner.run(false); // false = quiet mode
return 0;
}
Compile Your Backtest
Create a CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(MyBacktest)
set(CMAKE_CXX_STANDARD 17)
# Add tzutrader include directory
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include)
add_executable(my_backtest my_backtest.cpp)
Or compile directly:
cd build
g++ -std=c++11 -I../include -O2 ../my_backtest.cpp -o my_backtest
Prepare Your Data
tzutrader expects CSV format with OHLCV data:
timestamp,open,high,low,close,volume
1419984000,320.0,325.0,315.0,322.0,1000.0
1419984060,322.0,328.0,321.0,326.0,1500.0
- timestamp: Unix timestamp (seconds since epoch)
- open, high, low, close: Prices
- volume: Trading volume
Getting data: You can use yfnim to download historical price data from Yahoo Finance in the correct CSV format:
yf history -s:btc-usd --lookback:10y --format:csv --date_format:unix > btc_data.csv
See the yfnim documentation for more details.
Run the Backtest
cat your_data.csv | ./my_backtest
Or use the test data:
cat ../tests/data/btcusd.csv | ./my_backtest
Understand the Output
init_time:1419984000 curr_time:1767052000 init_cash:100000.0000
curr_cash:197422.2894 num_trades:92 num_closed:46 num_wins:28
num_losses:18 win_rate:0.6087 num_stop_loss:18 num_take_profit:7
quantity:0.0000 holdings:0.0000 valuation:197422.2894
total_costs:14952.7706 profit:97422.2894 total_return:0.9742
annual_return:0.0638 buy_and_hold_return:277.2788
buy_and_hold_annual:0.6677 max_drawdown:0.5280 sharpe:0.3694
Format it nicely:
cat ../tests/data/btcusd.csv | ./my_backtest | tr ' ' '\n' | column -t -s ':'
Key metrics:
total_return: 0.9742 = 97.42% profitannual_return: 0.0638 = 6.38% per yearwin_rate: 0.6087 = 60.87% of trades were winnersmax_drawdown: 0.5280 = 52.8% largest decline from peaksharpe: 0.3694 = risk-adjusted return
Core Data Types
Ohlcv
Open-High-Low-Close-Volume candlestick data:
struct Ohlcv {
int64_t timestamp;
double open;
double high;
double low;
double close;
double volume;
};
Signal
Trading signals generated by strategies:
struct Signal {
int64_t timestamp;
Side side; // BUY, SELL, or NONE
double price;
double volume;
};
Side Enum
enum class Side {
NONE, // No action
BUY, // Buy signal
SELL // Sell signal
};
Basic Workflow
- Prepare data in CSV format
- Choose or create a strategy (indicators + logic)
- Configure portfolio (capital, costs, risk limits)
- Run backtest with data streamed through strategy to portfolio
- Analyze results (metrics, drawdowns, trade count)
- Iterate (adjust parameters, test different periods, try different assets)
Component Overview
Data Streamers
Read and parse input data. Currently supports CSV format.
Csv<Ohlcv> csv(std::cin); // Read OHLCV from stdin
Indicators
Calculate technical values:
SMA sma(20); // 20-period simple moving average
EMA ema(20); // 20-period exponential moving average
RSI rsi(14); // 14-period RSI
MACD macd(12, 26, 9); // Standard MACD
MVar mvar(20, 1); // 20-period variance
Strategies
Generate trading signals:
SMACrossover strat(10, 30, 0.01); // Fast/slow crossover
RSIStrat strat(14, 30, 70); // RSI overbought/oversold
MACDStrat strat(12, 26, 9); // MACD crossover
Or create custom strategies.
Portfolios
Manage positions and capital:
BasicPortfolio portfolio(initial_capital, costs, stop_loss, take_profit);
Runners
Orchestrate the backtesting process:
BasicRunner<Portfolio, Strategy, Streamer> runner(portfolio, strategy, csv);
runner.run(verbose);
Verbose Mode
Enable verbose output to see portfolio state after each data point:
runner.run(true); // Print portfolio after each update
Useful for debugging strategies and understanding trade execution.
Example: Custom Strategy
class MyStrategy: public tzu::Strategy<MyStrategy, tzu::Ohlcv> {
private:
tzu::SMA fast;
tzu::SMA slow;
tzu::Side last_side;
public:
MyStrategy(size_t fast_period = 10, size_t slow_period = 30)
: fast(fast_period), slow(slow_period), last_side(tzu::Side::NONE) {}
tzu::Signal update(const tzu::Ohlcv& data) {
double fast_val = fast.update(data.close);
double slow_val = slow.update(data.close);
tzu::Signal signal = {data.timestamp, tzu::Side::NONE, data.close};
if (std::isnan(fast_val) || std::isnan(slow_val)) {
return signal; // Indicators not ready
}
if (fast_val > slow_val && last_side != tzu::Side::BUY) {
signal.side = tzu::Side::BUY;
last_side = tzu::Side::BUY;
} else if (fast_val < slow_val && last_side != tzu::Side::SELL) {
signal.side = tzu::Side::SELL;
last_side = tzu::Side::SELL;
}
return signal;
}
};
Next Steps
- Read Intro to Trading to understand backtesting fundamentals
- Learn about Indicators and how to create custom ones
- Explore Strategies for signal generation patterns
- Understand Portfolios for position management
- Review Architecture to understand the design
- Check Utilities for development helper tools
Common Issues
"No indicators ready" / lots of NaN values: Indicators need data to warm up. SMA(20) needs 20 data points before returning valid values.
No trades executed: Check if your strategy is generating signals (use verbose mode). Verify your data format is correct.
Unrealistic results: Check for lookahead bias in your strategy. Make sure you're not using future data to make current decisions.
Compile errors: Ensure you're using C++11 or later and including the tzutrader headers correctly.
Getting Help
- Check the FAQ for common questions
- Read the API documentation
- Review example code in the
examples/directory - File issues on Codeberg
What tzutrader Is and Isn't
tzutrader is:
- A library for testing trading ideas
- Focused on composability and simplicity
- An educational tool for understanding backtesting
- Experimental and evolving
tzutrader is not:
- A production trading system
- A guaranteed path to profits
- A complete trading platform with data feeds and execution
- Finished software (it's experimental)
Treat it as a learning tool and a way to test ideas systematically before risking real money.