Backtester

See the source on GitLab.

I started with the intention of using the latest tool chain: so Ubuntu 24.04, gcc from source and CMake 3.28 (with built-in modules support). I wanted to make everything as constexpr as possible from the outset and also C++ modules. However, you do spend a lot of time seeing compiler bugs! Normally that would be pretty cool but not when you can’t debug it without a fight and/or workaround.

Features of note

Strategy

constexpr

It was painful to get right, not helped by errors from the Moon and the compiler blowing up. It’s much less of a headache if you break the transform functions out into lambdas. However, once you get there, many classic undefined behaviours are caught at compile time: array out of bounds, invalid iterators, etc. Very cool.

Printing isn’t constexpr, of course, but you can add runtime sections to your compile-time code.

if (not std::is_constant_evaluated())
  std::println("parsed: {} ", line);

I’ve added a module constd.cxx where I’ve put all the constexpr implementation of functions that are missing from the Standard Library. I’ve used the namespace constd and attempted to keep the API as close to the Standard Library as possible; which will make it easier to replace when the Standard Library catches up.

Example errors from the Moon

FAILED: CMakeFiles/backtest.dir/main.cxx.o 
/usr/local/bin/c++   -std=c++23 -O1 -Wall -Wextra -Wpedantic -fmodules-ts -march=native -std=gnu++23 -MD -MT CMakeFiles/backtest.dir/main.cxx.o -MF CMakeFiles/backtest.dir/main.cxx.o.d -fmodules-ts -fmodule-mapper=CMakeFiles/backtest.dir/main.cxx.o.modmap -MD -fdeps-format=p1689r5 -x c++ -o CMakeFiles/backtest.dir/main.cxx.o -c /home/deanturpin/backtest/src/main.cxx
/home/deanturpin/backtest/src/main.cxx: In instantiation of ‘<lambda(auto:55&&)> [with auto:55 = std::ranges::subrange<std::counted_iterator<std::ranges::transform_view<std::ranges::transform_view<std::ranges::filter_view<std::ranges::drop_view<std::ranges::split_view<std::basic_string_view<char>, std::ranges::single_view<char> > >, backtest(std::string_view)::<lambda(auto:56)> >, <lambda(auto:52&&)> >, <lambda(auto:54&&)> >::_Iterator<false> >, std::default_sentinel_t, std::ranges::subrange_kind::sized>&]:
...

Ranges and views

Views lend themselves to many “tradey” applications: anything that involves moving a window around the data fits beautifully. And all the weird edge cases are handled by this data structure. And whilst it’s nice to keep things lazy; but sometimes it’s just easier to create a container from a view, especially if there’s any filtering going on: see std::ranges::to.

Modules

C++ modules are still very new and you can’t used them with the headers – which sadly was the first thing I tried (for quite a while) – but now CMake has direct support and this is much less painful. So you do still get crazy errors and compiler barfs but you can always revert to the “old fashioned” way of doing things (#include).

A very cool thing about modules is not having to manage (synchronise) the header files: you just import the module.

Threads

I’m using std::execution::par to provide parallelism at a file level; but it’s not without grumbles from the compiler.

/usr/local/include/c++/14.0.1/pstl/algorithm_impl.h:3390:5: note: ‘#pragma message:  [Parallel STL message]: "Vectorized algorithm unimplemented, redirected to serial"
 3390 |     _PSTL_PRAGMA_MESSAGE("Vectorized algorithm unimplemented, redirected to serial");
      |     ^~~~~~~~~~~~~~~~~~~~

Results

Mon Mar 4 08:23:11 UTC 2024

cmake -B build -S src -G Ninja
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /builds/deanturpin/backtest/build
cmake --build build --parallel
ninja: no work to do.
time build/backtest
hiya
Processing 469 files
13.3%   win 16  vwr 0.700   PCAR.csv    BUY
8.3%    win 34  vwr 0.700   FIS.csv BUY
15.0%   win 24  vwr 0.700   OPCH.csv    BUY
31.5%   win 66  vwr 1.020   NKTR.csv    BUY
9.4%    win 86  vwr 0.700   EYE.csv BUY
5.5%    win 26  vwr 0.700   JKHY.csv    BUY
6.7%    win 32  vwr 0.700   MAT.csv BUY
6.1%    win 48  vwr 1.005   POOL.csv    BUY
7.7%    win 140 vwr 0.960   FRPT.csv    BUY
7.8%    win 70  vwr 0.995   CSGP.csv    BUY
6.3%    win 14  vwr 0.700   COLM.csv    BUY
cya
2236.40user 0.94system 18:41.71elapsed 199%CPU (0avgtext+0avgdata 7944maxresident)k
0inputs+0outputs (0major+23536minor)pagefaults 0swaps

sloccount

sloccount bin/ src/*.cxx src/CMakeLists.txt Makefile style.css | grep -E "SLOC|Cost"
SLOC    Directory   SLOC-by-Language (Sorted)
Total Physical Source Lines of Code (SLOC)                = 436
 (Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05))
Total Estimated Cost to Develop                           = $ 11,301
SLOCCount, Copyright (C) 2001-2004 David A. Wheeler
SLOCCount is Open Source Software/Free Software, licensed under the GNU GPL.
SLOCCount comes with ABSOLUTELY NO WARRANTY, and you are welcome to
Please credit this data as "generated using David A. Wheeler's 'SLOCCount'."