4#include "./execution.hpp"
8#include <algotradelib/core>
57 "all_journal_actions must list every JournalAction"
157 return "indeterminate";
170 "all_journal_outcomes must list every JournalOutcome"
390 std::optional<Venue>
venue_{Venue::UNSPECIFIED};
519 std::optional<std::chrono::system_clock::time_point>
closed_at_;
538 std::vector<OpenInstruction> open;
539 for (
const auto& entry : replay.
entries_) {
542 instruction.
entry_ = entry;
544 instruction.
detail_ = entry.detail_;
545 open.push_back(std::move(instruction));
550 for (
auto at = open.begin(); at != open.end(); ++at) {
551 if (at->entry_.id_ != entry.id_) {
556 at->answered_ =
true;
557 at->closed_at_ = entry.recorded_at_;
558 at->detail_ = entry.detail_;
560 at->entry_.order_id_ = entry.order_id_.empty() ? at->entry_.order_id_ : entry.order_id_;
568 instruction.
entry_ = entry;
572 instruction.
detail_ = entry.detail_;
573 open.push_back(std::move(instruction));
593 std::vector<OpenInstruction> found;
594 for (
const auto& entry : replay.
entries_) {
597 std::find(ids.begin(), ids.end(), entry.id_) == ids.end()) {
600 auto known = std::find_if(found.begin(), found.end(), [&](
const OpenInstruction& instruction) {
601 return instruction.entry_.id_ == entry.id_;
603 if (known == found.end()) {
605 instruction.
entry_ = entry;
607 instruction.
detail_ = entry.detail_;
608 found.push_back(std::move(instruction));
609 known = std::prev(found.end());
612 known->answered_ =
true;
613 known->closed_at_ = entry.recorded_at_;
614 known->detail_ = entry.detail_;
640 entries_.push_back(entry);
641 if (entries_.back().scope_.empty()) {
642 entries_.back().scope_ = scope_;
661 const std::vector<JournalEntry>&
records() const noexcept {
return entries_; }
664 void clear() noexcept { entries_.clear(); }
670 const std::string&
scope() const noexcept {
return scope_; }
673 std::vector<JournalEntry> entries_;
694template <
typename Inner,
typename Journal,
typename Clock = SteadyClock>
707 : inner_(std::move(
inner)), journal_(&
journal), clock_(std::move(clock)) {
708 prefix_ = default_prefix();
733 template <
typename E = Inner>
735 ->
decltype(std::declval<const E&>().effective_route(order.venue_)) {
736 return inner_.effective_route(order.venue_);
772 entry.
id_ = next_id();
788 if (!journal_->append(entry)) {
789 return refuse_unjournaled();
792 const auto outcome = inner_.place(order);
796 entry.
order_id_ = accepted_order_id(outcome);
797 entry.
detail_ = failure_detail(outcome);
799 if (!journal_->append(entry)) {
802 note_failure(
"the order journal could not record an instruction's outcome");
812 const std::optional<JournalEntry>&
last_entry() const noexcept {
return last_entry_; }
825 if (journal_->append(entry)) {
828 note_failure(
"the order journal could not record that a placement was accounted for");
838 template <
typename BrokerTag>
841 entry.
id_ = next_id();
853 if (!journal_->append(entry)) {
854 return refuse_unjournaled();
857 const auto outcome = inner_.cancel(working);
861 entry.
detail_ = failure_detail(outcome);
862 if (!journal_->append(entry)) {
863 note_failure(
"the order journal could not record an instruction's outcome");
872 const Inner&
inner() const noexcept {
return inner_; }
877 Inner&
inner() noexcept {
return inner_; }
883 const Journal&
journal() const noexcept {
return *journal_; }
910 template <
typename Executor = Inner>
912 ->
decltype(std::declval<const Executor&>().requested_route(asked)) {
913 return inner_.requested_route(asked);
921 template <
typename Executor = Inner>
923 return inner_.last_ref();
931 template <
typename Executor = Inner>
933 return inner_.last_error();
940 note_failure(
"the order journal could not be written, so nothing was sent");
949 void note_failure(std::string detail) {
951 if (journal_error_.empty()) {
952 journal_error_ = std::move(detail);
957 std::string next_id() {
return prefix_ +
'-' + std::to_string(++sequence_); }
962 std::string default_prefix()
const {
963 const auto since = std::chrono::system_clock::now().time_since_epoch();
964 const auto micros = std::chrono::duration_cast<std::chrono::microseconds>(since).count();
965 return std::to_string(
static_cast<std::int64_t
>(micros));
972 std::string accepted_order_id(
SendOutcome outcome)
const {
975 return accepted_order_id_impl(0);
985 template <
typename Executor = Inner>
986 auto accepted_order_id_impl(
int)
const
987 ->
decltype(std::declval<const Executor&>().last_ref(), std::string()) {
988 const auto& ref = inner_.last_ref();
989 return ref ? ref->primary_ : std::string();
992 std::string accepted_order_id_impl(...)
const {
return {}; }
995 std::string failure_detail(
SendOutcome outcome)
const {
1003 return failure_detail_impl(0);
1005 return failure_detail_impl(0);
1008 template <
typename Executor = Inner>
1009 auto failure_detail_impl(
int)
const
1010 ->
decltype(std::declval<const Executor&>().last_error(), std::string()) {
1011 const auto& error = inner_.last_error();
1012 return error ? error->message_ : std::string();
1015 std::string failure_detail_impl(...)
const {
return {}; }
1020 std::optional<JournalEntry> last_entry_;
1021 std::string prefix_;
1022 std::string journal_error_;
1023 std::size_t refusals_{0};
1024 std::size_t failures_{0};
1025 std::uint64_t sequence_{0};
An exact signed decimal: an int64 coefficient times 10^-scale.
auto last_ref() const noexcept -> decltype(std::declval< const Executor & >().last_ref())
Returns the reference of the last order the wrapped executor placed.
SendOutcome cancel(const WorkingOrder< BrokerTag > &working)
Records and sends one cancellation.
std::size_t unjournaled_refusals() const noexcept
Returns how many instructions were not sent because their PENDING line could not be written.
Inner & inner() noexcept
Returns the wrapped executor.
Venue route_of(const strategy::PlannedOrder &order, long) const
Returns the venue an order asked for, when the wrapped executor cannot resolve routes (the fallback o...
const std::optional< JournalEntry > & last_entry() const noexcept
Returns the answering line last written (or attempted) for a placement the wrapped executor was asked...
std::size_t journal_failures() const noexcept
Returns how many journal writes have failed; Runner compares it with the count it last acknowledged t...
const std::string & last_journal_error() const noexcept
Returns the first journal failure's description.
Venue effective_route_of(const strategy::PlannedOrder &order) const
Returns the venue to record for an order: the binding's route, or the venue asked for.
auto route_of(const strategy::PlannedOrder &order, int) const -> decltype(std::declval< const E & >().effective_route(order.venue_))
Returns the venue the wrapped executor says an order will reach.
const Journal & journal() const noexcept
Returns the journal.
void set_id_prefix(std::string prefix)
Sets the prefix of line ids.
SendOutcome place(const strategy::PlannedOrder &order)
Records and places one order.
static constexpr bool reaches_a_broker
Whether the wrapped executor reaches a broker.
SendOutcome place(const strategy::PlannedOrder &order, std::optional< Decimal64 > baseline)
Records and places one order, stating the holding it was decided against (a serialized Runner calls t...
auto requested_route(Venue asked) const noexcept -> decltype(std::declval< const Executor & >().requested_route(asked))
Returns the wrapped executor's route for a requested venue.
auto last_error() const noexcept -> decltype(std::declval< const Executor & >().last_error())
Returns why the last instruction failed, from the wrapped executor.
const Inner & inner() const noexcept
Returns the wrapped executor.
bool record(JournalEntry &entry)
Appends a line the caller built, such as a serialized Runner's CONFIRMED line.
JournalingExecutor(Inner inner, Journal &journal, Clock clock={})
Wraps an executor.
void clear() noexcept
Forgets every line.
bool append(const JournalEntry &entry)
Appends a line.
MemoryJournal()=default
Constructs a journal with an empty scope.
const std::vector< JournalEntry > & records() const noexcept
Returns the lines recorded.
JournalReplay replay() const
Returns everything recorded.
const std::string & scope() const noexcept
Returns the journal's broker and account.
MemoryJournal(std::string scope)
Constructs a journal.
Runs a strategy: the runner, the polling loop, the order journal, and stopping.
constexpr bool journal_outcome_answers_send(JournalOutcome outcome) noexcept
Tests whether a line with this outcome answers the send itself (the broker's reply,...
std::optional< JournalOutcome > journal_outcome_from(std::string_view text) noexcept
Parses an outcome's name.
constexpr bool journal_outcome_is_closing_account(JournalOutcome outcome) noexcept
Tests whether a line with this outcome is a serialized runner's closing account of a placement (it co...
constexpr std::string_view journal_outcome_name(JournalOutcome outcome) noexcept
Returns an outcome's name as written to the journal ("pending", ...).
JournalReadStatus
Whether a journal file could be read.
@ MISSING
No such file: a first start, nothing owed.
@ READ
The file was read in full.
@ UNREADABLE
The file exists and could not be read; its contents are unknown.
constexpr JournalOutcome journal_outcome_of(SendOutcome outcome) noexcept
Maps a send outcome to the journal outcome that records it.
constexpr std::array< JournalOutcome, 7 > all_journal_outcomes
Every JournalOutcome, in declaration order.
constexpr bool journal_action_matches_by_order_id(JournalAction action) noexcept
Tests whether recovery matches an instruction of this action by the broker's order id it names,...
constexpr bool journal_outcome_refuses(JournalOutcome outcome) noexcept
Tests whether a line with this outcome says the instruction was refused, so it does not exist.
constexpr std::string_view journal_action_name(JournalAction action) noexcept
Returns an action's name as written to the journal ("place", "cancel").
SendOutcome
What became of one instruction (a placement or a cancellation) an executor was asked to send.
@ UNAVAILABLE
Local machinery failed (such as the order journal not being writable), so nothing was sent.
@ INDETERMINATE
The outcome is unknown (see outcome_of): the request failed in transit or was cancelled,...
@ REJECTED
The broker refused the instruction (for a placement, the order does not exist); nothing needs reconci...
@ ACCEPTED
The broker accepted the instruction (for a placement, the order exists).
@ REFUSED_LOCALLY
The binding refused the instruction before sending it (such as an idempotency key at a broker without...
constexpr std::string_view journal_read_status_name(JournalReadStatus status) noexcept
Returns a status's lowercase name, such as "unreadable".
constexpr bool journal_outcome_leaves_open(JournalOutcome outcome) noexcept
Tests whether a line with this outcome leaves its instruction's fate unknown, so recovery must still ...
JournalOutcome
What a journal line says about an instruction.
@ CONFIRMED
A serialized runner accounted for the placement: holdings showed what it filled, its history showed i...
@ INDETERMINATE
The outcome is unknown (SendOutcome::INDETERMINATE: a failure in transit, an unreadable reply,...
@ RESOLVED
A recovery sweep read the broker's order list and closed the question.
@ REJECTED
The instruction was refused, by the broker or before it was sent, so it does not exist.
@ ACCEPTED
The broker accepted the instruction; it exists.
@ HELD
Evidence contradicted the placement (holdings moved past what it can explain), so only an operator ma...
@ PENDING
Written before the send; whether the instruction exists is not yet known.
constexpr std::array< JournalAction, 2 > all_journal_actions
Every JournalAction, in declaration order.
constexpr bool journal_outcome_requires_operator(JournalOutcome outcome) noexcept
Tests whether a line with this outcome is written with requires_operator_ by design,...
std::vector< OpenInstruction > placements_of(const JournalReplay &replay, const std::vector< std::string > &ids)
Returns the placements with the given journal ids, each with the span in which it may have reached th...
std::vector< OpenInstruction > unresolved_instructions(const JournalReplay &replay)
Lists the instructions whose outcome is unknown, in the order they were opened.
JournalAction
Which kind of instruction a journal line records.
@ CANCEL
A cancellation of a resting order.
std::optional< JournalAction > journal_action_from(std::string_view text) noexcept
Parses an action's name.
constexpr bool journal_outcome_names_order(JournalOutcome outcome) noexcept
Tests whether a line with this outcome may carry the broker's id for the order.
constexpr bool journal_outcome_is_terminal(JournalOutcome outcome) noexcept
Tests whether an outcome answers a PENDING line.
The library: broker-neutral types (core), and the modules below.
constexpr std::optional< Enum > value_named(const std::array< Enum, N > &values, Name name, std::string_view text) noexcept
Returns the value of a list whose name is text, so a parser reads exactly what its writer writes.
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.
OrderType
How an order's price is decided (its price basis).
@ LIMIT
지정가: fills at the stated price or better.
@ UNREADABLE
No readable date rules it out, but an unreadable date might.
OrderSession
The trading session an order is submitted into.
@ REGULAR
The continuous regular session.
Venue
A trading venue: an exchange, or the combined view across exchanges.
TimeInForce
How long an order stays valid and what happens to an unfilled remainder.
@ DAY
Rests until the session ends; an unfilled remainder is cancelled then.
OrderSide
The side of an order: its economic direction, buy or sell.
Reconciliation: turning a strategy's intents and targets into the orders and cancellations to send.
Identifies an instrument: its country, venue and symbol.
OrderSide side_
Buy or sell.
OrderType type_
The price basis; UNKNOWN if the broker's code was not recognized.
std::optional< Decimal64 > trigger_price_
The trigger price, whenever the broker reports a positive one.
Decimal64 quantity_
The quantity ordered; the remainder is this minus execution_.filled_quantity_.
InstrumentId instrument_
The instrument (identity venue; the routing venue is venue_).
std::optional< Decimal64 > price_
The limit price, if the order has one.
An order still open to modification or cancellation.
OrderRef< Broker > ref_
The reference to amend or cancel the order with.
OrderSnapshot order_
The order's state.
One journal line: an instruction and what was known about it when the line was written.
std::string id_
Links an answering line to its PENDING line.
std::chrono::system_clock::time_point recorded_at_
When the line was written (for PENDING, before the request went out).
OrderType type_
The order type; recovery matches on type, time in force and session together.
Decimal64 quantity_
The quantity, which recovery matches against.
std::optional< Decimal64 > price_
The price, if the order has one.
JournalOutcome outcome_
What the line says about the instruction.
std::string detail_
Why an answering line says what it says, for the operator.
std::optional< Venue > venue_
The venue the order actually goes to, as the binding resolves it.
InstrumentId instrument_
The instrument (identity; the routing venue is venue_).
std::optional< Decimal64 > trigger_price_
The trigger price of a stop-limit order; empty otherwise (and on lines that predate it).
std::optional< Decimal64 > baseline_
On a serialized runner's placement, the instrument's net holding the order was decided against; on a ...
bool requires_operator_
Whether only an operator may resolve this instruction.
std::string scope_
The broker and account the instruction belongs to, such as "kis:live:12345678-01".
JournalAction action_
Whether the instruction is a placement or a cancellation.
OrderSession session_
The session.
TimeInForce time_in_force_
The time in force.
std::string order_id_
The broker's order id, if known: from the start on a CANCEL line, and only on an answering line for a...
The contents of a journal, including what could not be read.
bool readable() const noexcept
Tests whether the file could be read.
std::size_t unreadable_lines_
How many lines did not parse (on an append-only file, writes that did not finish).
bool intact() const noexcept
Tests whether everything was read.
bool has_header_
Whether a header was found; a non-empty file without one is not written to.
std::string scope_
The broker and account the file's header declares, if it has one.
std::vector< JournalEntry > entries_
Every line that parsed, in file order.
JournalReadStatus status_
Whether the file was read; READ for an in-memory journal.
std::string detail_
What was wrong, for the operator.
bool truncated_tail_
Whether the last line was incomplete, as after a kill during a write.
One instruction of unknown outcome, and the span in which it may have reached the broker.
JournalEntry entry_
The instruction, as its first line recorded it (its order_id_ may come from a later line).
std::string detail_
What the journal says went wrong.
bool answered_
Whether an INDETERMINATE line answered it.
std::optional< std::chrono::system_clock::time_point > closed_at_
When the send was answered: by an INDETERMINATE line in unresolved_instructions, by any answer in rec...
std::chrono::system_clock::time_point opened_at_
When the send began.
One order the runner is to place.
TimeInForce time_in_force_
How long the order stays valid.
OrderSession session_
The trading session.
Venue venue_
Where to route the order; see OrderRequest::venue_.
std::optional< Decimal64 > trigger_price_
The trigger price of a STOP_LIMIT order, exactly as stated; empty otherwise.
OrderType type_
The order type.
InstrumentId instrument_
The instrument to trade.
std::optional< Decimal64 > price_
The limit price, after clamping and rounding; empty for a market order.
Decimal64 quantity_
The quantity, after rounding onto the lot.