A small C++20 library that computes Wilder's Average True Range (ATR) and an ATR-based stop-loss level.
The true range of a candle is the largest of high − low, |high − previous close| and |low − previous close|. ATR averages true range over a period (commonly 14) to measure how far price typically moves per candle. An ATR stop sits a multiple k of ATR away from price, so it widens in volatile markets and tightens in quiet ones.
atr::calc_atr: Wilder's ATR. Candle 0 only supplies the first previous close, so true ranges start at candle 1. The first ATR value, at indexperiod, is the simple average of the true ranges of candles 1 …period; every later value uses Wilder's smoothing,ATR[i] = (ATR[i-1] × (period − 1) + TR[i]) / period.atr::trailing_sl: long-side stop level,close − k × ATR, withkdefaulting to 3.0.- One header and one source file, built as the CMake library target
atr_sl. - GoogleTest unit tests, a Google Benchmark micro-benchmark, Conan 2 dependency management and a GitHub Actions workflow.
namespace atr {
std::vector<double> calc_atr(std::span<const double> highs,
std::span<const double> lows,
std::span<const double> closes,
int period);
inline double trailing_sl(double close, double atr, double k = 3.0);
}- Inputs hold one element per candle, oldest first.
std::vector<double>and plain arrays convert tostd::spanautomatically. - The result has the same length as the inputs. Elements
0 … period-1are0.0; the first ATR value is at indexperiod. - ATR is in the same price units as the inputs.
- Preconditions: all three inputs have the same length,
period > 0, and there are at leastperiod + 1candles. They are checked withassertinsidesrc/atr_sl.cpp, so the checks run only when the library itself is compiled withoutNDEBUG(for example with-DCMAKE_BUILD_TYPE=Debug). The Release build below compiles them out, and breaking a precondition is then undefined behaviour (out-of-bounds access), not a reported error. trailing_slis stateless and returns the level for a single candle. To trail a long position, keep the highest level returned so far. For a short position the mirror level isclose + k × ATR; trail it by keeping the lowest level so far. The library has no separate helper for it.
#include "atr_sl.hpp"
#include <cstdio>
#include <vector>
int main() {
std::vector<double> highs {100, 102, 101, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115};
std::vector<double> lows { 99, 100, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112};
std::vector<double> closes {100, 101, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114};
const int period = 14; // needs at least period + 1 candles
std::vector<double> atr_series = atr::calc_atr(highs, lows, closes, period);
double stop = atr::trailing_sl(closes.back(), atr_series.back(), 2.5);
std::printf("ATR(%d) = %.4f, long stop = %.4f\n", period, atr_series.back(), stop);
// Prints: ATR(14) = 2.8673, long stop = 106.8316
}To try it, save it as example.cpp in the repository root and run c++ -std=c++20 -Iinclude example.cpp src/atr_sl.cpp -o example && ./example.
Requirements: CMake 3.20 or newer, a C++20 compiler, Conan 2 and Ninja (or drop -G Ninja to use CMake's default generator). These mirror the CI workflow's steps; on its fresh runner CI uses conan profile detect --force. The toolchain path below assumes your default Conan profile has build_type=Release, which conan profile detect sets.
conan profile detect # once per machine, if you have no default profile
conan install . -of build --build=missing
cmake -B build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=build/build/Release/generators/conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failureWith CMake 3.23 or newer you can use the presets that conan install generates instead (they use Conan's default generator, Unix Makefiles, so Ninja is not needed for this path):
cmake --preset conan-release
cmake --build --preset conan-release
ctest --preset conan-releaseRun the benchmark with ./build/bench_atr (or ./build/build/Release/bench_atr after a preset build).
CMakeLists.txt calls find_package(GTest REQUIRED CONFIG) and find_package(benchmark REQUIRED CONFIG) unconditionally, so any build without Conan, even of the library alone, needs both installed as CMake config packages where find_package can find them.
tests/test_atr.cpp contains two GoogleTest cases, registered with CTest as the single test RunAll:
ATR.Wilder14_KnownExample: 15 candles withperiod = 14; checksatr.back(), the first ATR value (index 14), against 2.85714285714 to within 1e-9. That is 40 / 14: the true ranges of candles 1 to 14 sum to 40.TrailingSL.Basic:trailing_sl(100.0, 2.0, 3.0)returns 94.0.
include/atr_sl.hpp public API (namespace atr)
src/atr_sl.cpp calc_atr implementation
tests/test_atr.cpp GoogleTest unit tests
bench/bench_atr.cpp Google Benchmark: calc_atr on 10,000 candles
CMakeLists.txt library, test and benchmark targets
conanfile.py Conan 2 conanfile used by `conan install` (gtest 1.14.0, benchmark 1.8.4)
.github/workflows/ci.yml GitHub Actions workflow
.github/workflows/ci.yml is triggered by pushes to main and by pull requests. On ubuntu-latest it installs Conan 2 and Ninja with pip, detects a Conan profile, installs dependencies, builds in Release mode with Ninja and runs ctest. The benchmark is built but not run in CI.