algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
conversion.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "./numeric.hpp"
4
5#include <algotradelib/core>
6
7#include <algorithm>
8#include <cmath>
9#include <cstdint>
10#include <limits>
11#include <optional>
12
13namespace algotradelib {
14namespace analytics {
15
23
37inline Scalar to_scalar(const Decimal64& value) {
38 // Every power of ten up to 10^22 is a double, so this product is exact over that range.
39 Scalar divisor{1};
40 for (std::uint8_t step = 0; step < value.scale(); ++step) {
41 divisor *= Scalar(10);
42 }
43 return static_cast<Scalar>(value.coefficient()) / divisor;
44}
45
60inline std::optional<Decimal64> to_decimal(Scalar value, std::uint8_t scale) {
61 if (!std::isfinite(value)) {
62 return std::nullopt;
63 }
64 Scalar scaled = value;
65 for (std::uint8_t step = 0; step < scale; ++step) {
66 scaled *= Scalar(10);
67 }
68 // `std::round` is half away from zero by definition. Adding 0.5 first rounds the sum: 0.49999999999999994
69 // became 1, and an odd integer near 2^52 moved up by one.
70 const Scalar rounded = std::round(scaled);
71 // `INT64_MAX` is not a double: it converts to 2^63, which does not fit, so that bound is exclusive.
72 if (
73 rounded >= static_cast<Scalar>(std::numeric_limits<std::int64_t>::max()) ||
74 rounded < static_cast<Scalar>(std::numeric_limits<std::int64_t>::min())
75 ) {
76 return std::nullopt;
77 }
78 return Decimal64(static_cast<std::int64_t>(rounded), scale);
79}
80
99inline std::optional<Decimal64> round_scalar_to_tick(
100 Scalar value,
101 const PriceRule& rule,
102 OrderSide side,
103 std::uint8_t scale = 0
104) {
105 const auto nearest = to_decimal(value, scale);
106 if (!nearest) {
107 return std::nullopt;
108 }
109 // `to_decimal` rounds to nearest, which can cross the computed value against the order. Step one
110 // unit back when it did by more than floating-point noise (a few units in the last place).
111 Scalar scaled = value;
112 for (std::uint8_t step = 0; step < scale; ++step) {
113 scaled *= Scalar(10);
114 }
115 // Each of the `scale` multiplications here can add half a unit in the last place, and a value that
116 // came through `to_scalar` within its bounds carries one more half unit from its division. The
117 // tolerance is wider than that sum: it also absorbs values computed from such inputs.
118 const Scalar noise = std::max(
119 Scalar(1e-9),
120 std::abs(scaled) * Scalar(2 * scale + 4) * std::numeric_limits<Scalar>::epsilon()
121 );
122 const auto coefficient = nearest->coefficient();
123 const Scalar crossed = static_cast<Scalar>(coefficient) - scaled;
124 auto exact = *nearest;
125 if (side == OrderSide::BUY && crossed > noise) {
126 if (coefficient == std::numeric_limits<std::int64_t>::min()) {
127 return std::nullopt;
128 }
129 exact = Decimal64(coefficient - 1, scale);
130 } else if (side == OrderSide::SELL && -crossed > noise) {
131 if (coefficient == std::numeric_limits<std::int64_t>::max()) {
132 return std::nullopt;
133 }
134 exact = Decimal64(coefficient + 1, scale);
135 }
136 return rule.round_to_tick(exact, side);
137}
138
139} // namespace analytics
140} // namespace algotradelib
An exact signed decimal: an int64 coefficient times 10^-scale.
Definition types.hpp:297
std::uint8_t scale() const noexcept
Returns the scale.
Definition types.hpp:405
std::int64_t coefficient() const noexcept
Returns the coefficient.
Definition types.hpp:399
Decimal64()=default
Constructs zero.
Returns, rolling statistics, indicators and performance metrics over numeric series.
std::optional< Decimal64 > round_scalar_to_tick(Scalar value, const PriceRule &rule, OrderSide side, std::uint8_t scale=0)
Converts a computed value into a price an order can carry.
Scalar to_scalar(const Decimal64 &value)
Converts an exact decimal to the nearest Scalar.
std::optional< Decimal64 > to_decimal(Scalar value, std::uint8_t scale)
Converts a Scalar to an exact decimal with a given number of decimal places.
double Scalar
The floating-point type the analytics and math layers compute in (double).
Definition scalar.hpp:18
The library: broker-neutral types (core), and the modules below.
OrderSide
The side of an order: its economic direction, buy or sell.
Definition types.hpp:1683
Re-exports math::Scalar, math::missing and math::observed into analytics.
The price grid (tick sizes) an instrument's order prices must sit on.
std::optional< Decimal64 > round_to_tick(const Decimal64 &price, OrderSide side) const
Rounds a price to its band's tick in the order's favour: a buy rounds down, a sell up.