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

Tue May 21 01:14:58 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 301 files
5.6%    win 68  vwr 0.700   CDW.csv BUY
0.8%    win 148 vwr 1.000   GLPI.csv    BUY
18.9%   win 6   vwr 0.700   ASND.csv    BUY
17.4%   win 76  vwr 0.700   AZPN.csv    BUY
9.0%    win 134 vwr 0.990   FSLR.csv    BUY
cya
1491.49user 0.40system 12:31.54elapsed 198%CPU (0avgtext+0avgdata 7956maxresident)k
0inputs+0outputs (0major+70047minor)pagefaults 0swaps

sloccount

sloccount bin/ src/*.cxx src/CMakeLists.txt Makefile | 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'."