algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
session_executor.hpp
1#pragma once
2
3#include "../runner/execution.hpp"
5
6#include <algotradelib/broker>
7
8#include <optional>
9#include <string>
10#include <utility>
11
12namespace algotradelib {
13namespace live {
14
24template <typename Session>
26public:
27
32 static constexpr bool reaches_a_broker = true;
34 using Caps = typename Session::Caps;
36 using BrokerTag = typename Session::BrokerTag;
37
38 static_assert(
39 !Caps::data_only,
40 "SessionExecutor sends orders, and this session's client is a market-data source (DataOnlyTraits) "
41 "that takes none: bind a broker's session here, and give the data source to SessionSource as its "
42 "market-data session"
43 );
44
50 explicit SessionExecutor(Session& session, Venue default_route = Venue::UNSPECIFIED)
51 : session_(&session), default_route_(default_route) {}
52
73 Venue default_route() const noexcept { return default_route_; }
74
80 Venue requested_route(Venue asked) const noexcept {
81 return asked == Venue::UNSPECIFIED ? default_route_ : asked;
82 }
83
94 Venue effective_route(Venue asked) const noexcept {
95 return traits_route<Session>(requested_route(asked), 0);
96 }
97
109 if (order.client_order_id_) {
110 if constexpr (!Caps::has_client_order_id) {
111 last_error_ = BrokerError{};
113 last_error_->reached_broker_ = false;
114 last_error_->message_ =
115 "this broker accepts no idempotency key; the intent asked for one";
116 // Refused here, not by the broker: nothing was sent, so nothing may be counted against the
117 // rate limit. Reporting this as a broker rejection spent budget for a request nobody made.
119 }
120 }
121
122 OrderRequest request;
123 request.instrument_ = order.instrument_;
124 request.side_ = order.side_;
125 request.type_ = order.type_;
126 request.time_in_force_ = order.time_in_force_;
127 // All three, every time. A fact not copied here never reaches the wire, so
128 // the order placed is not the order the caller planned -- and it fails
129 // silently, because a default is a valid value rather than an empty one.
130 request.session_ = order.session_;
131 // The runner has already written the route in; applied again for an executor driven by hand.
132 request.venue_ = requested_route(order.venue_);
133 request.quantity_ = order.quantity_;
134 request.price_ = order.price_;
135 // A broker whose `has_trigger_price` is false is refused it by the session before its adapter runs,
136 // which `outcome_of` reports as a local refusal: nothing was sent.
137 request.trigger_price_ = order.trigger_price_;
138 if constexpr (Caps::has_client_order_id) {
139 request.client_order_id_ = order.client_order_id_;
140 }
141
142 auto result = session_->place(std::move(request));
143 if (result) {
144 last_ref_ = result.value().ref_;
145 last_error_.reset();
147 }
148 last_error_ = result.error();
149 return runner::outcome_of(*last_error_);
150 }
151
161 if (working.ref_.primary_.empty()) {
162 // **A reference naming nothing addresses nothing at a broker.** A row can reach here with an
163 // empty one: a runner reserves an order it has just sent so that later limits count it, and
164 // an executor that could not report what the broker issued leaves the reference blank.
165 // Sending that is a request which can only fail while looking like an attempt, and a
166 // cancellation failure is the one this library must not manufacture noise around. Refused as
167 // an answer rather than as an outage, because nothing is in doubt: the order was never
168 // addressed, so there is nothing to reconcile.
169 //
170 // The check is here and not in the runner because a reference is only meaningful to a
171 // broker. A replay's engine matches a cancellation on instrument and side, and refusing one
172 // there would leave a queued order to fill that the caller asked to withdraw.
173 last_error_ = BrokerError{};
175 last_error_->reached_broker_ = false;
176 last_error_->message_ =
177 "no broker reference for this order; it cannot be withdrawn until a listing supplies one";
179 }
180 auto result = session_->cancel(working.ref_, cancel_all(working));
181 if (result) {
182 last_error_.reset();
184 }
185 last_error_ = result.error();
186 return runner::outcome_of(*last_error_);
187 }
188
199 std::optional<Decimal64> sellable_quantity(const InstrumentId& instrument) {
200 if constexpr (Caps::sellable_source == SellableSource::INQUIRY) {
201 auto result = session_->sellable_quantity(instrument);
202 if (!result) {
203 last_error_ = result.error();
204 return std::nullopt;
205 }
206 last_error_.reset();
207 return result.value();
208 } else {
209 static_cast<void>(instrument);
210 return std::nullopt;
211 }
212 }
213
215 static constexpr bool inquires_sellable = Caps::sellable_source == SellableSource::INQUIRY;
216
222 // Read through the same gate as `BrokerCaps`, because `Caps` is a caller's type parameter and need
223 // not be `BrokerCaps`: a hand-written capability set that makes the inquiry states its basis.
224 return caps_detail::inquiry_basis<Caps>();
225 }
226
231 const std::optional<OrderRef<BrokerTag>>& last_ref() const noexcept { return last_ref_; }
232
237 const std::optional<BrokerError>& last_error() const noexcept { return last_error_; }
238
239private:
240 // Asks the session's traits where `requested` goes, where they say.
241 template <typename S>
242 static constexpr auto traits_route(Venue requested, int) noexcept
243 -> decltype(S::Traits::effective_route(requested)) {
244 return S::Traits::effective_route(requested);
245 }
246
247 // The fallback, for traits that do not say: the requested route is the most that is known.
248 template <typename S>
249 static constexpr Venue traits_route(Venue requested, long) noexcept { return requested; }
250
251 Session* session_;
252 Venue default_route_;
253 std::optional<OrderRef<BrokerTag>> last_ref_;
254 std::optional<BrokerError> last_error_;
255};
256
257} // namespace live
258} // namespace algotradelib
std::optional< Decimal64 > sellable_quantity(const InstrumentId &instrument)
Asks the broker how much of an instrument may be sold, where the broker answers that by a separate in...
const std::optional< OrderRef< BrokerTag > > & last_ref() const noexcept
Returns the reference the broker issued for the last accepted order.
typename Session::BrokerTag BrokerTag
The broker tag, from the session.
Venue requested_route(Venue asked) const noexcept
Returns the venue this executor requests for an order.
runner::SendOutcome place(const strategy::PlannedOrder &order)
Places one order.
static constexpr bool inquires_sellable
true if the sellable quantity is a separate inquiry (one request per instrument sold).
Venue default_route() const noexcept
Returns the venue an order that names none is sent to.
runner::SendOutcome cancel(const WorkingOrder< BrokerTag > &working)
Cancels the whole remaining quantity of a working order.
const std::optional< BrokerError > & last_error() const noexcept
Returns why the last instruction failed.
static constexpr SellableBasis sellable_inquiry_basis()
Returns what the broker's sellable-quantity inquiry establishes, recorded with each figure.
Venue effective_route(Venue asked) const noexcept
Returns the route an order is sent with, for the journal to record before sending (for SOR,...
static constexpr bool reaches_a_broker
true: a runner built on this executor requires holdings to have been read before it places,...
typename Session::Caps Caps
The broker's capabilities, from the session.
SessionExecutor(Session &session, Venue default_route=Venue::UNSPECIFIED)
Constructs an executor for a session.
Connects a runner to a broker session for live trading.
Definition bind.hpp:12
SendOutcome
What became of one instruction (a placement or a cancellation) an executor was asked to send.
Definition execution.hpp:30
@ 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
SendOutcome outcome_of(const BrokerError &error)
Classifies an error from an order request: did the order come to exist?
The library: broker-neutral types (core), and the modules below.
@ INQUIRY
From a per-instrument inquiry, made only where the snapshot has no figure.
Definition types.hpp:1096
@ INVALID_ARGUMENT
The request was invalid, refused by the client before sending or by the broker.
Definition types.hpp:713
AmendInstruction cancel_all(const WorkingOrder< Broker > &order)
Makes an instruction cancelling all that remains of an order.
Definition types.hpp:2362
Venue
A trading venue: an exchange, or the combined view across exchanges.
Definition types.hpp:43
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
ErrorCategory category_
The kind of failure; the field to branch on.
Definition types.hpp:744
Identifies an instrument: its country, venue and symbol.
Definition types.hpp:78
An order to place, in broker-neutral form.
Definition types.hpp:1925
std::optional< std::string > client_order_id_
An idempotency key, if the broker offers one; never generated by the library.
Definition types.hpp:1966
Decimal64 quantity_
The quantity, in the instrument's units (shares).
Definition types.hpp:1950
OrderType type_
The price basis: limit, market, best-limit, and so on.
Definition types.hpp:1931
InstrumentId instrument_
The instrument; its venue_ is identity, and routing is this struct's venue_.
Definition types.hpp:1927
Venue venue_
Where to route the order, if the broker offers a choice.
Definition types.hpp:1948
std::optional< Decimal64 > price_
The price; required when carries_caller_price(type_), refused otherwise.
Definition types.hpp:1952
OrderSide side_
Buy or sell.
Definition types.hpp:1929
OrderSession session_
The trading session.
Definition types.hpp:1935
TimeInForce time_in_force_
How long the order stays valid.
Definition types.hpp:1933
std::optional< Decimal64 > trigger_price_
The trigger price; required for STOP_LIMIT, refused otherwise.
Definition types.hpp:1959
What a sellable quantity covers, and how RiskGuard must treat resting sells against it.
Definition types.hpp:918
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
One order the runner is to place.
TimeInForce time_in_force_
How long the order stays valid.
OrderSession session_
The trading session.
std::optional< std::string > client_order_id_
The idempotency key, if the intent carried one.
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.