algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
execution.hpp
1#pragma once
2
4
5#include <algotradelib/core>
6
7#include <array>
8#include <chrono>
9#include <string>
10#include <type_traits>
11#include <string_view>
12#include <utility>
13#include <vector>
14
15namespace algotradelib {
16namespace runner {
17
65
74
80inline constexpr std::string_view send_outcome_name(SendOutcome outcome) noexcept {
81 switch (outcome) {
83 return "accepted";
85 return "rejected";
87 return "indeterminate";
89 return "unavailable";
91 return "refused_locally";
92 }
93 return "";
94}
95
96static_assert(
97 lists_every_value(all_send_outcomes, [](SendOutcome outcome) { return !send_outcome_name(outcome).empty(); }),
98 "all_send_outcomes must list every SendOutcome"
99);
100
117inline SendOutcome outcome_of(const BrokerError& error) {
118 if (!error.reached_broker_) {
119 // Checked first: a request that never went out settles the question whatever the category.
121 }
122 switch (error.category_) {
133 // The broker was asked and refused (a client's own validation was caught above).
135 }
136 // An unrecognized category is treated as unknown, not refused.
138}
139
150inline constexpr bool error_retries_when_unsent(ErrorCategory category) noexcept {
151 switch (category) {
153 return false;
160 return true;
161 }
162 return false;
163}
164
176inline bool broker_unreachable(const BrokerError& error) noexcept {
177 return !error.reached_broker_ && error_retries_when_unsent(error.category_);
178}
179
190inline bool broker_rate_limited(const BrokerError& error) noexcept {
191 return error.reached_broker_ && error_category_is_rate_limit(error.category_);
192}
193
199inline constexpr bool send_outcome_reached_broker(SendOutcome outcome) noexcept {
200 switch (outcome) {
204 return true;
207 return false;
208 }
209 return true;
210}
211
218inline constexpr bool send_outcome_is_local_failure(SendOutcome outcome) noexcept {
219 switch (outcome) {
221 return true;
226 return false;
227 }
228 return true;
229}
230
231namespace runner_detail {
232
233// Whether `Executor` can put an order in front of a real broker.
234//
235// Declared by the executor, because it is the executor that knows. It decides one thing: whether a
236// runner built on it must have read the account before it places. That requirement has to hold for
237// every live path, and there are more of those than the two binding factories --
238// `doxygen/vignettes/binding-a-broker.md` shows a `Runner<..., SessionExecutor<Session>>` assembled
239// by hand, and a rule installed only by the factories would hold for their callers and nobody else.
240//
241// **The default is `true`, and an executor opts *out*.** That is the opposite of how a detection
242// trait beside it usually reads, and it is deliberate: the two directions fail differently. An unmarked
243// executor assumed harmless is a consumer's own adapter -- one whose `place` forwards to `SessionExecutor` or
244// straight to a broker client -- silently exempted from the rule, and it fails by sending a real order sized
245// against an account nobody read. An unmarked executor assumed live is a test double that refuses until it is
246// given a stamped portfolio or says `allow_absent_portfolio()`, and it fails by being noticed at once. Only
247// one of those two is discoverable, so silence means live.
248//
249// `RecordingExecutor` and `backtest::QueueingExecutor` therefore say `false` explicitly: a dry run
250// and a replay have no holdings inquiry that could have failed, and a replay stamps its own
251// portfolio. `JournalingExecutor` forwards whatever it wraps, because recording an order does not
252// change who receives it.
253template <typename Executor, typename = void>
254struct sends_to_a_broker : std::true_type {};
255
256template <typename Executor>
257struct sends_to_a_broker<Executor, std::void_t<decltype(Executor::reaches_a_broker)>>
258 : std::bool_constant<Executor::reaches_a_broker> {};
259
260} // namespace runner_detail
261
277
283inline constexpr std::string_view withheld_reason_name(WithheldReason reason) noexcept {
284 switch (reason) {
286 return "reconcile_required";
288 return "local_failure";
290 return "awaiting_confirmation";
291 }
292 return "unknown";
293}
294
328
334inline constexpr std::string_view withheld_plan_reason_name(WithheldPlanReason reason) noexcept {
335 switch (reason) {
337 return "portfolio_absent";
339 return "portfolio_stale";
341 return "portfolio_superseded";
343 return "placement_held";
345 return "awaiting_confirmation";
346 }
347 return "unknown";
348}
349
355template <typename BrokerTag>
370
384
391inline constexpr int obligation_weight(ObligationState state) noexcept {
392 switch (state) {
394 return 0;
396 return 1;
398 return 2;
400 return 3;
401 }
402 return 3;
403}
404
411inline constexpr bool obligation_is_unaccounted(ObligationState state) noexcept {
412 switch (state) {
415 return true;
418 return false;
419 }
420 return true;
421}
422
429inline constexpr bool obligation_needs_operator(ObligationState state) noexcept {
430 switch (state) {
432 return true;
436 return false;
437 }
438 return true;
439}
440
446 std::string journal_id_;
448 std::string order_id_;
450 OrderSide side_{OrderSide::BUY};
454 std::chrono::system_clock::time_point sent_at_{};
457};
458
464inline constexpr std::string_view obligation_state_name(ObligationState state) noexcept {
465 switch (state) {
467 return "none";
469 return "named";
471 return "unnamed";
473 return "operator";
474 }
475 return "unknown";
476}
477
486 std::string code_;
488 std::string message_;
492 std::string tag_;
493};
494
504
519public:
525 void mark(const InstrumentId& id, std::string detail) {
526 for (auto& entry : pending_) {
527 if (entry.instrument_ == id) {
528 // Keep the first reason: the earliest unresolved instruction is the one in doubt.
529 return;
530 }
531 }
532 pending_.push_back(Entry{id, std::move(detail)});
533 }
534
540 bool requires_reconcile(const InstrumentId& id) const {
541 for (const auto& entry : pending_) {
542 if (entry.instrument_ == id) {
543 return true;
544 }
545 }
546 return false;
547 }
548
554 std::string_view detail(const InstrumentId& id) const {
555 for (const auto& entry : pending_) {
556 if (entry.instrument_ == id) {
557 return entry.detail_;
558 }
559 }
560 return {};
561 }
562
568 for (auto at = pending_.begin(); at != pending_.end(); ++at) {
569 if (at->instrument_ == id) {
570 pending_.erase(at);
571 return;
572 }
573 }
574 }
575
580 std::vector<InstrumentId> pending() const {
581 std::vector<InstrumentId> ids;
582 ids.reserve(pending_.size());
583 for (const auto& entry : pending_) {
584 ids.push_back(entry.instrument_);
585 }
586 return ids;
587 }
588
593 bool empty() const noexcept { return pending_.empty(); }
594
595private:
596 struct Entry {
597 InstrumentId instrument_;
598 std::string detail_;
599 };
600
601 std::vector<Entry> pending_;
602};
603
611template <typename BrokerTag>
613public:
614
616 static constexpr bool reaches_a_broker = false;
623 placed_.push_back(order);
625 }
626
633 canceled_.push_back(working.ref_);
635 }
636
641 const std::vector<strategy::PlannedOrder>& placed() const noexcept { return placed_; }
642
647 const std::vector<OrderRef<BrokerTag>>& canceled() const noexcept { return canceled_; }
648
649private:
650 std::vector<strategy::PlannedOrder> placed_;
651 std::vector<OrderRef<BrokerTag>> canceled_;
652};
653
654} // namespace runner
655} // namespace algotradelib
An exact signed decimal: an int64 coefficient times 10^-scale.
Definition types.hpp:297
The instruments with an order of unknown outcome, where nothing may be placed until the broker's orde...
std::string_view detail(const InstrumentId &id) const
Returns why an instrument is unresolved.
bool empty() const noexcept
Tests whether nothing is unresolved.
std::vector< InstrumentId > pending() const
Returns every instrument waiting for reconciliation.
bool requires_reconcile(const InstrumentId &id) const
Tests whether an instrument is waiting for reconciliation.
void mark(const InstrumentId &id, std::string detail)
Records an instrument's order of unknown outcome; an existing entry keeps its first detail.
void resolve_after_reading_orders(const InstrumentId &id)
Clears an instrument; call it only after reading the broker's order list for it.
An executor that sends nothing and records what it was given (the dry run's default).
const std::vector< strategy::PlannedOrder > & placed() const noexcept
Returns every placement recorded.
SendOutcome place(const strategy::PlannedOrder &order)
Records a placement.
static constexpr bool reaches_a_broker
false: a dry run reaches no broker, so the runner does not require a read account.
const std::vector< OrderRef< BrokerTag > > & canceled() const noexcept
Returns every cancellation recorded.
SendOutcome cancel(const WorkingOrder< BrokerTag > &working)
Records a cancellation.
Runs a strategy: the runner, the polling loop, the order journal, and stopping.
Definition clock.hpp:7
bool broker_unreachable(const BrokerError &error) noexcept
Tests whether an error means the broker could not be reached (as opposed to the binding refusing the ...
constexpr bool send_outcome_reached_broker(SendOutcome outcome) noexcept
Tests whether an outcome means a request reached the broker.
constexpr bool obligation_is_unaccounted(ObligationState state) noexcept
Tests whether an order in this state counts toward the loop's owed-order time limit (PollingLoop::set...
constexpr std::array< SendOutcome, 5 > all_send_outcomes
Every SendOutcome, in declaration order.
Definition execution.hpp:67
constexpr std::string_view obligation_state_name(ObligationState state) noexcept
Returns a lowercase name for a state, such as "named", for logs.
SendOutcome
What became of one instruction (a placement or a cancellation) an executor was asked to send.
Definition execution.hpp:30
@ UNAVAILABLE
Local machinery failed (such as the order journal not being writable), so nothing was sent.
Definition execution.hpp:55
@ INDETERMINATE
The outcome is unknown (see outcome_of): the request failed in transit or was cancelled,...
Definition execution.hpp:48
@ REJECTED
The broker refused the instruction (for a placement, the order does not exist); nothing needs reconci...
Definition execution.hpp:34
@ ACCEPTED
The broker accepted the instruction (for a placement, the order exists).
Definition execution.hpp:32
@ REFUSED_LOCALLY
The binding refused the instruction before sending it (such as an idempotency key at a broker without...
Definition execution.hpp:63
WithheldPlanReason
Why the runner held back a whole reconciliation plan (cancellations and placements) before sending an...
@ PLACEMENT_HELD
A hold stops placements, so the plan's cancellations are held too (a reprice must not become a bare c...
@ PORTFOLIO_ABSENT
No holdings have been read, so the plan was computed against an account that looks flat (and could ca...
@ AWAITING_CONFIRMATION
A serialized runner may not place on the instrument yet, so the plan's cancellations are held too: an...
@ PORTFOLIO_STALE
The holdings the plan was computed from are older than the configured maximum age.
@ PORTFOLIO_SUPERSEDED
The working-order listing is newer than the holdings the plan was sized against (see strategy::Portfo...
constexpr std::string_view withheld_reason_name(WithheldReason reason) noexcept
Returns a lowercase name for a reason, such as "reconcile_required", for logs.
WithheldReason
Why the runner held back a planned order without sending it.
@ LOCAL_FAILURE
Local machinery cannot send (see SendOutcome::UNAVAILABLE).
@ AWAITING_CONFIRMATION
A serialized runner already has an order on this instrument that holdings have not yet accounted for ...
@ RECONCILE_REQUIRED
An earlier instruction for this instrument has an unknown outcome; nothing is sent until the broker's...
constexpr bool obligation_needs_operator(ObligationState state) noexcept
Tests whether only an operator can settle an obligation in this state, so no read of the broker's lis...
SendOutcome outcome_of(const BrokerError &error)
Classifies an error from an order request: did the order come to exist?
constexpr std::string_view withheld_plan_reason_name(WithheldPlanReason reason) noexcept
Returns a lowercase name for a reason, such as "portfolio_stale", for logs.
constexpr bool error_retries_when_unsent(ErrorCategory category) noexcept
Tests whether a request that was never sent may be tried again on a later pass, by its error's catego...
constexpr bool send_outcome_is_local_failure(SendOutcome outcome) noexcept
Tests whether an outcome means the runner's own machinery failed (not the broker, and not a check tha...
ObligationState
What a serialized runner still owes on an instrument (Runner::obligation_state).
@ NAMED
An order the broker named, waiting for its history and holdings to account for it.
@ OPERATOR
An order only an operator may account for (Runner::release_obligations).
@ NONE
Nothing: an order may be placed.
@ UNNAMED
An order whose existence is unknown (its reply was lost and no sweep named it).
constexpr int obligation_weight(ObligationState state) noexcept
Returns how much an obligation state weighs when one instrument owes several orders: the instrument r...
constexpr std::string_view send_outcome_name(SendOutcome outcome) noexcept
Returns a lowercase name for an outcome, such as "indeterminate", for logs.
Definition execution.hpp:80
bool broker_rate_limited(const BrokerError &error) noexcept
Tests whether an error is the broker's rate-limit refusal (such as KIS EGW00201).
The library: broker-neutral types (core), and the modules below.
constexpr bool lists_every_value(const std::array< Enum, N > &values, IsEnumerator is_enumerator) noexcept
Tests whether a list holds every value of an enum exactly once, in any order.
ErrorCategory
The kind of failure a BrokerError reports.
Definition types.hpp:687
@ RATE_LIMIT
The broker refused the request for its rate limit.
Definition types.hpp:697
@ HTTP
The broker answered with an HTTP error status (http_status_).
Definition types.hpp:693
@ PROTOCOL
The reply could not be read, or the broker declared a failure this client does not classify further: ...
Definition types.hpp:704
@ CANCELLED
The operation was cancelled, such as by closing the client or a local deadline.
Definition types.hpp:689
@ AUTHENTICATION
The credentials or token were refused.
Definition types.hpp:695
@ TRANSPORT
The connection failed (DNS, connect, TLS, write or read).
Definition types.hpp:691
@ INVALID_ARGUMENT
The request was invalid, refused by the client before sending or by the broker.
Definition types.hpp:713
constexpr bool error_category_is_rate_limit(ErrorCategory category) noexcept
Tests whether an error of this category is a broker asking the caller to slow down,...
Definition types.hpp:722
OrderSide
The side of an order: its economic direction, buy or sell.
Definition types.hpp:1683
Reconciliation: turning a strategy's intents and targets into the orders and cancellations to send.
The error a broker or transport operation returns.
Definition types.hpp:742
int http_status_
The HTTP status of the reply: set for an HTTP error and for a reply whose body is not a JSON object....
Definition types.hpp:759
ErrorCategory category_
The kind of failure; the field to branch on.
Definition types.hpp:744
bool reached_broker_
Whether a request was sent before this error.
Definition types.hpp:753
Identifies an instrument: its country, venue and symbol.
Definition types.hpp:78
An order still open to modification or cancellation.
Definition types.hpp:2290
OrderRef< Broker > ref_
The reference to amend or cancel the order with.
Definition types.hpp:2294
The broker's own code and message for a refusal, kept for diagnosis.
std::string tag_
The tag of the instruction.
std::string message_
The broker's message.
std::string code_
The broker's error code, if it gave one.
InstrumentId instrument_
The instrument of the refused instruction.
One order a serialized runner still owes (Runner::obligations).
std::string order_id_
The broker's order id; empty while unnamed.
Decimal64 quantity_
The quantity holdings must still account for.
ObligationState state_
Its state: NAMED, UNNAMED or OPERATOR.
std::chrono::system_clock::time_point sent_at_
When it was sent, on the journal's clock.
InstrumentId instrument_
The instrument.
std::string journal_id_
The id of the order's journal lines.
One order the runner held back without sending, and why.
WithheldReason reason_
Why it was held back.
strategy::PlannedOrder order_
The order.
std::string detail_
A description of why.
One reconciliation plan the runner held back, and what it would have done.
std::vector< OrderRef< BrokerTag > > not_canceled_
The orders the plan would have cancelled.
std::size_t not_placed_
How many orders the plan would have placed.
std::string tag_
The tag of the strategy's instruction.
InstrumentId instrument_
The instrument.
WithheldPlanReason reason_
Why the plan was held back.
std::string detail_
A description of why, naming the hold if there was one.
One order the runner is to place.