17void BacktestEngine::process_event(
const BacktestEvent& event) {
18 last_prices_[
event.symbol] =
event.price;
20 if (!strategy_)
return;
22 std::optional<Order> order_opt;
24 order_opt = strategy_->on_trade(event);
26 order_opt = strategy_->on_bar(event);
29 if (!order_opt.has_value())
return;
31 auto fill_opt = execute_order(*order_opt, event);
32 if (!fill_opt.has_value())
return;
34 const Fill& fill = *fill_opt;
35 fills_.push_back(fill);
40 portfolio_.
cash -= cost;
52 double equity = portfolio_.
cash;
53 for (
auto const& [sym, qty] : portfolio_.
positions) {
54 auto it = last_prices_.find(sym);
55 if (it != last_prices_.end()) {
56 equity += qty * it->second;
64std::optional<Fill> BacktestEngine::execute_order(
const Order& order,
66 if (order.
quantity <= 0.0)
return std::nullopt;
68 double fill_price =
event.price;
78 double notional = fill_price * order.
quantity;
79 double comm = notional * commission_;
82 double affordable = portfolio_.
cash / (fill_price * (1.0 + commission_));
83 if (affordable <= 0.0)
return std::nullopt;
84 notional = fill_price * affordable;
85 comm = notional * commission_;
89 return Fill{order, fill_price, order.
quantity,
event.timestamp_ms, comm};
95 if (strategy_) strategy_->on_start();
97 while (!queue_.empty()) {
100 process_event(event);
103 if (strategy_) strategy_->on_end();
105 return compute_result();
112 result.
num_trades =
static_cast<int>(fills_.size());
115 if (eq.empty())
return result;
117 double final_equity = eq.back();
118 result.
total_return = (final_equity - initial_cash_) / initial_cash_;
121 double peak = initial_cash_;
123 double running = initial_cash_;
124 for (
double e : eq) {
125 if (e > peak) peak = e;
126 double dd = (peak - e) / peak;
127 if (dd > max_dd) max_dd = dd;
133 std::vector<double> returns;
134 returns.reserve(eq.size());
135 double prev = initial_cash_;
136 for (
double e : eq) {
137 if (prev != 0.0) returns.push_back((e - prev) / prev);
141 if (!returns.empty()) {
142 double mean_r = std::accumulate(returns.begin(), returns.end(), 0.0)
143 /
static_cast<double>(returns.size());
145 for (
double r : returns) var += (r - mean_r) * (r - mean_r);
146 var /=
static_cast<double>(returns.size());
147 double std_r = std::sqrt(var);
149 constexpr double ann = 252.0;
151 ? (mean_r / std_r) * std::sqrt(ann)
156 if (!fills_.empty()) {
157 double gross_win = 0.0;
158 double gross_loss = 0.0;
161 for (std::size_t i = 1; i < fills_.size(); ++i) {
162 const Fill& prev_f = fills_[i - 1];
163 const Fill& curr_f = fills_[i];
164 if (curr_f.order.symbol != prev_f.order.symbol)
continue;
169 pnl = (curr_f.fill_price - prev_f.fill_price) * curr_f.fill_qty
170 - curr_f.commission - prev_f.commission;
173 pnl = (prev_f.fill_price - curr_f.fill_price) * curr_f.fill_qty
174 - curr_f.commission - prev_f.commission;
177 if (pnl > 0.0) { gross_win += pnl; ++wins; }
178 else if (pnl < 0.0) { gross_loss += std::abs(pnl); }
181 int round_trips =
static_cast<int>(fills_.size()) / 2;
183 ?
static_cast<double>(wins) / round_trips
185 result.
profit_factor = (gross_loss > 0.0) ? gross_win / gross_loss : 0.0;
193std::optional<Order> RelativisticStrategy::evaluate(
const BacktestEvent& event) {
203 double dt_s =
static_cast<double>(
event.timestamp_ms - prev_event_.
timestamp_ms) / 1000.0;
204 double dp =
event.price - prev_event_.
price;
205 double momentum = (prev_event_.
price > 0.0) ? dp / prev_event_.
price : 0.0;
214 static_cast<double>(
event.timestamp_ms) / 1000.0,
221 if (!interval_opt.has_value()) {
238 if (std::abs(momentum) < threshold_)
return std::nullopt;
241 order.
symbol =
event.symbol;
242 order.quantity = base_qty_;
244 order.timestamp_ms =
event.timestamp_ms;
251 return evaluate(event);
255 return evaluate(event);