algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
fill.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "./cost.hpp"
4#include "./ledger.hpp"
5
8
9#include <algotradelib/core>
10
11#include <optional>
12#include <stdexcept>
13#include <utility>
14
15namespace algotradelib {
16namespace backtest {
17
26
30 std::optional<Execution> execution_;
32 bool still_working_{false};
38 std::optional<BrokerError> costing_error_;
40 bool released_{false};
50 bool bounded_{false};
51};
52
54struct SellTax {
58 Country country_{Country::KR};
59};
60
69public:
80 )
81 : commission_(CommissionPolicy::stated(std::move(commission))),
82 slippage_(std::move(slippage)),
83 limit_price_(limit_price) {}
84
95 )
96 : commission_(std::move(commission)),
97 slippage_(std::move(slippage)),
98 limit_price_(limit_price) {}
99
104 const CommissionPolicy& commission() const noexcept { return commission_; }
105
110 const SlippageModel& slippage() const noexcept { return slippage_; }
111
116 LimitFillPrice limit_price() const noexcept { return limit_price_; }
117
132 throw std::invalid_argument("a sell tax rate must be at least 0 and below 1");
133 }
134 auto model = *this;
135 model.sell_tax_ = std::move(tax);
136 return model;
137 }
138
143 const std::optional<SellTax>& sell_tax() const noexcept { return sell_tax_; }
144
151 static constexpr bool limit_priced(OrderType type) noexcept {
152 switch (type) {
153 case OrderType::LIMIT:
155 return true;
156 case OrderType::UNKNOWN:
163 return false;
164 }
165 return false;
166 }
167
178 static bool rests_unfilled(const strategy::PlannedOrder& order) noexcept {
179 switch (order.time_in_force_) {
180 case TimeInForce::DAY:
181 break;
182 // Filled or not on the next bar, and not kept after it.
185 case TimeInForce::UNKNOWN:
186 return false;
187 }
188 // A stop rests while dormant and, once released, as the limit it has become.
189 return limit_priced(order.type_);
190 }
191
221 const strategy::PlannedOrder& order,
222 const strategy::BarEvent& next,
223 const PriceRule& rule,
224 const Date& date,
225 const std::optional<Date>& open_date = std::nullopt,
226 bool released = false
227 ) const {
228 FillDecision decision;
229 bool at_open = false;
230 std::optional<Decimal64> reference;
231 if (carries_trigger_price(order.type_) && !released) {
232 if (!order.trigger_price_ || !order.price_) {
233 // Not a stop this model can release (a caller driving the model directly).
234 return decision;
235 }
236 switch (release_of(order, next)) {
237 case Release::NONE:
238 // Dormant: the bar never reached the trigger.
239 decision.still_working_ = true;
240 return decision;
241 case Release::AT_OPEN:
242 // The whole bar is after the release, so it is priced as the limit it now is.
243 decision.released_ = true;
244 reference = reference_price(order, next, at_open);
245 // Dated from the open whatever the price policy credits: it traded at the open.
246 if (reference && limit_marketable_at(order, *next.bar_.open_)) {
247 at_open = true;
248 }
249 break;
250 case Release::INTRABAR:
251 decision.released_ = true;
252 reference = intrabar_release_price(order, next);
253 if (!reference) {
254 // The bar cannot show the price reached the limit after the release. Rests for the rest of
255 // its session (on daily bars, it expires unfilled).
256 decision.still_working_ = true;
257 return decision;
258 }
259 decision.intrabar_release_fill_ = true;
260 break;
261 }
262 } else {
263 reference = reference_price(order, next, at_open);
264 }
265 if (!reference) {
266 // A limit the bar never reached stays queued for the rest of its session.
267 decision.still_working_ = rests_unfilled(order);
268 return decision;
269 }
270
271 // A limit fill is credited a price its bar traded and never one through the limit: the bound is a
272 // sell's max(low, limit) or a buy's min(high, limit), applied before and after slippage.
273 std::optional<Decimal64> bound;
274 const bool limit_basis = limit_priced(order.type_);
275 if (limit_basis && order.price_) {
276 const auto& limit = *order.price_;
277 if (order.side_ == OrderSide::SELL) {
278 bound = decimal_less(limit, next.bar_.low_) ? next.bar_.low_ : limit;
279 if (decimal_less(*reference, *bound)) {
280 reference = bound;
281 decision.bounded_ = true;
282 }
283 } else {
284 bound = decimal_less(next.bar_.high_, limit) ? next.bar_.high_ : limit;
285 if (decimal_less(*bound, *reference)) {
286 reference = bound;
287 decision.bounded_ = true;
288 }
289 }
290 }
291 auto slipped = slippage_.apply(*reference, order.side_, rule);
292 if (slipped && decision.intrabar_release_fill_) {
293 // No favourable slippage after an intrabar release: the bar's other extreme may have come first.
294 const bool favourable = order.side_ == OrderSide::SELL ?
295 decimal_less(*reference, slipped->first) :
296 decimal_less(slipped->first, *reference);
297 if (favourable) {
298 slipped->first = *reference;
299 slipped->second = Decimal64::parse("0");
300 }
301 }
302 if (slipped && bound) {
303 // Negative slippage cannot carry a fill past the bar's opposite extreme either.
304 const auto& ceiling = order.side_ == OrderSide::SELL ? next.bar_.high_ : next.bar_.low_;
305 const bool past = order.side_ == OrderSide::SELL ?
306 decimal_less(ceiling, slipped->first) :
307 decimal_less(slipped->first, ceiling);
308 if (past) {
309 const auto moved = order.side_ == OrderSide::SELL ?
310 decimal_subtract(*reference, ceiling) :
311 decimal_subtract(ceiling, *reference);
312 if (!moved) {
313 slipped.reset();
314 } else {
315 slipped->first = ceiling;
316 slipped->second = *moved;
317 decision.bounded_ = true;
318 }
319 }
320 }
321 if (slipped && bound) {
322 const bool beyond = order.side_ == OrderSide::SELL ?
323 decimal_less(slipped->first, *bound) :
324 decimal_less(*bound, slipped->first);
325 if (beyond) {
326 const auto moved = order.side_ == OrderSide::SELL ?
327 decimal_subtract(*reference, *bound) :
328 decimal_subtract(*bound, *reference);
329 if (!moved) {
330 slipped.reset();
331 } else {
332 slipped->first = *bound;
333 slipped->second = *moved;
334 decision.bounded_ = true;
335 }
336 }
337 }
338 if (!slipped) {
339 decision.still_working_ = rests_unfilled(order);
340 return decision;
341 }
342 const auto& charged_on = at_open && open_date ? *open_date : date;
343 auto charged =
344 commission_.charge(order.quantity_, slipped->first, charged_on, order.instrument_.country_);
345 if (!charged) {
346 // A cost that cannot be resolved halts the engine; it is neither a non-fill nor silent.
347 decision.costing_error_ = std::move(charged.error());
348 return decision;
349 }
350
351 // The tax on a sale, where a rate was stated: of the sale's value, truncated to whole units.
352 std::optional<Decimal64> tax;
353 if (sell_tax_ && order.side_ == OrderSide::SELL && order.instrument_.country_ == sell_tax_->country_) {
354 const auto value = decimal_multiply(order.quantity_, slipped->first);
355 const auto charged = value ? decimal_multiply(*value, sell_tax_->rate_) : std::nullopt;
356 tax = charged ? decimal_floor_to_multiple(*charged, Decimal64::parse("1")) : std::nullopt;
357 if (!tax) {
358 // Not a tax of zero: the cost is unknown, so the engine halts.
359 BrokerError error;
361 error.reached_broker_ = false;
362 error.broker_code_ = "tax_not_representable";
363 error.message_ = "the tax on that sale does not fit an exact decimal";
364 decision.costing_error_ = std::move(error);
365 return decision;
366 }
367 }
368
369 Execution execution;
370 execution.fill_.instrument_ = order.instrument_;
371 execution.fill_.side_ = order.side_;
372 execution.fill_.quantity_ = order.quantity_;
373 execution.fill_.price_ = slipped->first;
374 execution.fill_.tag_ = order.tag_;
375 execution.fill_.at_ = next.period_end_;
376 execution.commission_ = charged.value().broker_;
377 execution.institution_fee_ = charged.value().institution_;
378 execution.applied_ = std::move(charged.value().applied_);
379 execution.slippage_ = slipped->second;
380 if (tax) {
381 execution.tax_ = *tax;
382 }
383 decision.execution_ = std::move(execution);
384 return decision;
385 }
386
387private:
388 // Where in a bar a dormant stop released, if it did.
389 enum class Release {
390 // The bar never reached the trigger.
391 NONE,
392 // The open was already through the trigger, so the whole bar followed the release.
393 AT_OPEN,
394 // The bar's extreme reached the trigger and its open did not.
395 INTRABAR,
396 };
397
398 // Returns whether, and where, `next` released a dormant stop.
399 //
400 // **A sell stop releases when the price falls to its trigger and a buy stop when it rises to it.**
401 // That is an assumption about the broker -- neither KIS nor Kiwoom documents the direction -- and
402 // it is the universal convention. `ReportCaveats::stop_releases_` counts how often it was used.
403 //
404 // A bar with no open cannot establish a release *at* the open, so a release there is intrabar,
405 // which is the answer that credits less.
406 static Release release_of(const strategy::PlannedOrder& order, const strategy::BarEvent& next) {
407 const auto& trigger = *order.trigger_price_;
408 const bool sell = order.side_ == OrderSide::SELL;
409 const bool reached = sell ?
410 !decimal_less(trigger, next.bar_.low_) :
411 !decimal_less(next.bar_.high_, trigger);
412 if (!reached) {
413 return Release::NONE;
414 }
415 if (next.bar_.open_) {
416 const auto& open = *next.bar_.open_;
417 const bool through = sell ? !decimal_less(trigger, open) : !decimal_less(open, trigger);
418 if (through) {
419 return Release::AT_OPEN;
420 }
421 }
422 return Release::INTRABAR;
423 }
424
425 // Returns whether the order's limit would trade at `price` -- a sell at or above it, a buy at or below.
426 static bool limit_marketable_at(const strategy::PlannedOrder& order, const Decimal64& price) {
427 const auto& limit = *order.price_;
428 return order.side_ == OrderSide::SELL ? !decimal_less(price, limit) : !decimal_less(limit, price);
429 }
430
431 // Returns what a stop released inside `next` is credited with, or nothing when the bar cannot show
432 // it traded after the release.
433 //
434 // At the release the price *was* the trigger. Two cases, using only prices the bar proves came after
435 // it -- the trigger itself and the close, which is always last:
436 //
437 // - **Marketable at the trigger** (a sell limited at or below it, a buy at or above): it trades. It is
438 // credited the **worst price the bar traded that the limit allows** -- a sell the higher of its
439 // limit and the bar's low, a buy the lower of its limit and the bar's high -- so the price is inside
440 // the bar and never through the limit. Crediting the limit itself could name a price the bar never
441 // reached; crediting the trigger would assume the book was there.
442 // - **Not marketable at the trigger** (a sell limited above it): it trades only if the price came back
443 // to the limit after the release. The close proves that when it is at or through the limit; then it
444 // is credited the limit. Otherwise the bar's other extreme may have come first, and nothing is.
445 static std::optional<Decimal64> intrabar_release_price(
446 const strategy::PlannedOrder& order,
447 const strategy::BarEvent& next
448 ) {
449 const auto& trigger = *order.trigger_price_;
450 const auto& limit = *order.price_;
451 const bool sell = order.side_ == OrderSide::SELL;
452 if (limit_marketable_at(order, trigger)) {
453 if (sell) {
454 return decimal_less(limit, next.bar_.low_) ? next.bar_.low_ : limit;
455 }
456 return decimal_less(next.bar_.high_, limit) ? next.bar_.high_ : limit;
457 }
458 if (limit_marketable_at(order, next.bar_.close_)) {
459 return limit;
460 }
461 return std::nullopt;
462 }
463
464 // Returns the price before slippage, or nothing when the order does not trade against this bar.
465 //
466 // A market order takes the open. A limit trades only if the bar's range reached it — a buy needs the
467 // low at or below the limit, a sell needs the high at or above it — because a bar that never touched
468 // the price is a bar in which the order sat unfilled, however close it came.
469 //
470 // Sets `at_open` when the price is the bar's stated open -- a market order, or a limit under
471 // `AT_OPEN_IF_BETTER` whose open was better -- which is what lets `decide` date the charge from the
472 // open's day. False for a bar with no open, whose close stands in for it.
473 std::optional<Decimal64> reference_price(
474 const strategy::PlannedOrder& order,
475 const strategy::BarEvent& next,
476 bool& at_open
477 ) const {
478 at_open = false;
479 // A bar with no open prices a market order at its close, which is not the open's moment.
480 const bool has_open = next.bar_.open_.has_value();
481 const auto open = has_open ? *next.bar_.open_ : next.bar_.close_;
482
483 // A released stop is priced as the limit it has become.
484 const bool limit_basis = limit_priced(order.type_);
485 if (!limit_basis || !order.price_) {
486 at_open = has_open;
487 return open;
488 }
489 const auto& limit = *order.price_;
490 bool open_if_better = false;
491 switch (limit_price_) {
493 open_if_better = true;
494 break;
496 break;
497 }
498
499 if (order.side_ == OrderSide::BUY) {
500 if (decimal_less(limit, next.bar_.low_)) {
501 return std::nullopt;
502 }
503 if (open_if_better && decimal_less(open, limit)) {
504 at_open = has_open;
505 return open;
506 }
507 return limit;
508 }
509
510 if (decimal_less(next.bar_.high_, limit)) {
511 return std::nullopt;
512 }
513 if (open_if_better && decimal_less(limit, open)) {
514 at_open = has_open;
515 return open;
516 }
517 return limit;
518 }
519
520 CommissionPolicy commission_;
521 SlippageModel slippage_;
522 LimitFillPrice limit_price_;
523 std::optional<SellTax> sell_tax_;
524};
525
526} // namespace backtest
527} // namespace algotradelib
An exact signed decimal: an int64 coefficient times 10^-scale.
Definition types.hpp:297
static Decimal64 parse(std::string_view input)
Parses a decimal string exactly (no binary floating point).
Definition types.hpp:323
How a backtest charges commission: one stated model, or a schedule resolved per execution.
Definition cost.hpp:184
const CommissionPolicy & commission() const noexcept
Returns the commission policy.
Definition fill.hpp:104
NextBarFillModel(CommissionPolicy commission, SlippageModel slippage=SlippageModel::none(), LimitFillPrice limit_price=LimitFillPrice::AT_LIMIT)
Constructs a model with a commission policy, which may resolve a schedule per execution.
Definition fill.hpp:91
NextBarFillModel with_sell_tax(SellTax tax) const
Returns a copy of this model that charges a tax on sales.
Definition fill.hpp:130
FillDecision decide(const strategy::PlannedOrder &order, const strategy::BarEvent &next, const PriceRule &rule, const Date &date, const std::optional< Date > &open_date=std::nullopt, bool released=false) const
Decides whether an order trades against a bar, and at what price and cost.
Definition fill.hpp:220
const std::optional< SellTax > & sell_tax() const noexcept
Returns the tax on sales.
Definition fill.hpp:143
NextBarFillModel(CommissionModel commission=CommissionModel::none(), SlippageModel slippage=SlippageModel::none(), LimitFillPrice limit_price=LimitFillPrice::AT_LIMIT)
Constructs a model with a flat commission.
Definition fill.hpp:76
static bool rests_unfilled(const strategy::PlannedOrder &order) noexcept
Tests whether an order that did not fill stays queued for a later bar of its session.
Definition fill.hpp:178
LimitFillPrice limit_price() const noexcept
Returns which price a filled limit order is credited.
Definition fill.hpp:116
static constexpr bool limit_priced(OrderType type) noexcept
Tests whether the model prices an order type against its limit (a limit order, and a stop-limit once ...
Definition fill.hpp:151
const SlippageModel & slippage() const noexcept
Returns the slippage model.
Definition fill.hpp:110
Trading costs for a backtest: commission (CommissionModel, CommissionPolicy), slippage (SlippageModel...
The events a runner delivers to a strategy's handlers, and the market phase.
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.
Definition clock.hpp:14
LimitFillPrice
Which price a backtest credits a limit order that fills.
Definition cost.hpp:562
@ AT_OPEN_IF_BETTER
Fill at the bar's open when it opened through the limit (assumes the order was resting before the ope...
Definition cost.hpp:576
@ AT_LIMIT
The default: fill at the limit, or at the bar's worst traded price when the whole bar was better than...
Definition cost.hpp:571
The library: broker-neutral types (core), and the modules below.
std::optional< Decimal64 > decimal_floor_to_multiple(const Decimal64 &value, const Decimal64 &step)
Rounds down to a multiple of a step (toward negative infinity).
Definition types.hpp:639
@ INVALID_ARGUMENT
The request was invalid, refused by the client before sending or by the broker.
Definition types.hpp:713
OrderType
How an order's price is decided (its price basis).
Definition types.hpp:1707
@ LIMIT
지정가: fills at the stated price or better.
Definition types.hpp:1710
@ BEST_LIMIT
최유리지정가: priced at the best quote on the opposite side.
Definition types.hpp:1716
@ PRIMARY_LIMIT
최우선지정가: priced at the best quote on the order's own side.
Definition types.hpp:1718
@ CONDITIONAL_LIMIT
조건부지정가: a limit that becomes a market order at the close.
Definition types.hpp:1714
@ MID_PRICE
중간가: priced at the midpoint of the spread.
Definition types.hpp:1720
@ STOP_LIMIT
스톱지정가: a limit released when a trigger price is reached.
Definition types.hpp:1722
@ CLOSING_PRICE
시간외종가: priced at a session's closing price.
Definition types.hpp:1724
@ MARKET
시장가: fills at whatever the book offers.
Definition types.hpp:1712
constexpr bool carries_trigger_price(OrderType type) noexcept
Tests whether an order type takes a trigger price.
Definition types.hpp:1774
Country
A country (market) the library supports.
Definition types.hpp:30
std::optional< Decimal64 > decimal_subtract(const Decimal64 &lhs, const Decimal64 &rhs)
Subtracts two decimals exactly.
Definition types.hpp:530
bool decimal_less(const Decimal64 &lhs, const Decimal64 &rhs)
Compares two decimals numerically, whatever their scales.
Definition types.hpp:665
std::optional< Decimal64 > decimal_multiply(const Decimal64 &lhs, const Decimal64 &rhs)
Multiplies two decimals exactly; the scales add.
Definition types.hpp:571
@ FILL_OR_KILL
Fills entirely and immediately, or not at all.
Definition types.hpp:1804
@ DAY
Rests until the session ends; an unfilled remainder is cancelled then.
Definition types.hpp:1800
@ IMMEDIATE_OR_CANCEL
Fills what it can immediately and cancels the remainder.
Definition types.hpp:1802
Reconciliation: turning a strategy's intents and targets into the orders and cancellations to send.
Decimal64 high_
The highest trade price.
Decimal64 close_
The last trade price.
std::optional< Decimal64 > open_
The first trade's price; empty if nothing traded in the period (never zero or the previous close).
Decimal64 low_
The lowest trade price.
The error a broker or transport operation returns.
Definition types.hpp:742
std::string message_
A human-readable description, for logs; do not parse it.
Definition types.hpp:772
ErrorCategory category_
The kind of failure; the field to branch on.
Definition types.hpp:744
std::string broker_code_
The broker's own error code, verbatim (for example KIS's EGW00201).
Definition types.hpp:761
bool reached_broker_
Whether a request was sent before this error.
Definition types.hpp:753
A calendar date with no time of day or time zone.
Definition types.hpp:1142
Country country_
The listing country, which selects the symbol convention and the price grid.
Definition types.hpp:80
The price grid (tick sizes) an instrument's order prices must sit on.
A flat commission: a rate on traded value, with an optional per-execution minimum.
Definition cost.hpp:28
static CommissionModel none()
Returns a model that charges nothing (the report says so).
Definition cost.hpp:67
One execution a backtest produced, and what it cost.
Definition ledger.hpp:31
std::optional< Decimal64 > institution_fee_
The institution fee (유관기관제비용), if the rate was split; empty is not zero.
Definition ledger.hpp:41
Decimal64 commission_
The broker's commission; includes the institution fee when institution_fee_ is empty.
Definition ledger.hpp:35
AppliedRate applied_
The commission rate that charged this execution.
Definition ledger.hpp:45
Decimal64 slippage_
The price given up to slippage (already included in the fill price).
Definition ledger.hpp:47
strategy::Fill fill_
The fill, as on_fill receives it.
Definition ledger.hpp:33
Decimal64 tax_
The sale tax charged; zero unless a sell tax applies (NextBarFillModel::with_sell_tax).
Definition ledger.hpp:53
What NextBarFillModel::decide concluded about one order and one bar.
Definition fill.hpp:28
std::optional< BrokerError > costing_error_
Why a fill could not be costed (no commission rate, or a tax that does not fit).
Definition fill.hpp:38
bool still_working_
Whether the order stays queued for a later bar (it did not trade but may still).
Definition fill.hpp:32
bool bounded_
Whether a limit-type fill price was moved to stay within the bar's range and the limit; counted by th...
Definition fill.hpp:50
bool released_
Whether this bar released a dormant stop-limit order (whether or not it also filled).
Definition fill.hpp:40
std::optional< Execution > execution_
The execution, if the order traded against this bar.
Definition fill.hpp:30
bool intrabar_release_fill_
Whether the fill depended on a stop released inside the bar, where the bar cannot show the order of p...
Definition fill.hpp:45
A tax on sales, stated by the caller; see NextBarFillModel::with_sell_tax.
Definition fill.hpp:54
Country country_
The market whose sales pay it.
Definition fill.hpp:58
Decimal64 rate_
The fraction of a sale's value charged, such as 0.0020 for 0.20%.
Definition fill.hpp:56
Slippage: how far a fill price moves against the order (a buy pays more, a sell gets less).
Definition cost.hpp:425
static SlippageModel none()
Returns a model with no slippage (the report says so).
Definition cost.hpp:522
One bar, delivered to on_bar.
Definition event.hpp:202
std::chrono::system_clock::time_point period_end_
When the bar's period ended: the earliest moment the bar could be known.
Definition event.hpp:218
std::chrono::system_clock::time_point at_
When the execution happened.
Definition event.hpp:421
InstrumentId instrument_
The instrument traded.
Definition event.hpp:411
std::string tag_
The tag of the intent that produced the order.
Definition event.hpp:419
OrderSide side_
The side of the order.
Definition event.hpp:413
Decimal64 price_
The execution price.
Definition event.hpp:417
Decimal64 quantity_
The quantity of this execution, not the order's running total.
Definition event.hpp:415
One order the runner is to place.
std::string tag_
The tag of the intent or target the order came from.
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.