5#include <algotradelib/analytics>
6#include <algotradelib/core>
333 report.bars_ = result.
bars_;
335 report.rejected_ = result.
rejected_.size();
336 report.refused_ = result.
refused_.size();
339 report.halted_ = result.
halted_;
344 report.caveats_.commission_tariff_ = std::string(fills.
commission().
tariff());
347 report.caveats_.gaps_ = result.
gaps_;
348 report.caveats_.unfilled_ = result.
unfilled_;
352 report.caveats_.quotes_ = result.
quotes_;
368 bool costs_fit =
true;
369 const auto accumulate = [&costs_fit](std::vector<Money>& totals,
const Money& amount) {
370 for (
auto& held : totals) {
371 if (held.currency_ == amount.currency_) {
372 const auto sum =
decimal_add(held.amount_, amount.amount_);
381 totals.push_back(amount);
384 std::vector<InstrumentId> seen;
386 accumulate(report.commission_by_currency_, Money{execution.commission_, execution.currency_});
387 if (execution.institution_fee_) {
389 report.institution_by_currency_, Money{*execution.institution_fee_, execution.currency_}
394 ++report.caveats_.inclusive_commission_executions_;
399 bool counted =
false;
400 for (
auto& applied : report.caveats_.applied_rates_) {
401 if (applied == execution.applied_) {
402 ++applied.executions_;
408 auto entry = execution.applied_;
409 entry.executions_ = 1;
410 report.caveats_.applied_rates_.push_back(std::move(entry));
415 if (fills.
sell_tax() && execution.fill_.instrument_.country_ == fills.
sell_tax()->country_) {
416 accumulate(report.tax_by_currency_, Money{execution.tax_, execution.currency_});
421 if (
const auto cost =
decimal_multiply(execution.slippage_, execution.fill_.quantity_)) {
422 accumulate(report.slippage_by_currency_, Money{*cost, execution.currency_});
427 for (
const auto&
id : seen) {
428 if (
id == execution.fill_.instrument_) {
434 seen.push_back(execution.fill_.instrument_);
437 report.caveats_.cost_totals_incomplete_ = !costs_fit;
442 if (costs_fit && report.commission_by_currency_.size() == 1) {
443 report.commission_paid_ = report.commission_by_currency_.front().amount_;
447 if (costs_fit && report.caveats_.inclusive_commission_executions_ == 0 &&
448 report.institution_by_currency_.size() == 1) {
449 report.institution_fees_paid_ = report.institution_by_currency_.front().amount_;
451 if (costs_fit && report.slippage_by_currency_.size() == 1) {
452 report.slippage_paid_ = report.slippage_by_currency_.front().amount_;
454 report.caveats_.sell_tax_ = fills.
sell_tax();
455 if (fills.
sell_tax() && costs_fit && report.tax_by_currency_.size() == 1) {
456 report.tax_paid_ = report.tax_by_currency_.front().amount_;
458 report.caveats_.multiple_instruments_ = seen.size() > 1;
460 if (!(execution.currency_ == result.
currency_)) {
461 ++report.caveats_.other_currency_executions_;
477 report.opening_equity_ = result.
equity_.front().equity_;
478 report.closing_equity_ = result.
equity_.back().equity_;
483 for (std::size_t at = 0; at < result.
equity_.size(); ++at) {
499 for (std::size_t at = 0; at + 1 < result.
equity_.size(); ++at) {
500 if (result.
equity_[at].equity_.coefficient() <= 0) {
513 const auto outside = report.caveats_.other_currency_executions_;
514 if (outside > 0 && outside == result.
executions_.size()) {
515 report.total_return_ = math::missing();
516 report.sharpe_ = math::missing();
517 report.annualized_sharpe_ = math::missing();
518 report.sortino_ = math::missing();
519 report.max_drawdown_ = math::missing();
531 for (const auto& execution : result.executions_) {
532 const auto traded = decimal_multiply(execution.fill_.quantity_, execution.fill_.price_);
534 report.total_turnover_ += analytics::to_scalar(*traded) / opening;
An exact signed decimal: an int64 coefficient times 10^-scale.
const VenueKey & asserted_venue() const noexcept
Returns the asserted venue.
std::string_view tariff() const noexcept
Returns the schedule's tariff name.
bool is_scheduled() const noexcept
Tests whether the policy resolves a schedule.
The fill model: market orders at the next bar's open, limit orders when the next bar's range reaches ...
const CommissionPolicy & commission() const noexcept
Returns the commission policy.
const std::optional< SellTax > & sell_tax() const noexcept
Returns the tax on sales.
LimitFillPrice limit_price() const noexcept
Returns which price a filled limit order is credited.
const SlippageModel & slippage() const noexcept
Returns the slippage model.
The backtest engine: Backtester replays bars (and quotes) through the same Runner a live session uses...
Scalar sortino(math::SeriesView period_returns, Scalar target=Scalar{0})
Computes the per-period Sortino ratio, (mean(returns) - target) / downside_deviation.
Scalar annualize_ratio(Scalar ratio, Scalar periods_per_year)
Annualizes a per-period ratio by multiplying it by sqrt(periods_per_year).
MaxDrawdown max_drawdown(math::SeriesView levels)
Finds the worst peak-to-trough decline of a level series.
Scalar to_scalar(const Decimal64 &value)
Converts an exact decimal to the nearest Scalar.
math::Vector returns(math::SeriesView prices)
Computes simple period returns, p[i] / p[i - 1] - 1.
Scalar sharpe(math::SeriesView period_returns, Scalar risk_free=Scalar{0})
Computes the per-period Sharpe ratio, (mean(returns) - risk_free) / stdev(returns).
Replays a strategy over stored bars: fill and cost models, the replay ledger, and the report.
Report summarize(const ReplayResult &result, const NextBarFillModel &fills, std::optional< math::Scalar > periods_per_year=std::nullopt, math::Scalar risk_free=math::Scalar{0})
Summarizes a replay into a Report.
LimitFillPrice
Which price a backtest credits a limit order that fills.
@ AT_LIMIT
The default: fill at the limit, or at the bar's worst traded price when the whole bar was better than...
std::optional< math::Scalar > periods_per_year(BarInterval interval, const strategy::SessionWindow &session, math::Scalar trading_days)
Returns how many bars of an interval a year contains, for analytics::annualize_ratio.
double Scalar
The floating-point type the analytics and math layers compute in (double).
bool observed(Scalar value)
Tests whether a value is an observation.
Eigen::Index Index
The signed index type of rows and columns.
Scalar undefined()
Returns the value that stands for "the data was there, but the statistic does not exist".
Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > Vector
A dense column vector of Scalar.
Scalar missing()
Returns the value that stands for "no observation" (a quiet NaN).
The library: broker-neutral types (core), and the modules below.
std::optional< Decimal64 > decimal_add(const Decimal64 &lhs, const Decimal64 &rhs)
Adds two decimals exactly.
std::optional< Decimal64 > decimal_multiply(const Decimal64 &lhs, const Decimal64 &rhs)
Multiplies two decimals exactly; the scales add.
Scalar magnitude_
The decline as a non-negative fraction of the peak (0.2 is 20% below it).
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...
What a replay's figures assumed and did not account for.
std::size_t quotes_
How many quotes the replay was fed (zero for a bars-only replay).
static constexpr bool volume_ignored
Always true: fills are never capped by traded volume, and queue position, market impact and borrow av...
std::size_t gaps_
Bars that did not follow their predecessor (see ReplayClock::contiguous); gaps are not interpolated.
std::size_t stop_releases_
How many stop-limit orders a bar released, each using the assumed direction (a sell stop releases whe...
std::size_t inclusive_commission_executions_
How many executions were charged a single combined rate (no institution fee split).
std::optional< std::chrono::system_clock::time_point > first_unvalued_
The period end of the first unvalued bar, if any.
std::size_t limit_fills_bounded_
Limit-type fills whose price was moved to stay within the bar's range and the limit (see LimitFillPri...
bool multiple_instruments_
Whether more than one instrument traded: a caution about survivorship bias.
int slippage_ticks_
The ticks of slippage configured; fewer apply where the grid has no further step.
std::size_t unfilled_
Orders still queued, unfilled, when the replay ended.
static constexpr bool bar_tape_unverified
Always true: the replay cannot know which venue's tape its bars came from.
std::optional< Money > lowest_cash_
The lowest cash an execution left, if it went below zero.
std::size_t stop_fills_intrabar_
Fills of stops released inside a bar, which assume the price moved continuously through the trigger.
std::optional< std::chrono::system_clock::time_point > first_overdrawn_
The first bar end at which cash was below zero.
std::optional< std::chrono::system_clock::time_point > lowest_cash_at_
When the lowest cash was reached.
std::size_t overdrawn_bars_
Bars that closed with cash below zero in the replay's currency: the replay borrowed,...
std::string commission_tariff_
The tariff the commission schedule named; empty for a flat model.
std::size_t unchecked_fill_causality_
How many times a fill was allowed without the engine being able to prove its price came after the dec...
bool cost_totals_incomplete_
Whether some cost could not be summed exactly, so the cost totals are incomplete.
std::size_t unvalued_bars_
Bars whose equity total could not be computed exactly (ReplayResult::unvalued_bars_).
std::optional< SellTax > sell_tax_
The sale tax given to the fill model; absent if none was modelled.
std::vector< AppliedRate > applied_rates_
Every commission rate that charged an execution, with how many it charged.
Decimal64 slippage_basis_points_
The basis points of slippage applied.
std::size_t expired_day_orders_
DAY orders withdrawn unfilled when their session ended, as they would expire live.
LimitFillPrice limit_fill_
Which price a filled limit order was credited.
std::size_t other_currency_executions_
Executions in a currency other than the equity curve's (ReplayConfig::currency_).
std::optional< VenueKey > asserted_venue_
The venue costs were computed at, as the caller asserted it (a replay cannot observe where a fill exe...
static constexpr bool buying_power_is_ledger_cash
Always true: a replay's buying power is the ledger's own cash, not a broker's figure.
static constexpr bool sellable_is_whole_holding
Always true: a replay reports every held share as sellable.
The summary of a replay; read halted_ and caveats_ before the figures.
std::optional< BrokerError > halted_
Why the replay stopped early, if it did; read this before any figure.
math::Scalar total_turnover_
One-way turnover summed over the replay, as a fraction of equity.
std::optional< Decimal64 > slippage_paid_
Slippage given up, in money.
Decimal64 opening_equity_
Equity at the first recorded point.
math::Scalar total_return_
The total return over the replay, as a fraction; math::undefined() when the opening equity is zero or...
Decimal64 closing_equity_
Equity at the last recorded point.
math::Scalar max_drawdown_
The worst peak-to-trough decline of equity, as a non-negative fraction.
std::size_t refused_
Instructions a risk guard refused.
std::vector< Money > commission_by_currency_
Commission paid per currency.
std::size_t rejected_
Instructions not placed: reconciliation could not build them, or the replay cannot model them (BINDIN...
std::optional< Decimal64 > tax_paid_
Sale tax paid.
std::size_t aborted_
Orders cleared from the queue by a halt (unlike ReportCaveats::unfilled_).
math::Scalar annualized_sharpe_
The annualized Sharpe ratio; missing() unless a period count was given to summarize.
math::Scalar sortino_
The per-period Sortino ratio; see analytics::sortino.
std::size_t bars_
The bars replayed.
std::vector< Money > institution_by_currency_
Institution fees per currency.
std::vector< Money > slippage_by_currency_
Slippage given up per currency.
std::optional< Decimal64 > institution_fees_paid_
Institution fees (유관기관제비용) paid.
std::optional< Decimal64 > commission_paid_
Commission paid.
std::vector< Money > tax_by_currency_
Sale tax paid per currency, over executions in the taxed market.
math::Scalar sharpe_
The per-period Sharpe ratio; see analytics::sharpe.
ReportCaveats caveats_
What this run assumed and did not model.
std::size_t executions_
The executions.
Decimal64 basis_points_
The price given up per execution, in ten-thousandths of the price.
int ticks_
Grid steps given up per execution, each a step to the next grid price against the order.