Skip to content

jailop/tzutrader

Repository files navigation

tzutrader

An experiment on building a composable C++ trading backtesting library.

At this moment, the implementation covers only very basic features, in order to explore and validate design and architecture choices.

Criticism is very welcome!

Documentation

Example

#include <iostream>
#include <string>
#include <utility>
#include "tzu.h"

using namespace tzu;

int main(int argc, char** argv) {
    bool verbose = (argc > 1 && std::string(argv[1]) == "-v");
    RSIStrat strat;
    BasicPortfolio portfolio(
        100000.0,   // initial capital
        0.001,      // trading fee 0.1%,
        0.10,       // stop-loss 10%
        0.20        // take-profit 20%
    );
    Csv<Ohlcv> csv(std::cin);
    BasicRunner<BasicPortfolio, RSIStrat, Csv<Ohlcv>> runner(
            portfolio,
            strat,
            csv);
    runner.run(verbose);
    return 0;
}

Build the backtesting example:

cd build && cmake .. && cmake --build . && cd ..

The example reads CSV OHLCV data from stdin. That file includes data from 2015 to 2026.

cat tests/data/btcusd.csv | ./build/example01

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

Doing the output prettier with Unix tools:

cat tests/data/btcusd.csv | ./build/example01 | tr ' ' '\n' | column -t -s ':'

Will produce:

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

How does it work?

  • The Csv class reads OHLCV data from a CSV file and streams it as Ohlcv objects.
  • The RSIStrat class implements a simple RSI-based trading strategy. It generates buy/sell signals based on RSI thresholds.
  • The BasicPortfolio class manages the trading portfolio, tracking cash, holdings, and performance metrics.
  • The BasicRunner class orchestrates the backtesting process, feeding data to the strategy and updating the portfolio accordingly.

Here is the RSIStrat's update method:

    Signal update(const Ohlcv& data) {
        double rsi_value = rsi.update(data);
        Signal signal = {data.timestamp, Side::NONE, data.close};
        if (std::isnan(rsi_value))
            return signal;
        if ((rsi_value < oversold) && (last_side != Side::BUY))
            last_side = signal.side = Side::BUY;
        else if ((rsi_value > overbought) && (last_side != Side::SELL))
            last_side = signal.side = Side::SELL;
        return signal;
    }

Initial Features

  • Indicators: SMA, EMA, Moving Variance, RSI, and MACD.
  • Data types: OHLCV, trades, and single value time series.
  • Input data formats: csv
  • Built-in Strategies: Crossover, RSI, and MACD.
  • Basic portfolio management
  • Initial testing suite for the core components

Documentation is available through the user guide and API reference listed above, with tutorials for creating custom indicators and strategies, and detailed explanations of the library's architecture.

Architecture

The library is designed into several core components:

  • Data Streamers: Responsible for reading and streaming input data in various formats (e.g. CSV).
  • Indicators: Implement various indicators (e.g. SMA, EMA, RSI, MACD) that can be used by trading strategies to generate signals. Indicators are implemented using circular buffers to efficiently compute values.
  • Strategies: Implement trading strategies that generate buy/sell signals based on the indicators' values. Strategies can be as simple or complex as needed, and can use multiple indicators to make decisions.
  • Portfolio Management: Manages the trading portfolio, tracking cash and holdings. It handles the execution of trades, applying trading fees, and making decisions about position sizing, stop-loss, and take-profit.
  • Performance Metrics: Computes various performance metrics, such as total return, annualized return, buy-and-hold return, max drawdown, and Sharpe ratio.
  • Runner: Orchestrates the backtesting process, feeding data to the strategy and updating the portfolio accordingly.

The library is intended to let users to easily implement their own custom indicators, strategies, and portfolio management approaches, by providing clear interfaces and a modular design that allows to easily swap out different components.

Design Philosophy

  • Designed to be as simple and lightweight as possible, with a focus on core functionality.
  • Built to be easily composable, with a modular design that allows to easily swap out different components, as well as to implement their own custom indicators, strategies, and portfolio management approaches.
  • The library processes data in a streaming fashion, allowing it to handle large datasets without needing to load everything into memory at once. This also allows for more realistic backtesting, as it simulates the way that real trading systems operate, processing data as it arrives, and protecting against look-ahead bias.

The library is inspired by the Unix philosophy of building small, composable tools that do one thing well, and can be combined together to create more complex systems.

Roadmap

  • Improve the architecture and design of the library, based on feedback and experimentation.
  • Add more examples to demonstrate the library's features and usage.
  • Add support for the most common input data formats, e.g. JSON.
  • Implement a minimal but useful set of built-in trading strategies and indicators.
  • Add support for a more realistic portfolio, risk and order management features, e.g. multiple assets, position sizing, slippage, etc.

Not considered:

  • Include an extensive set of built-in strategies and indicators. The library is designed to be small, and users are encouraged to implement their own custom strategies and indicators using the provided interfaces.
  • Support for data retrieval APIs. The library is designed to be agnostic to data sources, and users can implement their own data retrieval logic using the provided interfaces.

Building Documentation

To build the documentation locally:

make -f Makefile.docs all

This will generate:

  • User guide and tutorials in ./docs/
  • API reference in ./docs/html/

Requirements:

  • mkdocs: pip install mkdocs
  • doxygen: apt install doxygen (or equivalent)

To serve the documentation locally:

make -f Makefile.docs serve

Available targets: all, docs, api, serve, clean, rebuild, help

About

An algorithmic trading framework designed to develop, and test systematic trading strategies. This is a readonly repo.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors