7#include "../runner/order_updates.hpp"
8#include "../runner/runner.hpp"
10#include <algotradelib/core>
38template <
typename BrokerTag>
56 std::optional<std::chrono::system_clock::time_point>
decided_at_;
86template <
typename BrokerTag>
113 last_ref_ = entry.
ref_;
114 queued_.push_back(std::move(entry));
133 switch (order.type_) {
136 return order.trigger_price_.has_value() && order.price_.has_value() &&
142 case OrderType::UNKNOWN:
165 case TimeInForce::UNKNOWN:
181 for (
auto at = queued_.begin(); at != queued_.end(); ++at) {
182 if (at->ref_ == working.
ref_) {
194 std::optional<OrderRef<BrokerTag>>
last_ref()
const {
return last_ref_; }
200 const std::vector<QueuedOrder<BrokerTag>>&
queued() const noexcept {
return queued_; }
208 for (
const auto& entry : queued_) {
209 if (entry.ref_ == ref) {
222 for (
auto& entry : queued_) {
223 if (entry.ref_ == ref) {
224 entry.session_ = session;
236 for (
auto at = queued_.begin(); at != queued_.end(); ++at) {
237 if (at->ref_ == ref) {
251 for (
auto& entry : queued_) {
252 if (entry.ref_ == ref) {
253 entry.released_ =
true;
269 std::uint64_t
bar() const noexcept {
return bar_; }
276 void deciding_at(std::chrono::system_clock::time_point at,
bool exact =
true) {
278 decision_is_exact_ = exact;
282 std::vector<QueuedOrder<BrokerTag>> queued_;
283 std::optional<OrderRef<BrokerTag>> last_ref_;
284 std::uint64_t issued_{0};
285 std::uint64_t bar_{0};
286 std::optional<std::chrono::system_clock::time_point> deciding_at_;
287 bool decision_is_exact_{
true};
293 std::chrono::system_clock::time_point
at_{};
436template <
typename Strategy,
typename CapsType = strategy::NoCaps>
455 NextBarFillModel
fills = NextBarFillModel{}
457 : config_(std::move(config)),
458 fills_(std::move(
fills)),
459 ledger_(config_.cash_, config_.currency_),
460 clock_(config_.session_),
461 runner_(std::move(
strategy), config_.limits_, config_.series_capacity_) {
463 runner_.context().set_instrument(reference);
474 const Strategy&
strategy() const noexcept {
return runner_.strategy(); }
523 if (result_.halted_) {
531 check_slice_order(event);
532 if (!pending_.empty() && pending_.period_end() < event.
period_end_) {
534 if (result_.halted_) {
548 runner_.executor().open_bar();
560 runner_.executor().deciding_at(event.
period_end_, period_end_is_exact(event));
562 settle_queued_against(event);
569 if (result_.halted_) {
583 error.
message_ =
"a position in " +
event.instrument_.symbol_ +
" could not be valued at the "
584 "bar's close without leaving an exact decimal, so the replay stopped rather than let a "
585 "strategy decide on a position one bar behind its price";
586 result_.halted_ = std::move(error);
590 record_equity(event);
600 if (clock_.advance(event, trading_day)) {
601 runner_.context().set_phase(clock_.phase());
614 drain_reports(runner_.feed(clock_.phase()));
616 runner_.context().set_phase(clock_.phase());
621 runner_.record(event);
625 drain_reports(runner_.feed(event));
629 publish_order_updates();
653 if (result_.halted_) {
660 const auto at =
event.quote_.received_at_;
661 if (!pending_.empty() && at < pending_.period_end()) {
662 throw std::invalid_argument(
663 "a quote earlier than bars already fed: with on_bars, feed quotes and bars in time order"
666 if (pending_.empty() && delivered_through_ && at < *delivered_through_) {
667 throw std::invalid_argument(
668 "a quote earlier than a period already delivered: with on_bars, feed quotes and bars in time order"
671 if (!pending_.empty()) {
673 if (result_.halted_) {
691 runner_.context().set_phase(clock_.phase());
701 drain_reports(runner_.feed(clock_.phase()));
708 drain_reports(runner_.feed(event));
714 publish_order_updates();
726 if (result_.halted_) {
738 if (!pending_.empty()) {
740 if (result_.halted_) {
746 drain_reports(runner_.stop());
749 publish_order_updates();
750 result_.unfilled_ = runner_.executor().queued().size();
759 if (delivered_through_ && !(*delivered_through_ < event.
period_end_)) {
760 throw std::invalid_argument(
761 "a bar for a period already delivered to on_bars: feed every bar of a period before anything "
762 "later (a bar of the next period, a quote at or after it, or finish)"
765 if (pending_.empty()) {
769 throw std::invalid_argument(
770 "a bar earlier than bars already fed: with on_bars, feed bars in period-end order across "
774 if (event.
period_end_ == pending_.period_end() && pending_.holds(event)) {
775 throw std::invalid_argument(
776 "a second bar of one instrument and interval for the same period end: on_bars receives one bar "
788 void deliver_pending() {
789 const auto end = pending_.period_end();
790 auto slices = pending_.take();
791 delivered_through_ = end;
792 for (
const auto& slice : slices) {
793 if (result_.halted_) {
797 for (
const auto& bar : slice.bars_) {
798 exact = exact || period_end_is_exact(bar);
800 runner_.executor().deciding_at(end, exact);
802 drain_reports(runner_.feed(slice));
803 publish_order_updates();
816 std::vector<OrderRef<Tag>> remaining;
817 remaining.reserve(runner_.executor().queued().size());
818 for (
const auto& entry : runner_.executor().queued()) {
819 remaining.push_back(entry.ref_);
821 for (
const auto& ref : remaining) {
822 runner_.executor().withdraw(ref);
824 result_.aborted_ = remaining.size();
840 static bool period_end_is_exact(
const strategy::BarEvent& event)
noexcept {
869 std::optional<std::chrono::system_clock::time_point> period_start_of(
870 const strategy::BarEvent& event
873 return event.bar_.timestamp_;
876 return event.period_end_ - *duration;
878 if (event.
interval_ == BarInterval::ONE_DAY) {
879 return clock_.session().opens_at(event.
period_end_);
925 const QueuedOrder<Tag>& entry,
926 const strategy::BarEvent& event
928 if (!entry.decided_at_) {
931 if (
const auto start = period_start_of(event)) {
932 if (!(*start < *entry.decided_at_)) {
937 if (entry.decision_is_exact_) {
943 ++result_.unchecked_fill_causality_;
946 ++result_.unchecked_fill_causality_;
959 void settle_queued_against(
const strategy::BarEvent& event) {
960 const auto bar_date = clock_.session().local_date(event.
bar_.
timestamp_);
963 expire_day_orders(event, bar_date,
false);
964 std::vector<OrderRef<Tag>> eligible;
965 std::vector<OrderRef<Tag>> first_session;
966 for (
const auto& entry : runner_.executor().queued()) {
970 if (!(entry.order_.instrument_ == event.
instrument_)) {
973 if (entry.accepted_on_ >= runner_.executor().bar()) {
982 if (!may_fill(entry, event)) {
985 eligible.push_back(entry.ref_);
993 first_session.push_back(entry.ref_);
996 for (
const auto& ref : first_session) {
997 runner_.executor().assign_session(ref, bar_date);
1000 for (
const auto& ref : eligible) {
1001 const auto* entry = runner_.executor().find(ref);
1002 if (entry ==
nullptr) {
1006 const auto order = entry->order_;
1007 const auto rule = rule_for(order.instrument_);
1012 ? std::optional<Date>(clock_.session().local_date(event.
bar_.
timestamp_))
1014 auto decision = fills_.decide(
1015 order, event, rule, clock_.session().local_date(event.
period_end_), open_date,
1018 if (decision.released_) {
1021 runner_.executor().release(ref);
1022 ++result_.stop_releases_;
1024 if (decision.costing_error_) {
1028 runner_.executor().withdraw(ref);
1029 result_.halted_ = std::move(*decision.costing_error_);
1036 if (!decision.execution_) {
1037 if (!decision.still_working_) {
1040 runner_.executor().withdraw(ref);
1046 runner_.executor().withdraw(ref);
1049 decision.execution_->currency_ = currency_for(order.instrument_);
1050 if (!ledger_.apply(*decision.execution_, currency_for(order.instrument_))) {
1059 error.reached_broker_ =
false;
1060 error.broker_code_ =
"execution_not_representable";
1061 error.message_ =
"an execution's cash arithmetic does not fit an exact decimal, so the "
1062 "ledger could not record a fill the model produced";
1063 result_.halted_ = std::move(error);
1067 note_overdraft(*decision.execution_);
1068 if (decision.intrabar_release_fill_) {
1069 ++result_.stop_fills_intrabar_;
1071 if (decision.bounded_) {
1072 ++result_.limit_fills_bounded_;
1074 result_.executions_.push_back(*decision.execution_);
1075 install_portfolio();
1076 drain_reports(runner_.feed(decision.execution_->fill_));
1078 expire_day_orders(event, bar_date,
true);
1081 publish_order_updates();
1085 void note_overdraft(
const Execution& execution) {
1086 if (!(execution.currency_ == result_.currency_)) {
1089 const auto cash = ledger_.cash_in(execution.currency_);
1093 if (!result_.lowest_cash_ ||
decimal_less(*cash, result_.lowest_cash_->amount_)) {
1094 result_.lowest_cash_ = Money{*cash, execution.currency_};
1095 result_.lowest_cash_at_ = execution.fill_.at_;
1110 void expire_day_orders(
const strategy::BarEvent& event,
const Date& bar_date,
bool after_fills) {
1113 const auto closes = clock_.session().opens_at(event.
bar_.
timestamp_) - clock_.session().open_ +
1114 clock_.session().close_;
1115 const bool ends_session = after_fills && (daily_or_longer || !(
event.period_end_ < closes));
1116 std::vector<OrderRef<Tag>> expired;
1117 for (
const auto& entry : runner_.executor().queued()) {
1118 if (!(entry.order_.instrument_ == event.
instrument_) || !entry.session_ ||
1122 if (*entry.session_ < bar_date || (*entry.session_ == bar_date && ends_session)) {
1123 expired.push_back(entry.ref_);
1126 for (
const auto& ref : expired) {
1127 runner_.executor().withdraw(ref);
1128 ++result_.expired_day_orders_;
1133 void record_equity(
const strategy::BarEvent& event) {
1134 if (
const auto cash = ledger_.cash_in(config_.currency_);
1136 ++result_.overdrawn_bars_;
1137 if (!result_.first_overdrawn_) {
1138 result_.first_overdrawn_ =
event.period_end_;
1141 const auto equity = ledger_.equity_in(config_.currency_);
1146 ++result_.unvalued_bars_;
1147 if (!result_.first_unvalued_) {
1148 result_.first_unvalued_ =
event.period_end_;
1154 point.at_ =
event.period_end_;
1155 point.equity_ = *equity;
1160 const bool after_gap = equity_gap_;
1161 equity_gap_ =
false;
1162 if (!result_.equity_.empty() && !after_gap) {
1163 const auto& previous = result_.equity_.back().equity_;
1166 if (previous.coefficient() > 0) {
1171 point.period_return_ = analytics_to_scalar(*ratio);
1176 result_.equity_.push_back(point);
1186 static math::Scalar analytics_to_scalar(
const Decimal64& value) {
1188 for (std::uint8_t at = 0; at < value.scale(); ++at) {
1191 return static_cast<math::Scalar>(value.coefficient()) / divisor;
1248 void publish_order_updates() {
1249 if (result_.halted_) {
1254 std::vector<WorkingOrder<Tag>> rows;
1255 rows.reserve(runner_.executor().queued().size());
1256 for (
const auto& entry : runner_.executor().queued()) {
1257 rows.push_back(working_row(entry));
1259 for (
const auto& update : updates_.changed(rows)) {
1268 const auto* still_queued = find_queued(update);
1269 if (still_queued ==
nullptr) {
1272 install_portfolio();
1273 drain_reports(runner_.feed(update));
1274 if (result_.halted_) {
1289 const QueuedOrder<Tag>* find_queued(
const OrderSnapshot& update)
const {
1290 for (
const auto& entry : runner_.executor().queued()) {
1291 if (entry.ref_.primary_ != update.order_id_) {
1294 const auto row = working_row(entry);
1295 if (runner::order_update_detail::same_observable_state(row.order_, update)) {
1302 void install_portfolio() {
1306 if (
const auto equity = ledger_.equity_in(config_.currency_)) {
1307 runner_.set_portfolio_value(Money{*equity, config_.currency_});
1309 runner_.clear_portfolio_value();
1311 auto portfolio = ledger_.snapshot<
Tag>(clock_.now());
1312 for (
const auto& entry : runner_.executor().queued()) {
1313 portfolio.working_.push_back(working_row(entry));
1314 commit_open_buy(portfolio, entry);
1316 runner_.context().set_portfolio(std::move(portfolio));
1327 WorkingOrder<Tag> working_row(
const QueuedOrder<Tag>& entry)
const {
1328 WorkingOrder<Tag> row;
1329 row.ref_ = entry.ref_;
1335 row.order_.order_id_ = entry.ref_.primary_;
1336 row.order_.instrument_ = entry.order_.instrument_;
1337 row.order_.side_ = entry.order_.side_;
1338 row.order_.type_ = entry.order_.type_;
1339 row.order_.time_in_force_ = entry.order_.time_in_force_;
1340 row.order_.session_ = entry.order_.session_;
1341 row.order_.venue_ = entry.order_.venue_;
1342 row.order_.price_ = entry.order_.price_;
1345 row.order_.trigger_price_ = entry.order_.trigger_price_;
1346 row.order_.quantity_ = entry.order_.quantity_;
1347 row.order_.currency_ = currency_for(entry.order_.instrument_);
1350 row.order_.status_ = OrderStatus::PENDING;
1352 row.amendable_quantity_ = entry.order_.quantity_;
1376 void commit_open_buy(strategy::Portfolio<Tag>& portfolio,
const QueuedOrder<Tag>& entry)
const {
1377 if (!config_.limits_.enforce_buying_power_ || entry.order_.side_ != OrderSide::BUY) {
1380 const auto price = entry.order_.price_ ?
1381 entry.order_.price_ :
1382 runner_.context().last_price(entry.order_.instrument_);
1390 const auto currency = currency_for(entry.order_.instrument_);
1392 if (!(reading.cash_.currency_ == currency)) {
1396 reading.cash_.amount_ = *left;
1402 Currency currency_for(
const InstrumentId& instrument)
const {
1403 const auto reference = runner_.context().instrument(instrument);
1405 return config_.currency_;
1407 return reference->currency_;
1410 PriceRule rule_for(
const InstrumentId& instrument)
const {
1411 const auto reference = runner_.context().instrument(instrument);
1415 return reference->price_rule_;
1419 template <
typename Report>
1420 void drain_reports(
const Report& report) {
1425 template <
typename Report>
1426 void collect(
const Report& report) {
1427 for (
const auto& rejection : report.rejected_) {
1428 result_.rejected_.push_back(rejection);
1430 for (
const auto& refusal : report.refused_) {
1431 result_.refused_.push_back(refusal);
1446 void settle_deferred() {
1447 if (runner_.deferred_targets().empty()) {
1450 install_portfolio();
1451 collect(runner_.retry_deferred_on_one_snapshot());
1454 ReplayConfig config_;
1455 NextBarFillModel fills_;
1456 ReplayLedger ledger_;
1460 runner::OrderUpdateWatch<Tag> updates_;
1461 ReplayResult result_;
1463 bool equity_gap_{
false};
1464 std::optional<strategy::BarEvent> previous_;
1467 strategy::slice_detail::Bucket pending_;
1468 std::optional<std::chrono::system_clock::time_point> delivered_through_;
Bar-interval arithmetic and the replay clock.
A three-letter currency code, such as KRW or USD (ISO 4217 style).
An exact signed decimal: an int64 coefficient times 10^-scale.
static Decimal64 parse(std::string_view input)
Parses a decimal string exactly (no binary floating point).
const ReplayLedger & ledger() const noexcept
Returns the ledger: positions, cash and realized profit.
Backtester(Strategy strategy={}, ReplayConfig config={}, NextBarFillModel fills=NextBarFillModel{})
Constructs a replay.
const ReplayResult & result() const noexcept
Returns what the replay has produced so far.
void feed(const strategy::QuoteEvent &event, std::optional< bool > trading_day=true)
Replays one quote: moves the price and the clock to it, re-derives the phase (calling on_phase on a c...
const NextBarFillModel & fills() const noexcept
Returns the fill model, for summarize.
void feed(const strategy::BarEvent &event, std::optional< bool > trading_day=true)
Replays one bar.
QueueingExecutor< Tag > Executor
The executor type.
runner::Runner< Strategy, CapsType, Tag, Executor > Runner
The runner::Runner the replay drives, the same type a live session uses.
void finish()
Ends the replay: calls on_stop (with the current portfolio, including orders from the last bar),...
const Strategy & strategy() const noexcept
Returns the strategy, to read the state it kept.
BacktestTag Tag
The order-reference tag.
The fill model: market orders at the next bar's open, limit orders when the next bar's range reaches ...
The replay's executor: accepts orders and holds them until they fill or are withdrawn.
void deciding_at(std::chrono::system_clock::time_point at, bool exact=true)
Sets the decision instant stamped on orders accepted from now on.
const std::vector< QueuedOrder< BrokerTag > > & queued() const noexcept
Returns every outstanding order.
static constexpr bool models_time_in_force(TimeInForce condition) noexcept
std::optional< OrderRef< BrokerTag > > last_ref() const
Returns the reference issued to the most recently accepted order.
std::uint64_t bar() const noexcept
Returns the bar being replayed.
runner::SendOutcome cancel(const WorkingOrder< BrokerTag > &working)
Withdraws the order a working row names, matched by reference.
static constexpr bool reaches_a_broker
bool release(const OrderRef< BrokerTag > &ref)
Marks a queued stop-limit order as released.
void assign_session(const OrderRef< BrokerTag > &ref, Date session)
Records the session date of a queued order; see QueuedOrder::session_.
static bool can_model(const strategy::PlannedOrder &order) noexcept
const QueuedOrder< BrokerTag > * find(const OrderRef< BrokerTag > &ref) const
Finds an outstanding order.
runner::SendOutcome place(const strategy::PlannedOrder &order)
Accepts and queues an order, issuing it a reference.
bool withdraw(const OrderRef< BrokerTag > &ref)
Removes an order from the queue.
void open_bar() noexcept
Starts the next bar; orders accepted from now on cannot fill against it.
static bool contiguous(const strategy::BarEvent &previous, const strategy::BarEvent &event)
Tests whether a bar follows the previous one with no gap.
Positions, cash and realized profit, updated by each execution of a replay.
void set_calendar(TradingCalendar calendar)
Replaces the trading calendar (for runners and sources, not strategies).
Runs one strategy: feeds it events, reconciles what it asks for, checks the risk limits,...
static constexpr bool takes_bar_slices
const Context & context() const noexcept
Returns the context.
The backtest fill model: whether a queued order trades against the next bar, and at what price and co...
The replay ledger: positions, cash and realized profit a backtest keeps, in exact decimals.
Replays a strategy over stored bars: fill and cost models, the replay ledger, and the report.
std::optional< std::chrono::seconds > interval_duration(BarInterval interval)
Returns the fixed duration of an interval (bar_interval_length).
double Scalar
The floating-point type the analytics and math layers compute in (double).
Scalar missing()
Returns the value that stands for "no observation" (a quiet NaN).
SendOutcome
What became of one instruction (a placement or a cancellation) an executor was asked to send.
@ 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...
What a strategy sees and emits: events, intents, the portfolio, reconciliation and risk limits.
The library: broker-neutral types (core), and the modules below.
@ CLOSE
The timestamp is the instant the period ended.
@ OPEN
The timestamp is the first instant of the period.
constexpr std::optional< std::chrono::seconds > bar_interval_length(BarInterval interval) noexcept
Returns an interval's fixed length, for an intraday interval.
@ INVALID_ARGUMENT
The request was invalid, refused by the client before sending or by the broker.
@ LIMIT
지정가: fills at the stated price or better.
@ BEST_LIMIT
최유리지정가: priced at the best quote on the opposite side.
@ PRIMARY_LIMIT
최우선지정가: priced at the best quote on the order's own side.
@ CONDITIONAL_LIMIT
조건부지정가: a limit that becomes a market order at the close.
@ MID_PRICE
중간가: priced at the midpoint of the spread.
@ STOP_LIMIT
스톱지정가: a limit released when a trigger price is reached.
@ CLOSING_PRICE
시간외종가: priced at a session's closing price.
@ MARKET
시장가: fills at whatever the book offers.
@ REGULAR
The continuous regular session.
std::optional< Decimal64 > decimal_subtract(const Decimal64 &lhs, const Decimal64 &rhs)
Subtracts two decimals exactly.
std::optional< Decimal64 > decimal_divide(const Decimal64 &lhs, const Decimal64 &rhs, std::uint8_t scale)
Divides two decimals, truncating toward zero at a given scale.
constexpr bool time_in_force_expires_at_close(TimeInForce condition) noexcept
Tests whether an order with this condition expires at the session's close, so its filled quantity is ...
bool decimal_less(const Decimal64 &lhs, const Decimal64 &rhs)
Compares two decimals numerically, whatever their scales.
std::optional< Decimal64 > decimal_multiply(const Decimal64 &lhs, const Decimal64 &rhs)
Multiplies two decimals exactly; the scales add.
TimeInForce
How long an order stays valid and what happens to an unfilled remainder.
@ FILL_OR_KILL
Fills entirely and immediately, or not at all.
@ DAY
Rests until the session ends; an unfilled remainder is cancelled then.
@ IMMEDIATE_OR_CANCEL
Fills what it can immediately and cancels the remainder.
Decimal64 close_
The last trade price.
std::chrono::system_clock::time_point timestamp_
The instant marking the period; BarSeries::stamp_ says which edge.
The error a broker or transport operation returns.
std::string message_
A human-readable description, for logs; do not parse it.
ErrorCategory category_
The kind of failure; the field to branch on.
std::string broker_code_
The broker's own error code, verbatim (for example KIS's EGW00201).
bool reached_broker_
Whether a request was sent before this error.
A calendar date with no time of day or time zone.
A broker's reference to one order, used to amend or cancel it.
std::string primary_
The order number or id.
static PriceRule unknown(std::string reason)
Returns a rule that knows no grid and refuses to round.
Decimal64 last_price_
The last traded price, as the broker reported it.
std::chrono::system_clock::time_point received_at_
When this process received the quote, on this machine's clock.
InstrumentId instrument_
The instrument.
The trading days a broker reported for one market, ascending by date.
An order still open to modification or cancellation.
OrderRef< Broker > ref_
The reference to amend or cancel the order with.
The broker tag of a replay's order references (they reach no broker).
One point on a replay's equity curve, recorded per bar.
Decimal64 equity_
Cash plus positions marked at the bar's close, in the replay's currency.
math::Scalar period_return_
The return since the previous point; missing() on the first point, after an unvalued bar,...
std::chrono::system_clock::time_point at_
The bar's period end.
One order a replay accepted and has not yet filled or withdrawn.
std::optional< std::chrono::system_clock::time_point > decided_at_
When the strategy decided the order, if known.
std::optional< Date > session_
The market-local date of the session a DAY order lives in, once known (the date of the first bar it c...
bool released_
Whether a stop-limit order has been released, so it now rests as its limit.
bool decision_is_exact_
Whether decided_at_ is enforced strictly, or only a nominal upper bound (the period end of an open-st...
strategy::PlannedOrder order_
The order as reconciliation planned it.
std::uint64_t accepted_on_
The bar it was accepted during; it cannot fill against that bar.
OrderRef< BrokerTag > ref_
The reference the replay issued for it.
A replay's settings, besides the strategy and the bars.
strategy::RiskLimits limits_
The risk limits, applied as they are live.
Decimal64 cash_
The starting cash, in currency_.
TradingCalendar calendar_
The calendar ctx.calendar() returns, for a strategy asking about dates it has no bar for (such as whe...
Currency currency_
The accounting currency; the equity curve is in this currency only.
std::vector< Instrument > instruments_
Reference data for every instrument replayed; orders on an instrument without it are refused.
std::size_t series_capacity_
How many trailing bars the context caches per series, for indicators; must be positive.
strategy::SessionWindow session_
The session window phases are derived from and DAY orders expire at; one for the whole replay.
Everything a replay produced; summarize turns it into a Report.
std::size_t unvalued_bars_
Bars whose equity total did not fit an exact decimal, so they have no curve point.
std::size_t stop_releases_
Stop-limit orders a bar released; see ReportCaveats::stop_releases_.
std::size_t bars_
The bars fed.
std::size_t limit_fills_bounded_
Limit-type fills moved to stay in range; see ReportCaveats::limit_fills_bounded_.
std::size_t unchecked_fill_causality_
How many fill decisions could not be checked for causality; see ReportCaveats::unchecked_fill_causali...
std::optional< std::chrono::system_clock::time_point > first_unvalued_
The period end of the first unvalued bar, if any.
std::vector< strategy::GuardRejection > refused_
Instructions a risk guard refused.
std::vector< strategy::Rejection > rejected_
Instructions not placed: reconciliation could not build them, or the replay cannot model them (BINDIN...
std::vector< Execution > executions_
Every execution, in order.
std::size_t gaps_
Bars that did not follow their predecessor; gaps are not interpolated.
std::size_t expired_day_orders_
DAY orders withdrawn unfilled when their session ended, as a broker does at the close.
std::size_t unfilled_
Orders still queued when finish ran.
std::optional< BrokerError > halted_
Why the replay stopped early, if it did.
std::size_t stop_fills_intrabar_
Fills of stops released inside a bar; see ReportCaveats::stop_fills_intrabar_.
std::size_t quotes_
The quotes fed; see ReportCaveats::quotes_ for what quote-driven orders mean in a replay.
Currency currency_
The currency of the equity curve (the replay's accounting currency).
std::optional< std::chrono::system_clock::time_point > first_overdrawn_
The first bar end at which cash was below zero, if any.
std::size_t aborted_
Orders still working when the replay halted, cleared from the queue (unlike unfilled_).
std::optional< Money > lowest_cash_
The lowest cash any execution left in currency_, if below zero (checked at every fill).
std::optional< std::chrono::system_clock::time_point > lowest_cash_at_
When lowest_cash_ was reached (that execution's Fill::at_).
std::vector< EquityPoint > equity_
One point per bar that could be valued, in order.
std::size_t overdrawn_bars_
Bars that closed with cash below zero in currency_ (counted per bar fed, so two instruments count bot...
One bar, delivered to on_bar.
BarStamp stamp_
Whether bar_.timestamp_ marks the period's open or close.
std::chrono::system_clock::time_point period_end_
When the bar's period ended: the earliest moment the bar could be known.
InstrumentId instrument_
The instrument the bar is for.
BarInterval interval_
The period length the bar covers.
One order the runner is to place.
std::vector< BuyingPower > buying_power_
What may be spent, per currency.
A quote the runner has just read, delivered to on_quote.
QuoteSnapshot quote_
The quote, as the broker reported it.
The limits the operator sets for RiskGuard.
The intraday session of a market, as the operator states it.
static SessionWindow krx()
Returns the KRX regular session, 09:00-15:30 KST.