3#include <algotradelib/core>
99 const auto selection = schedule.
rate_for(country, date);
100 if (!selection.rate_) {
106 if (selection.bounds_unreadable_) {
108 error.
message_ =
"the published schedule holds a commission rate for that market whose date "
109 "bounds could not be read, so whether it applies on that date is unknown";
113 error.
message_ =
"the published schedule states no commission rate for that market and date";
116 const auto& rate = selection.
rate_;
196 policy.flat_ = std::move(model);
218 "commission_tariff_unnamed",
219 "a caller-stated commission schedule must name the tariff it was read from, because the "
220 "published table is channel by venue and nothing here can select the channel"
225 "commission_venue_not_an_execution",
226 "COMBINED is consolidated routing rather than a venue an order matched on, so it cannot be "
227 "asserted as the venue a fill happened at"
231 policy.schedule_ = std::move(schedule);
232 policy.asserted_ = std::move(asserted);
233 policy.dating_market_ = dating_market;
253 std::string_view
tariff() const noexcept {
254 return schedule_ ? std::string_view(schedule_->tariff_) : std::string_view();
277 const auto charged = flat_.on(quantity, price);
280 "commission_not_representable",
281 "the commission on that execution does not fit an exact decimal"
298 if (country != dating_market_ && schedule_->any_dated_rate_for(country)) {
302 error.
broker_code_ =
"commission_date_not_market_local";
303 error.
message_ =
"this market's rates have dated bounds, and the replay's date " +
304 named(date) +
" is that of another market's trading day, so which rate applies cannot be "
305 "decided here; price it in a replay whose session is that market's, or state rates with "
310 const auto selection = schedule_->rate_for(country, asserted_, date);
311 const auto& rate = selection.rate_;
317 if (selection.bounds_unreadable_) {
319 error.
message_ =
"the schedule holds a commission rate for " + described(asserted_) +
320 " in this instrument's market whose date bounds could not be read, so whether it applies "
321 "on " + named(date) +
" is unknown; refetch the schedule or state the rates";
325 error.
message_ =
"the schedule states no commission rate for " + named(date) +
326 " at venue " + described(asserted_) +
" in the market this instrument is listed in";
331 const auto broker = component(quantity, price, rate->rate_,
Decimal64::parse(
"0"));
334 "commission_not_representable",
335 "the commission on that execution does not fit an exact decimal"
340 if (rate->institution_rate_) {
342 const auto institution =
343 component(quantity, price, *rate->institution_rate_,
Decimal64::parse(
"0"));
346 "commission_not_representable",
347 "the institution fee on that execution does not fit an exact decimal"
364 static std::string described(
const VenueKey& venue) {
365 switch (venue.
kind()) {
367 return "(none asserted)";
377 static std::string named(
const Date& date) {
378 const auto pad = [](
unsigned value) {
379 return value < 10 ?
"0" + std::to_string(value) : std::to_string(value);
381 return std::to_string(date.year_) +
"-" + pad(date.month_) +
"-" + pad(date.day_);
384 static BrokerError refusal(std::string code, std::string message) {
388 error.reached_broker_ =
false;
389 error.broker_code_ = std::move(code);
390 error.message_ = std::move(message);
395 static std::optional<Decimal64> component(
396 const Decimal64& quantity,
397 const Decimal64& price,
398 const Decimal64& rate,
399 const Decimal64& minimum
401 CommissionModel model;
403 model.minimum_ = minimum;
404 return model.on(quantity, price);
408 CommissionModel flat_{};
410 std::optional<CommissionSchedule> schedule_;
414 Country dating_market_{Country::KR};
444 std::optional<std::pair<Decimal64, Decimal64>>
apply(
460 const auto shifted = side == OrderSide::BUY ?
474 for (
int step = 0; step <
ticks_; ++step) {
475 const auto tick = side == OrderSide::BUY ?
477 tick_below(rule, walked);
482 const auto next = side == OrderSide::BUY ?
494 const auto steps = side == OrderSide::BUY ?
500 const auto shifted = side == OrderSide::BUY ?
509 const auto given_up = side == OrderSide::BUY ?
515 return std::pair<Decimal64, Decimal64>{moved, *given_up};
549 static std::optional<Decimal64> tick_below(
const PriceRule& rule,
const Decimal64& price) {
550 std::optional<Decimal64> tick;
551 for (
const auto& band : rule.
bands_) {
589 return "at_open_if_better";
std::string str() const
Returns the code as a string.
An exact signed decimal: an int64 coefficient times 10^-scale.
std::int64_t coefficient() const noexcept
Returns the coefficient.
static Decimal64 parse(std::string_view input)
Parses a decimal string exactly (no binary floating point).
Either a value or a BrokerError.
Result(T value)
Constructs a successful result.
Which venue a commission rate applies to: any venue, a normalized Venue, or a raw code.
const Code< 8 > & raw_code() const noexcept
Returns the raw venue code.
VenueKeyKind kind() const noexcept
Returns the key's kind.
Venue venue() const noexcept
Returns the normalized venue.
CommissionPolicy()=default
Constructs a policy that charges nothing (the report says so).
static Result< CommissionPolicy > scheduled(CommissionSchedule schedule, VenueKey asserted, Country dating_market)
Makes a policy that picks a rate from a schedule for each execution.
const VenueKey & asserted_venue() const noexcept
Returns the asserted venue.
std::string_view tariff() const noexcept
Returns the schedule's tariff name.
static CommissionPolicy stated(CommissionModel model)
Makes a policy that charges one model on every execution.
Result< CommissionCharge > charge(const Decimal64 &quantity, const Decimal64 &price, const Date &date, Country country) const
Computes the commission on one execution.
bool is_scheduled() const noexcept
Tests whether the policy resolves a schedule.
Replays a strategy over stored bars: fill and cost models, the replay ledger, and the report.
Result< CommissionModel > commission_model_from(const CommissionSchedule &schedule, Country country, const Date &date)
Builds a flat commission model from a schedule's rate for a market on a date.
LimitFillPrice
Which price a backtest credits a limit order that fills.
@ AT_OPEN_IF_BETTER
Fill at the bar's open when it opened through the limit (assumes the order was resting before the ope...
@ AT_LIMIT
The default: fill at the limit, or at the bar's worst traded price when the whole bar was better than...
constexpr std::string_view limit_fill_price_name(LimitFillPrice choice) noexcept
Returns a lowercase name for a choice, such as "at_limit", for reports.
The library: broker-neutral types (core), and the modules below.
@ NORMALIZED
A Venue the library normalizes.
@ RAW
A broker venue code the library does not normalize, kept verbatim.
@ ANY
Any venue (a venue-neutral rate).
CommissionOrigin
Where a commission schedule's numbers came from.
@ CALLER_STATED
The caller stated them, from a fee page or their own agreement.
@ BROKER_PUBLISHED
The broker published them, through an inquiry the library made.
constexpr bool commission_origin_names_tariff(CommissionOrigin origin) noexcept
Tests whether a schedule of this origin must name the tariff it was read from.
std::optional< Decimal64 > decimal_add(const Decimal64 &lhs, const Decimal64 &rhs)
Adds two decimals exactly.
@ INVALID_ARGUMENT
The request was invalid, refused by the client before sending or by the broker.
Country
A country (market) the library supports.
constexpr bool commission_origin_is_broker(CommissionOrigin origin) noexcept
Tests whether commission rates of this origin came from the broker.
constexpr std::string_view venue_name(Venue venue) noexcept
Returns a display name for a venue, such as "KRX".
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.
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.
OrderSide
The side of an order: its economic direction, buy or sell.
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.
std::optional< CommissionRate > rate_
The rate in force, if one definitely is.
The commission rates a broker published for an account, or a caller stated.
RateSelection rate_for(Country country, const VenueKey &asserted, const Date &date) const
Finds the rate in force for a market and venue on a date.
CommissionOrigin origin_
Whether a broker published the rates or a caller stated them.
std::string tariff_
Which tariff the rates are, in the source's own words.
A calendar date with no time of day or time zone.
The price grid (tick sizes) an instrument's order prices must sit on.
std::optional< Decimal64 > tick_at(const Decimal64 &price) const
Returns the tick size at a price.
std::vector< TickBand > bands_
The bands, ascending by from_; empty unless kind_ is STATED.
The commission rate that charged an execution, as the backtest report lists it.
VenueKey venue_
The venue the rate was keyed to.
std::optional< Decimal64 > institution_rate_
The institution fee rate; empty if the source gave one combined rate.
std::optional< Date > until_
The last date of the rate's window; empty if open-ended.
friend bool operator==(const AppliedRate &lhs, const AppliedRate &rhs)
Compares every field except executions_.
std::optional< Date > from_
The first date of the rate's window; empty if open-ended.
Decimal64 minimum_
The per-execution minimum on the broker's commission; zero if none.
CommissionOrigin origin_
Whether a broker published the rate or a caller stated it.
Decimal64 rate_
The broker's commission, as a fraction of traded value.
std::size_t executions_
How many executions the rate charged, counted in the report's table.
Country country_
The market the rate was selected for.
What one execution was charged: the broker's commission and the institution fee.
std::optional< Decimal64 > institution_
The institution fee (유관기관제비용), if the rate is split.
Decimal64 broker_
The broker's commission, after its minimum.
AppliedRate applied_
The rate that charged it.
A flat commission: a rate on traded value, with an optional per-execution minimum.
std::optional< Decimal64 > on(const Decimal64 &quantity, const Decimal64 &price) const
Computes the commission on one execution.
Decimal64 rate_
The fraction of traded value charged per execution (0.00015, not 0.015).
static CommissionModel of(Decimal64 rate, Decimal64 minimum=Decimal64::parse("0"))
Returns a rate-based model.
Decimal64 minimum_
The smallest commission on any execution.
bool from_broker_
Whether the rate came from a broker's published schedule; commission_model_from sets it from the sche...
static CommissionModel none()
Returns a model that charges nothing (the report says so).
Slippage: how far a fill price moves against the order (a buy pays more, a sell gets less).
Decimal64 basis_points_
The price given up per execution, in ten-thousandths of the price.
static SlippageModel of_basis_points(Decimal64 points)
Returns a basis-point slippage model.
static SlippageModel of_ticks(int count)
Returns a tick slippage model.
static SlippageModel none()
Returns a model with no slippage (the report says so).
int ticks_
Grid steps given up per execution, each a step to the next grid price against the order.
std::optional< std::pair< Decimal64, Decimal64 > > apply(const Decimal64 &price, OrderSide side, const PriceRule &rule) const
Moves a price against the order.