33#include "fastmm/core/containers/static_vector.hpp"
88 std::optional<std::string> (*
parse)(
void* obj, std::string_view value);
90 std::string (*
format)(
const void* obj);
94 std::int64_t (*
get_raw)(
const void* obj)
noexcept;
95 void (*
set_raw)(
void* obj, std::int64_t raw)
noexcept;
103 [[nodiscard]]
const ParamDesc*
find(std::string_view name)
const noexcept {
105 if (name == d.name)
return &d;
109 [[nodiscard]] std::size_t
size() const noexcept {
return descs_.size(); }
110 [[nodiscard]]
const ParamDesc*
begin() const noexcept {
return descs_.begin(); }
111 [[nodiscard]]
const ParamDesc*
end() const noexcept {
return descs_.end(); }
114 StaticVector<ParamDesc, kMaxParams> descs_;
117using ParamMap = std::map<std::string, std::string>;
121inline thread_local ParamSchema* t_param_collector =
nullptr;
122inline thread_local bool t_param_overflow =
false;
125template <
class T,
class U>
126constexpr T param_cast(U v)
noexcept {
127 if constexpr (std::is_same_v<T, U>) {
130 return static_cast<T
>(v);
135inline std::string format_scaled(std::int64_t v,
int decimals) {
136 const std::uint64_t mag =
137 v < 0 ? 0U -
static_cast<std::uint64_t
>(v) :
static_cast<std::uint64_t
>(v);
138 std::uint64_t scale = 1;
139 for (
int k = 0; k < decimals; ++k) scale *= 10;
140 std::string out = v < 0 ?
"-" :
"";
141 out += std::to_string(mag / scale);
142 std::uint64_t frac = mag % scale;
144 std::string digits(
static_cast<std::size_t
>(decimals),
'0');
145 for (std::size_t k = digits.size(); k > 0; --k) {
146 digits[k - 1] =
static_cast<char>(
'0' + frac % 10);
149 while (digits.back() ==
'0') digits.pop_back();
161template <
class T,
class Unit>
163 static_assert(
sizeof(T) == 0,
164 "fastmm: FASTMM_PARAM supports bool, integers, floating point, Price, Qty and "
165 "Notional; use FASTMM_PARAM_BPS for a Ratio and FASTMM_PARAM_MS for a Duration");
169struct ParamCodec<bool, PlainUnit> {
171 static constexpr std::string_view kHint =
" (true or false)";
172 [[nodiscard]]
static std::optional<bool> parse(std::string_view s)
noexcept {
173 if (s ==
"true" || s ==
"1" || s ==
"yes" || s ==
"on")
return true;
174 if (s ==
"false" || s ==
"0" || s ==
"no" || s ==
"off")
return false;
177 [[nodiscard]]
static std::string format(
bool v) {
return v ?
"true" :
"false"; }
178 [[nodiscard]]
static double display(
bool v)
noexcept {
return v ? 1.0 : 0.0; }
179 [[nodiscard]]
static std::int64_t to_raw(
bool v)
noexcept {
return v ? 1 : 0; }
180 [[nodiscard]]
static bool from_raw(std::int64_t raw)
noexcept {
return raw != 0; }
184 requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
185struct ParamCodec<T, PlainUnit> {
187 static constexpr std::string_view kHint =
" (a whole number)";
188 [[nodiscard]]
static std::optional<T> parse(std::string_view s)
noexcept {
189 const std::optional<std::int64_t> v = parse_scaled_decimal(s, 0);
190 if (!v || !std::in_range<T>(*v))
return std::nullopt;
191 return param_cast<T>(*v);
193 [[nodiscard]]
static std::string format(T v) {
return std::to_string(v); }
194 [[nodiscard]]
static double display(T v)
noexcept {
return static_cast<double>(v); }
195 [[nodiscard]]
static std::int64_t to_raw(T v)
noexcept {
return param_cast<std::int64_t>(v); }
196 [[nodiscard]]
static T from_raw(std::int64_t raw)
noexcept {
return param_cast<T>(raw); }
200 requires std::is_floating_point_v<T>
201struct ParamCodec<T, PlainUnit> {
203 static constexpr std::string_view kHint =
" (a finite number)";
204 [[nodiscard]]
static std::optional<T> parse(std::string_view s)
noexcept {
205 if (!s.empty() && s.front() ==
'+') s.remove_prefix(1);
207 const auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), d);
208 if (s.empty() || ec != std::errc{} || ptr != s.data() + s.size() || !std::isfinite(d))
210 return param_cast<T>(d);
212 [[nodiscard]]
static std::string format(T v) {
214 const auto r = std::to_chars(buf, buf +
sizeof(buf), v);
217 [[nodiscard]]
static double display(T v)
noexcept {
return param_cast<double>(v); }
218 [[nodiscard]]
static std::int64_t to_raw(T v)
noexcept {
219 return std::bit_cast<std::int64_t>(param_cast<double>(v));
221 [[nodiscard]]
static T from_raw(std::int64_t raw)
noexcept {
222 return param_cast<T>(std::bit_cast<double>(raw));
227 requires(std::is_same_v<Fixed<Tag>,
Price> || std::is_same_v<Fixed<Tag>,
Qty> ||
228 std::is_same_v<Fixed<Tag>,
Notional>)
231 static constexpr std::string_view kHint =
" (at most 8 decimals)";
232 [[nodiscard]]
static std::optional<Fixed<Tag>> parse(std::string_view s)
noexcept {
235 [[nodiscard]]
static std::string format(
Fixed<Tag> v) {
239 [[nodiscard]]
static std::int64_t to_raw(
Fixed<Tag> v)
noexcept {
return v.
raw; }
240 [[nodiscard]]
static Fixed<Tag> from_raw(std::int64_t raw)
noexcept {
246struct ParamCodec<
Ratio, BpsUnit> {
248 static constexpr std::string_view kHint =
" (basis points, at most 4 decimals)";
249 [[nodiscard]]
static std::optional<Ratio> parse(std::string_view s)
noexcept {
250 const std::optional<std::int64_t> raw = parse_scaled_decimal(s,
kBpsDecimals);
251 if (!raw)
return std::nullopt;
255 [[nodiscard]]
static double display(
Ratio v)
noexcept {
return v.
to_bps(); }
256 [[nodiscard]]
static std::int64_t to_raw(
Ratio v)
noexcept {
return v.
raw; }
261struct ParamCodec<
Duration, MsUnit> {
263 static constexpr std::string_view kHint =
" (whole milliseconds)";
264 static constexpr std::int64_t kNsPerMs = 1'000'000;
265 [[nodiscard]]
static std::optional<Duration> parse(std::string_view s)
noexcept {
266 const std::optional<std::int64_t> ms = parse_scaled_decimal(s, 0);
267 constexpr std::int64_t kMaxMs = std::numeric_limits<std::int64_t>::max() / kNsPerMs;
268 if (!ms || *ms > kMaxMs || *ms < -kMaxMs)
return std::nullopt;
271 [[nodiscard]]
static std::string format(
Duration v) {
return format_scaled(v.
ns, 6); }
272 [[nodiscard]]
static double display(
Duration v)
noexcept {
273 return static_cast<double>(v.
ns) /
static_cast<double>(kNsPerMs);
275 [[nodiscard]]
static std::int64_t to_raw(
Duration v)
noexcept {
return v.
ns; }
276 [[nodiscard]]
static Duration from_raw(std::int64_t raw)
noexcept {
return Duration{raw}; }
279template <
class Codec,
class T>
280std::optional<std::string> assign_param(T& field, std::string_view value, T lo, T hi) {
281 const std::optional<T> v = Codec::parse(value);
284 return err.append(
"cannot parse '")
288 .append(Codec::kHint);
290 if (*v < lo || hi < *v) {
291 return err.append(
"value ")
293 .append(
" outside [")
294 .append(Codec::format(lo))
296 .append(Codec::format(hi))
303inline void register_param(
const ParamDesc& d)
noexcept {
304 if (t_param_collector !=
nullptr && !t_param_collector->
add(d)) t_param_overflow =
true;
309 static_assert(std::is_default_constructible_v<Self>);
311 t_param_collector = &s;
312 t_param_overflow =
false;
314 static_cast<void>(tmp);
315 t_param_collector =
nullptr;
317 if (t_param_overflow) {
318 throw std::invalid_argument(
"fastmm: a strategy has at most " + std::to_string(
kMaxParams) +
325inline std::optional<std::string> apply_params(
const ParamSchema& schema,
328 for (
const auto& [key, value] : m) {
331 if (d ==
nullptr)
return err.append(
"unknown parameter '").append(key).append(
"'");
332 if (
auto e = d->parse(obj, value)) {
333 return err.append(
"parameter '").append(key).append(
"': ").append(*e);
339inline std::string describe_params(
const ParamSchema& schema,
const void* obj) {
344 out += d.format(obj);
347 if (!out.empty()) out.pop_back();
353std::optional<std::string> validate_params(
const P& p) {
354 if constexpr (
requires { p.validate(); }) {
356 std::is_same_v<
decltype(p.validate()), std::optional<std::string>>,
357 "fastmm: validate() must be declared std::optional<std::string> validate() const");
361 !
requires(P& q) { q.validate(); },
362 "fastmm: validate() must be const: std::optional<std::string> validate() const");
370#define FASTMM_PARAMS(Self) \
371 using FastmmParamsSelf = Self; \
372 static const ::fastmm::ParamSchema& schema() { \
373 static const ::fastmm::ParamSchema s = ::fastmm::detail::collect_schema<Self>(); \
376 std::optional<std::string> apply(const ::fastmm::ParamMap& m) { \
377 return ::fastmm::detail::apply_params(schema(), this, m); \
379 [[nodiscard]] std::string describe() const { \
380 return ::fastmm::detail::describe_params(schema(), this); \
385#define FASTMM_PARAM_IMPL(unit, type, name, def, lo, hi, doc) \
387 struct FastmmReg_##name { \
388 FastmmReg_##name() noexcept { \
389 using FastmmCodec = ::fastmm::detail::ParamCodec<type, unit>; \
390 ::fastmm::detail::register_param(::fastmm::ParamDesc{ \
392 FastmmCodec::kType, \
393 FastmmCodec::display(::fastmm::detail::param_cast<type>(def)), \
394 FastmmCodec::display(::fastmm::detail::param_cast<type>(lo)), \
395 FastmmCodec::display(::fastmm::detail::param_cast<type>(hi)), \
397 +[](void* fastmm_obj, std::string_view fastmm_value) { \
398 return ::fastmm::detail::assign_param<FastmmCodec, type>( \
399 static_cast<FastmmParamsSelf*>(fastmm_obj)->name, \
401 ::fastmm::detail::param_cast<type>(lo), \
402 ::fastmm::detail::param_cast<type>(hi)); \
404 +[](const void* fastmm_obj) { \
405 return FastmmCodec::format(static_cast<const FastmmParamsSelf*>(fastmm_obj)->name); \
407 +[](const void* fastmm_obj) noexcept -> std::int64_t { \
408 return FastmmCodec::to_raw(static_cast<const FastmmParamsSelf*>(fastmm_obj)->name); \
410 +[](void* fastmm_obj, std::int64_t fastmm_raw) noexcept { \
411 static_cast<FastmmParamsSelf*>(fastmm_obj)->name = FastmmCodec::from_raw(fastmm_raw); \
415 [[no_unique_address]] FastmmReg_##name fastmm_reg_##name{};
419#define FASTMM_PARAM(type, name, def, lo, hi, doc) \
420 FASTMM_PARAM_IMPL(::fastmm::detail::PlainUnit, type, name, def, lo, hi, doc)
423#define FASTMM_PARAM_BPS(name, def, lo, hi, doc) \
424 FASTMM_PARAM_IMPL(::fastmm::detail::BpsUnit, ::fastmm::Ratio, name, def, lo, hi, doc)
427#define FASTMM_PARAM_MS(name, def, lo, hi, doc) \
428 FASTMM_PARAM_IMPL(::fastmm::detail::MsUnit, ::fastmm::Duration, name, def, lo, hi, doc)
Definition params.hpp:102
bool add(const ParamDesc &d) noexcept
Definition params.hpp:104
const ParamDesc * begin() const noexcept
Definition params.hpp:112
const ParamDesc * end() const noexcept
Definition params.hpp:113
std::size_t size() const noexcept
Definition params.hpp:111
const ParamDesc * find(std::string_view name) const noexcept
Definition params.hpp:105
Fixed<Tag>: int64 fixed point with a global 1e-8 scale (ADR-0001).
Definition strategy.hpp:16
constexpr Duration milliseconds(std::int64_t v) noexcept
Definition time.hpp:38
constexpr int kFixedDecimals
Definition fixed_point.hpp:24
constexpr std::string_view to_string(ParamType t) noexcept
Definition params.hpp:63
ParamType
Definition params.hpp:55
@ Bps
Ratio configured in basis points.
@ Decimal
Price, Qty, Notional.
@ Millis
Duration configured in milliseconds.
constexpr std::size_t kMaxParams
Definition params.hpp:100
constexpr int kBpsDecimals
Definition fixed_point.hpp:29
std::map< std::string, std::string > ParamMap
Definition params.hpp:119
std::int64_t ns
Definition time.hpp:23
double to_double() const noexcept
Definition fixed_point.hpp:297
static constexpr std::optional< Fixed > parse(std::string_view s) noexcept
Exact parse of a decimal in plain or exponent notation: "0.1", "2e-05", "1.5E3", "-3".
Definition fixed_point.hpp:220
double to_bps() const noexcept
Ratio only. Diagnostics only.
Definition fixed_point.hpp:242
static constexpr Fixed from_raw(std::int64_t r) noexcept
Definition fixed_point.hpp:155
std::int64_t raw
Definition fixed_point.hpp:152
double max
Definition params.hpp:86
ParamType type
Definition params.hpp:83
std::optional< std::string >(* parse)(void *obj, std::string_view value)
Parses value exactly, checks it against the typed range and assigns the field.
Definition params.hpp:90
double min
Definition params.hpp:85
const char * doc
Definition params.hpp:87
std::string(* format)(const void *obj)
The field's current value in a form parse() accepts ("0.01", "2.5", "true", "2000").
Definition params.hpp:92
std::int64_t(* get_raw)(const void *obj) noexcept
The field as the int64 a ParamUpdateMsg carries, and back (engine thread: no checks).
Definition params.hpp:96
const char * name
Definition params.hpp:82
double def
display only (listings, Python); range checks use the typed bounds
Definition params.hpp:84
void(* set_raw)(void *obj, std::int64_t raw) noexcept
Definition params.hpp:97