4#include "../strategy/portfolio.hpp"
7#include <algotradelib/core>
27 static constexpr std::string_view
name =
"backtest";
69 cash_.push_back(
Money{std::move(cash), std::move(currency)});
78 for (
const auto& amount : cash_) {
79 if (amount.currency_ == currency) {
80 return amount.amount_;
92 for (
const auto& lot : positions_) {
93 if (lot.instrument_ == instrument) {
106 for (
const auto& lot : positions_) {
107 if (lot.instrument_ == instrument && lot.quantity_.coefficient() != 0) {
108 return lot.average_purchase_price_;
120 for (
const auto& entry : realized_) {
121 if (entry.currency_ == currency) {
122 return entry.amount_;
136 const std::vector<HoldingPosition>&
positions() const noexcept {
return positions_; }
142 const std::vector<Execution>&
executions() const noexcept {
return executions_; }
158 const auto& fill = execution.
fill_;
162 const auto signed_quantity = fill.side_ == OrderSide::BUY ?
163 std::optional<Decimal64>(fill.quantity_) :
165 if (!signed_quantity) {
174 const auto cash_change = fill.side_ == OrderSide::BUY ?
176 std::optional<Decimal64>(*gross);
181 if (!after_commission) {
197 const auto* existing = find(fill.instrument_);
198 const auto held = existing !=
nullptr ? existing->quantity_ : zero;
199 const auto average = existing !=
nullptr ? existing->average_purchase_price_ : zero;
202 if (existing !=
nullptr && held.coefficient() != 0 &&
203 !(existing->market_value_.currency_ == currency)) {
207 const auto closing = closed_quantity(held, *signed_quantity);
213 std::optional<Decimal64> realized_total;
214 if (closing->coefficient() != 0) {
231 if (!realized_total) {
236 const auto resulting =
decimal_add(held, *signed_quantity);
248 auto new_average = average;
249 if (resulting->coefficient() == 0) {
251 }
else if (crossed_or_opened(held, *resulting)) {
252 new_average = fill.price_;
253 }
else if (adding(held, *signed_quantity)) {
254 const auto blended = weighted_average(held, average, *signed_quantity, fill.price_);
258 new_average = *blended;
264 cash_in(currency).value_or(zero), *after_tax
271 auto* lot = find_or_open(fill.instrument_);
272 lot->average_purchase_price_ = new_average;
273 lot->quantity_ = *resulting;
274 lot->last_price_ = fill.price_;
281 lot->market_value_ =
Money{*valued, currency};
282 set_amount(cash_, currency, *cash_total);
283 if (realized_total) {
284 set_amount(realized_, currency, *realized_total);
286 executions_.push_back(execution);
300 for (
auto& lot : positions_) {
301 if (lot.instrument_ == instrument) {
313 lot.last_price_ = price;
314 lot.market_value_ =
Money{*valued, lot.market_value_.currency_};
331 template <
typename BrokerTag>
333 std::chrono::system_clock::time_point as_of
340 position.sellable_quantity_ = position.quantity_;
348 portfolio.
cash_ = cash_;
352 for (
const auto& amount : cash_) {
369 for (
const auto& lot : positions_) {
370 if (lot.quantity_.coefficient() == 0) {
374 if (!(lot.market_value_.currency_ == currency)) {
394 for (
const auto& lot : positions_) {
395 if (lot.instrument_ == instrument) {
402 HoldingPosition* find_or_open(
const InstrumentId& instrument) {
403 for (
auto& lot : positions_) {
404 if (lot.instrument_ == instrument) {
408 HoldingPosition opened;
413 positions_.push_back(opened);
414 return &positions_.back();
418 static std::optional<Decimal64> closed_quantity(
419 const Decimal64& held,
420 const Decimal64& traded
426 if ((long_held && buying) || (short_held && !buying) || held.coefficient() == 0) {
429 const auto magnitude_held = long_held ? held :
decimal_subtract(zero, held);
430 const auto magnitude_traded = buying ? traded :
decimal_subtract(zero, traded);
431 if (!magnitude_held || !magnitude_traded) {
434 return decimal_less(*magnitude_held, *magnitude_traded) ? *magnitude_held : *magnitude_traded;
438 static bool crossed_or_opened(
const Decimal64& held,
const Decimal64& resulting) {
440 if (held.coefficient() == 0) {
447 static bool adding(
const Decimal64& held,
const Decimal64& traded) {
453 static std::optional<Decimal64> weighted_average(
454 const Decimal64& held,
455 const Decimal64& held_price,
456 const Decimal64& traded,
457 const Decimal64& traded_price
461 const auto traded_magnitude =
463 if (!held_magnitude || !traded_magnitude) {
468 if (!held_cost || !traded_cost) {
471 const auto total_cost =
decimal_add(*held_cost, *traded_cost);
472 const auto total_quantity =
decimal_add(*held_magnitude, *traded_magnitude);
473 if (!total_cost || !total_quantity || total_quantity->coefficient() == 0) {
485 static void set_amount(
486 std::vector<Money>& amounts,
487 const Currency& currency,
488 const Decimal64& total
490 for (
auto& amount : amounts) {
491 if (amount.currency_ == currency) {
492 amount.amount_ = total;
496 amounts.push_back(Money{total, currency});
499 std::vector<HoldingPosition> positions_;
500 std::vector<Money> cash_;
501 std::vector<Money> realized_;
502 std::vector<Execution> executions_;
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).
std::optional< Decimal64 > quantity_of(const InstrumentId &instrument) const
Returns the quantity held of an instrument.
std::optional< Decimal64 > average_price_of(const InstrumentId &instrument) const
Returns the average price of the position in an instrument.
const std::vector< HoldingPosition > & positions() const noexcept
Returns every position, including ones that went flat.
std::optional< Decimal64 > equity_in(const Currency ¤cy) const
Returns total equity in a currency: cash plus positions at their last marks.
const std::vector< Execution > & executions() const noexcept
Returns every execution applied.
ReplayLedger(Decimal64 cash, Currency currency)
Constructs a ledger holding only cash.
bool mark(const InstrumentId &instrument, const Decimal64 &price)
Marks positions in an instrument to a price, updating their market value.
Decimal64 realized_in(const Currency ¤cy) const
Returns the profit realized by closing positions, in a currency (fees excluded).
strategy::Portfolio< BrokerTag > snapshot(std::chrono::system_clock::time_point as_of) const
Returns the portfolio a strategy sees.
bool apply(const Execution &execution, const Currency ¤cy)
Applies one execution to the position, cash and realized profit.
std::optional< Decimal64 > cash_in(const Currency ¤cy) const
Returns the cash held in a currency.
Trading costs for a backtest: commission (CommissionModel, CommissionPolicy), slippage (SlippageModel...
The events a runner delivers to a strategy's handlers, and the market phase.
Replays a strategy over stored bars: fill and cost models, the replay ledger, and the report.
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_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.
What the broker says may be spent, in one currency.
One position row in a holdings snapshot.
InstrumentId instrument_
The instrument held (brokers report holdings without a trading venue).
Identifies an instrument: its country, venue and symbol.
An exact amount of money in one currency.
What a sellable quantity covers, and how RiskGuard must treat resting sells against it.
@ WHOLE_HOLDING
Every share held of the instrument, whatever venue or lot.
@ DEDUCT
Subtract every resting sell from the figure.
The commission rate that charged an execution, as the backtest report lists it.
The broker tag of a replay's order references (they reach no broker).
static constexpr std::string_view name
The name an order reference prints: "backtest".
One execution a backtest produced, and what it cost.
std::optional< Decimal64 > institution_fee_
The institution fee (유관기관제비용), if the rate was split; empty is not zero.
Decimal64 commission_
The broker's commission; includes the institution fee when institution_fee_ is empty.
AppliedRate applied_
The commission rate that charged this execution.
Decimal64 slippage_
The price given up to slippage (already included in the fill price).
strategy::Fill fill_
The fill, as on_fill receives it.
Decimal64 tax_
The sale tax charged; zero unless a sell tax applies (NextBarFillModel::with_sell_tax).
Currency currency_
The currency of the cost figures.
One execution of an order the strategy placed, delivered to on_fill.
What the account holds and which orders are still working, as a strategy sees it.
SellableBasis sellable_basis_
What the sellable_quantity_ figures on positions_ cover.
std::vector< Money > cash_
Cash per currency, in a backtest only.
std::vector< BuyingPower > buying_power_
What may be spent, per currency.
std::chrono::system_clock::time_point as_of_
When the holdings were received, on this machine's clock.
std::vector< HoldingPosition > positions_
Every position row the broker reported.