Last active
July 4, 2022 22:38
-
-
Save ecatmur/0b5cd64c3e3bd68c02eac1d264731900 to your computer and use it in GitHub Desktop.
Advanced metaprogramming for low-latency trading, Maven-style
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <boost/describe.hpp> | |
#include <boost/mp11.hpp> | |
#include <concepts> | |
#include <cstdint> | |
#include <optional> | |
#include <span> | |
#include <stdexcept> | |
#include <string> | |
#include <string_view> | |
#include <type_traits> | |
#include <ranges> | |
#include <variant> | |
BOOST_DEFINE_ENUM(Category, | |
Equity, | |
Debt, | |
Future, | |
Option, | |
Strategy, | |
) | |
template<auto C> struct Constant { | |
using type = std::remove_const_t<decltype(C)>; | |
static constexpr type value = C; | |
}; | |
namespace concepts { | |
template<class T> concept BooleanTestable = requires(T t) { t ? void() : void(); }; | |
template<class T, class U> concept ExplicitlyConvertibleTo = std::constructible_from<U, T>; | |
template<class T, template<class> class B> concept Satisfies = B<T>::value; | |
template<class T, class U> concept Optional = BooleanTestable<T> and requires(T opt) { | |
{ *opt } -> ExplicitlyConvertibleTo<U>; }; | |
template<class P, class T, Category C> concept Properties = | |
(C != Category::Equity or requires(P const props) { | |
props; }) and | |
(C != Category::Debt or requires(P const props) { | |
props; }) and | |
(C != Category::Future or requires(P const props) { | |
props; }) and | |
(C != Category::Option or requires(P const props) { | |
{ props.getContractSize() } -> Satisfies<std::is_arithmetic>; }) and | |
(C != Category::Strategy or requires(P const props) { | |
{ props.getLegs() } -> std::ranges::forward_range; }) and | |
true; | |
template<class T, Category C> concept PropertiesCheck = | |
not requires(T const inst) { inst.template getProperties<C>(); } or | |
requires(T const inst) { {inst.template getProperties<C>()} -> Properties<T, C>; }; | |
template<class T> concept Instrument = requires(T const inst) { | |
{ inst.getSymbol() } -> Optional<std::string>; | |
{ inst.getCategory() } -> std::same_as<Category>; | |
[]<Category C> -> decltype(inst.template getProperties<C>()) {}; } and | |
PropertiesCheck<T, Category::Equity> and | |
PropertiesCheck<T, Category::Debt> and | |
PropertiesCheck<T, Category::Future> and | |
PropertiesCheck<T, Category::Option> and | |
PropertiesCheck<T, Category::Strategy> and | |
true; | |
} | |
struct Empty { | |
[[noreturn]] explicit Empty(std::string_view reason) { | |
#if NDEBUG | |
__builtin_unreachable(); | |
#else | |
throw std::logic_error(std::string(reason)); | |
#endif | |
} | |
template<class T> operator T() const { __builtin_unreachable(); } | |
}; | |
template<class T, template<class> class TQ, template<class> class UQ> | |
struct std::basic_common_reference<T, Empty, TQ, UQ> : std::basic_common_reference<T, T, TQ, UQ> {}; | |
template<class U, template<class> class TQ, template<class> class UQ> | |
struct std::basic_common_reference<Empty, U, TQ, UQ> : std::basic_common_reference<U, U, TQ, UQ> {}; | |
template<template<class> class TQ, template<class> class UQ> | |
struct std::basic_common_reference<Empty, Empty, TQ, UQ> {}; | |
auto visitProperties(concepts::Instrument auto const& instr, auto f) { | |
using Categories = boost::describe::describe_enumerators<Category>; | |
auto ff = [&](auto C) { | |
static constexpr Category Cat = C.value; | |
if constexpr (requires { instr.template getProperties<Cat>(); }) | |
return f(Constant<Cat>(), instr.template getProperties<C.value>()); | |
else | |
return Empty("Unsupported Category"); | |
}; | |
using R = typename decltype([&]<template<class...> class L, class... D>(L<D...>) { | |
return std::common_reference<decltype(ff(Constant<D::value>()))...>(); | |
}(Categories()))::type; | |
return boost::mp11::mp_with_index<boost::mp11::mp_size<Categories>>( | |
static_cast<unsigned>(instr.getCategory()), | |
[&]<std::size_t I>(std::integral_constant<std::size_t, I>) -> R { | |
return ff(Constant<static_cast<Category>(I)>()); | |
}); | |
} | |
namespace ace { | |
struct Option { | |
static constexpr auto Category = ::Category::Option; | |
std::uint32_t size; | |
auto getContractSize() const { return size; } | |
}; | |
struct Strategy { | |
static constexpr auto Category = ::Category::Strategy; | |
struct Leg {}; | |
std::span<Leg> getLegs() const; | |
}; | |
struct Instrument { | |
std::string symbol; | |
std::variant<Option, Strategy> props; | |
auto getSymbol() const { return std::optional(std::string_view(symbol)); } | |
auto getCategory() const { | |
return std::visit([]<class P>(P const&) { return P::Category; }, props); | |
} | |
template<Category C> requires (C == Category::Option) auto const& getProperties() const { | |
return *std::get_if<Option>(&props); } | |
template<Category C> requires (C == Category::Strategy) auto const& getProperties() const { | |
return *std::get_if<Strategy>(&props); } | |
}; | |
} | |
static_assert(concepts::Instrument<ace::Instrument>); | |
template<concepts::Instrument T> | |
auto getContractSize(T const& instr) { | |
using R = std::optional<decltype(visitProperties(instr, [](auto C, auto const& props) { | |
if constexpr (requires { props.getContractSize(); }) | |
return props.getContractSize(); | |
else | |
return Empty(""); | |
}))>; | |
return visitProperties(instr, [&](auto C, auto const& props) -> R { | |
if constexpr (requires { props.getContractSize(); }) | |
return props.getContractSize(); | |
else | |
return std::nullopt; | |
}); | |
} | |
auto test(ace::Instrument const& instr) { | |
return getContractSize(instr).value_or(0); | |
} | |
int main() { | |
ace::Instrument instr; | |
instr.props = ace::Option{.size = 100}; | |
return getContractSize(instr).value_or(0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment