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% profit
  • annual_return: 0.0638 = 6.38% per year
  • win_rate: 0.6087 = 60.87% of trades were winners
  • max_drawdown: 0.5280 = 52.8% largest decline from peak
  • sharpe: 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

  1. Prepare data in CSV format
  2. Choose or create a strategy (indicators + logic)
  3. Configure portfolio (capital, costs, risk limits)
  4. Run backtest with data streamed through strategy to portfolio
  5. Analyze results (metrics, drawdowns, trade count)
  6. 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

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

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.