algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
order_updates.hpp
1#pragma once
2
3#include <algorithm>
4#include <cstddef>
5#include <optional>
6#include <unordered_map>
7#include <utility>
8#include <vector>
9
10#include "../core/types.hpp"
11
12namespace algotradelib {
13namespace runner {
14
15namespace order_update_detail {
16
35inline bool same_number(const Decimal64& lhs, const Decimal64& rhs) {
36 return !decimal_less(lhs, rhs) && !decimal_less(rhs, lhs);
37}
38
40inline bool same_number(
41 const std::optional<Decimal64>& lhs, const std::optional<Decimal64>& rhs
42) {
43 if (lhs.has_value() != rhs.has_value()) {
44 return false;
45 }
46 return !lhs.has_value() || same_number(*lhs, *rhs);
47}
48
56inline bool same_observable_state(const OrderSnapshot& lhs, const OrderSnapshot& rhs) {
57 return lhs.status_ == rhs.status_ &&
58 same_number(lhs.execution_.filled_quantity_, rhs.execution_.filled_quantity_) &&
59 same_number(lhs.price_, rhs.price_) &&
60 same_number(lhs.trigger_price_, rhs.trigger_price_) &&
61 same_number(lhs.quantity_, rhs.quantity_) &&
62 same_number(lhs.order_amount_, rhs.order_amount_) &&
63 lhs.canceled_at_ == rhs.canceled_at_;
64}
65
66} // namespace order_update_detail
67
96template <typename Broker>
98public:
110 std::vector<OrderSnapshot> changed(const std::vector<WorkingOrder<Broker>>& rows) {
111 return observe(rows, /*report=*/true);
112 }
113
119 void adopt(const std::vector<WorkingOrder<Broker>>& rows) {
120 observe(rows, /*report=*/false);
121 }
122
127 std::size_t untracked_rows() const noexcept { return untracked_; }
128
134 std::size_t tracked_orders() const noexcept { return tracked_.size(); }
135
141 std::size_t contradicted_rows() const noexcept { return contradicted_; }
142
143private:
144 // One order's last observed state, against which the next listing is compared.
145 struct Tracked {
146 OrderRef<Broker> ref_;
147 OrderSnapshot last_;
148 // Set when this listing stated the same order twice and the two readings disagreed.
149 bool contradicted_{false};
150 };
151
152 // Runs one observation, reporting differences or silently adopting them.
153 //
154 // Two passes, and the split is what keeps the duplicate rule honest. The first reduces the
155 // listing to one entry per reference; only then does the second decide what changed, so a
156 // contradiction found on the listing's last row still suppresses a report the first row would
157 // otherwise have earned.
158 std::vector<OrderSnapshot> observe(
159 const std::vector<WorkingOrder<Broker>>& rows, bool report
160 ) {
161 std::vector<Tracked> next;
162 next.reserve(rows.size());
163 // Where each reference sits, in this listing (`next`) and in the previous one (`tracked_`), so a
164 // long listing is read once and not once per row. Both are looked up and never iterated: the
165 // order of `next`, and so of the updates, is the listing's. Built here from the vectors each
166 // time, so nothing has to be kept in step with `tracked_`; a reference maps to the first entry
167 // that has it, which is the one a scan would find.
168 std::unordered_map<OrderRef<Broker>, std::size_t> built;
169 built.reserve(rows.size());
170 std::unordered_map<OrderRef<Broker>, std::size_t> held_at;
171 held_at.reserve(tracked_.size());
172 for (std::size_t at = 0; at < tracked_.size(); ++at) {
173 held_at.emplace(tracked_[at].ref_, at);
174 }
175
176 for (const auto& row : rows) {
177 // This library's own arithmetic rather than something a broker stated, and the contract is
178 // about what a broker stated. Neither caller passes one today.
179 if (row.reserved_) {
180 continue;
181 }
182 if (row.ref_.primary_.empty() && row.ref_.secondary_.empty()) {
183 ++untracked_;
184 continue;
185 }
186 const auto [at, first] = built.try_emplace(row.ref_, next.size());
187 if (first) {
188 next.push_back(Tracked{row.ref_, row.order_, false});
189 continue;
190 }
191 // One reference, one entry: a duplicated row must not become two events (and two orders).
192 auto& already = next[at->second];
193 if (!order_update_detail::same_observable_state(already.last_, row.order_)) {
194 // Two readings that disagree: report neither, and count it.
195 already.contradicted_ = true;
196 }
197 }
198
199 std::vector<OrderSnapshot> updates;
200 for (const auto& entry : next) {
201 if (entry.contradicted_) {
202 ++contradicted_;
203 continue;
204 }
205 const auto found = held_at.find(entry.ref_);
206 const Tracked* held = found == held_at.end() ? nullptr : &tracked_[found->second];
207 const bool differs =
208 held == nullptr || !order_update_detail::same_observable_state(held->last_, entry.last_);
209 if (report && differs) {
210 updates.push_back(entry.last_);
211 }
212 }
213
214 // A contradicted order keeps no baseline, because there is no reading of it this listing could
215 // settle on. The next listing that states it once reports it afresh.
216 next.erase(
217 std::remove_if(
218 next.begin(),
219 next.end(),
220 [](const Tracked& entry) { return entry.contradicted_; }
221 ),
222 next.end()
223 );
224 tracked_ = std::move(next);
225 return updates;
226 }
227
228 std::vector<Tracked> tracked_;
229 std::size_t untracked_{0};
230 std::size_t contradicted_{0};
231};
232
233} // namespace runner
234} // namespace algotradelib
Turns successive working-order listings into order updates: the rows that changed since the previous ...
std::size_t contradicted_rows() const noexcept
Returns how many times a listing stated one order twice with disagreeing readings (and so it was not ...
std::size_t untracked_rows() const noexcept
Returns how many rows had an empty reference, and so were never reported.
void adopt(const std::vector< WorkingOrder< Broker > > &rows)
Takes a listing as the baseline without reporting anything (such as the startup read,...
std::vector< OrderSnapshot > changed(const std::vector< WorkingOrder< Broker > > &rows)
Compares a listing with the previous one and returns the rows that changed.
std::size_t tracked_orders() const noexcept
Returns how many orders the watch is tracking.
Runs a strategy: the runner, the polling loop, the order journal, and stopping.
Definition clock.hpp:7
The library: broker-neutral types (core), and the modules below.
bool decimal_less(const Decimal64 &lhs, const Decimal64 &rhs)
Compares two decimals numerically, whatever their scales.
Definition types.hpp:665
A broker's reference to one order, used to amend or cancel it.
Definition types.hpp:2111
The state of one order, as a broker listing reported it.
Definition types.hpp:2008
An order still open to modification or cancellation.
Definition types.hpp:2290