Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
event_backtester.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file include/srfm/event_backtester.hpp
4/// @brief Event-Driven Backtester — Round 3 addition.
5///
6/// # Module: Event-Driven Backtester
7///
8/// ## Responsibility
9/// Provide a lightweight event queue-based simulation engine that replays
10/// market events in strict timestamp order and passes them to a pluggable
11/// Strategy. All trade fills, portfolio accounting, and performance metrics
12/// are tracked internally.
13///
14/// ## Architecture
15/// BacktestEvent → priority_queue → BacktestEngine → Strategy::on_trade /
16/// on_bar → optional<Order> → simulated Fill → Portfolio update →
17/// BacktestResult
18///
19/// ## RelativisticStrategy
20/// A concrete Strategy subclass that uses `SpacetimeInterval::classify()` to
21/// filter trades. Only trades that are TIMELIKE relative to the previous
22/// filled trade are accepted — spacelike jumps are treated as noise and
23/// rejected.
24///
25/// ## Guarantees
26/// - No raw pointers; ownership by value
27/// - All fallible operations return std::optional or bool
28/// - Thread-safe reads: const member functions on BacktestResult are safe
29
30#include "srfm/manifold.hpp"
31#include "srfm/types.hpp"
32#include "srfm/constants.hpp"
33
34#include <cmath>
35#include <functional>
36#include <map>
37#include <memory>
38#include <optional>
39#include <queue>
40#include <string>
41#include <vector>
42
43namespace srfm::event_bt {
44
45// ─── EventType ────────────────────────────────────────────────────────────────
46
47enum class EventType {
48 Trade, ///< Individual trade execution tick
49 Quote, ///< Bid-ask quote update
50 Bar, ///< OHLCV bar (1-min, 5-min, daily, etc.)
51};
52
53// ─── BacktestEvent ────────────────────────────────────────────────────────────
54
55/// A single market event with a timestamp.
57 long long timestamp_ms; ///< Unix epoch milliseconds
58 double price; ///< Trade price / bar close / mid-quote
59 double volume; ///< Volume for this event (0 for quotes)
60 EventType type; ///< Trade, Quote, or Bar
61 std::string symbol; ///< Instrument identifier
62
63 /// Priority comparison — earlier events have higher priority.
64 bool operator>(const BacktestEvent& rhs) const noexcept {
65 return timestamp_ms > rhs.timestamp_ms;
66 }
67};
68
69// ─── OrderSide ────────────────────────────────────────────────────────────────
70
71enum class OrderSide { Buy, Sell };
72
73// ─── OrderType ────────────────────────────────────────────────────────────────
74
75enum class OrderType {
76 Market, ///< Fill immediately at current price
77 Limit, ///< Fill only when price <= limit_price (buy) or >= (sell)
78};
79
80// ─── Order ────────────────────────────────────────────────────────────────────
81
82/// An order emitted by a Strategy in response to a market event.
83struct Order {
84 std::string symbol;
86 double quantity = 0.0;
88 double limit_price = 0.0; ///< Only used for Limit orders
89 long long timestamp_ms = 0;
90};
91
92// ─── Fill ─────────────────────────────────────────────────────────────────────
93
94/// Confirmation of an executed order.
95struct Fill {
97 double fill_price = 0.0;
98 double fill_qty = 0.0;
99 long long timestamp_ms = 0;
100 double commission = 0.0;
101};
102
103// ─── Portfolio ────────────────────────────────────────────────────────────────
104
105/// Running portfolio state tracked by BacktestEngine.
106struct Portfolio {
107 double cash = 0.0;
108 std::map<std::string,double> positions; ///< symbol -> net quantity
109 std::vector<double> equity_curve; ///< Equity snapshot after each fill
110
111 /// Current gross notional exposure.
112 double gross_notional(const std::map<std::string,double>& last_prices) const {
113 double gn = 0.0;
114 for (auto const& [sym, qty] : positions) {
115 auto it = last_prices.find(sym);
116 if (it != last_prices.end()) {
117 gn += std::abs(qty) * it->second;
118 }
119 }
120 return gn;
121 }
122};
123
124// ─── BacktestResult ───────────────────────────────────────────────────────────
125
126/// Aggregate performance statistics from a completed backtest.
128 double total_return = 0.0; ///< (final_equity - initial_equity) / initial
129 double sharpe_ratio = 0.0; ///< Annualised Sharpe (assuming 252 days)
130 double max_drawdown = 0.0; ///< Peak-to-trough equity drawdown fraction
131 int num_trades = 0; ///< Total fills executed
132 double win_rate = 0.0; ///< Fraction of profitable trades
133 double profit_factor = 0.0; ///< Gross gains / gross losses
134};
135
136// ─── Strategy (abstract base) ─────────────────────────────────────────────────
137
138/// Abstract strategy interface. Subclass and override the virtual methods.
139class Strategy {
140public:
141 virtual ~Strategy() = default;
142
143 /// Called at the start of the simulation before any events are dispatched.
144 virtual void on_start() {}
145
146 /// Called at the end of the simulation after all events are processed.
147 virtual void on_end() {}
148
149 /// Called for every Trade-type event.
150 ///
151 /// @return An Order to execute, or std::nullopt for no action.
152 virtual std::optional<Order> on_trade(const BacktestEvent& /*event*/) {
153 return std::nullopt;
154 }
155
156 /// Called for every Bar-type event.
157 ///
158 /// @return An Order to execute, or std::nullopt for no action.
159 virtual std::optional<Order> on_bar(const BacktestEvent& /*event*/) {
160 return std::nullopt;
161 }
162};
163
164// ─── BacktestEngine ───────────────────────────────────────────────────────────
165
166/// Event-driven backtesting engine.
167///
168/// Events are inserted via `add_event()` and processed in strict
169/// timestamp order by `run()`. The engine simulates market fills,
170/// tracks a Portfolio, and returns a BacktestResult.
171///
172/// Usage:
173/// @code
174/// BacktestEngine engine(10'000.0, 0.001);
175/// engine.set_strategy(std::make_unique<MyStrategy>());
176/// for (auto& ev : raw_events) engine.add_event(ev);
177/// auto result = engine.run();
178/// @endcode
180public:
181 /// @param initial_cash Starting cash in quote currency.
182 /// @param commission Commission fraction per trade notional.
183 explicit BacktestEngine(double initial_cash = 100'000.0,
184 double commission = 0.001) noexcept
185 : initial_cash_(initial_cash)
186 , commission_(commission)
187 {
188 portfolio_.cash = initial_cash;
189 }
190
191 /// Set the strategy to drive order generation.
192 void set_strategy(std::unique_ptr<Strategy> strat) noexcept {
193 strategy_ = std::move(strat);
194 }
195
196 /// Insert a market event into the priority queue.
198 queue_.push(std::move(event));
199 }
200
201 /// Process all queued events and return aggregate performance.
202 ///
203 /// @return BacktestResult populated after all events are consumed.
204 [[nodiscard]] BacktestResult run();
205
206 /// Const accessor for the fill history.
207 [[nodiscard]] const std::vector<Fill>& fills() const noexcept { return fills_; }
208
209 /// Const accessor for the portfolio state after `run()`.
210 [[nodiscard]] const Portfolio& portfolio() const noexcept { return portfolio_; }
211
212private:
213 // Process a single event; may append to fills_ and update portfolio_.
214 void process_event(const BacktestEvent& event);
215
216 // Attempt to execute an Order against the current event price.
217 std::optional<Fill> execute_order(const Order& order,
218 const BacktestEvent& event);
219
220 // Compute BacktestResult from fills_ and portfolio_ equity curve.
221 [[nodiscard]] BacktestResult compute_result() const noexcept;
222
223 double initial_cash_;
224 double commission_;
225 std::unique_ptr<Strategy> strategy_;
226
227 using EventQueue = std::priority_queue<
229 std::vector<BacktestEvent>,
230 std::greater<BacktestEvent>>;
231 EventQueue queue_;
232
233 Portfolio portfolio_;
234 std::vector<Fill> fills_;
235 std::map<std::string,double> last_prices_; // symbol -> latest price
236};
237
238// ─── RelativisticStrategy ─────────────────────────────────────────────────────
239
240/// A concrete Strategy that filters trades using SpacetimeInterval::classify().
241///
242/// On every Trade event the engine converts the current and previous event
243/// into SpacetimeEvents and computes their interval. Only TIMELIKE intervals
244/// (ds² < 0) are allowed to generate orders — SPACELIKE events are treated as
245/// causally disconnected noise and ignored.
246///
247/// This implements the causal-cone hypothesis: only market moves that respect
248/// the relativistic causal structure of financial spacetime are acted upon.
250public:
251 /// @param base_qty Quantity to buy/sell on each qualifying signal.
252 /// @param threshold Fraction of equity change required to generate a signal.
253 explicit RelativisticStrategy(double base_qty = 1.0,
254 double threshold = 0.001) noexcept
255 : base_qty_(base_qty)
256 , threshold_(threshold)
257 , has_prev_(false)
258 {}
259
260 void on_start() override { has_prev_ = false; }
261
262 std::optional<Order> on_trade(const BacktestEvent& event) override;
263 std::optional<Order> on_bar (const BacktestEvent& event) override;
264
265 /// Number of events rejected due to spacelike interval.
266 [[nodiscard]] int spacelike_rejections() const noexcept {
267 return spacelike_count_;
268 }
269
270 /// Number of events accepted due to timelike interval.
271 [[nodiscard]] int timelike_accepts() const noexcept {
272 return timelike_count_;
273 }
274
275private:
276 std::optional<Order> evaluate(const BacktestEvent& event);
277
278 double base_qty_;
279 double threshold_;
280 bool has_prev_;
281 BacktestEvent prev_event_{};
282 int spacelike_count_ = 0;
283 int timelike_count_ = 0;
284};
285
286} // namespace srfm::event_bt
void add_event(BacktestEvent event)
Insert a market event into the priority queue.
const Portfolio & portfolio() const noexcept
Const accessor for the portfolio state after run().
void set_strategy(std::unique_ptr< Strategy > strat) noexcept
Set the strategy to drive order generation.
const std::vector< Fill > & fills() const noexcept
Const accessor for the fill history.
BacktestEngine(double initial_cash=100 '000.0, double commission=0.001) noexcept
int spacelike_rejections() const noexcept
Number of events rejected due to spacelike interval.
void on_start() override
Called at the start of the simulation before any events are dispatched.
int timelike_accepts() const noexcept
Number of events accepted due to timelike interval.
RelativisticStrategy(double base_qty=1.0, double threshold=0.001) noexcept
Abstract strategy interface. Subclass and override the virtual methods.
virtual void on_end()
Called at the end of the simulation after all events are processed.
virtual ~Strategy()=default
virtual std::optional< Order > on_bar(const BacktestEvent &)
virtual void on_start()
Called at the start of the simulation before any events are dispatched.
virtual std::optional< Order > on_trade(const BacktestEvent &)
Physical and financial constants for the SRFM system.
Spacetime Market Manifold — AGT-02 public API (implemented by AGT-06).
@ Market
Fill immediately at current price.
@ Limit
Fill only when price <= limit_price (buy) or >= (sell)
@ Trade
Individual trade execution tick.
@ Quote
Bid-ask quote update.
@ Bar
OHLCV bar (1-min, 5-min, daily, etc.)
A single market event with a timestamp.
bool operator>(const BacktestEvent &rhs) const noexcept
Priority comparison — earlier events have higher priority.
std::string symbol
Instrument identifier.
double volume
Volume for this event (0 for quotes)
EventType type
Trade, Quote, or Bar.
long long timestamp_ms
Unix epoch milliseconds.
double price
Trade price / bar close / mid-quote.
Aggregate performance statistics from a completed backtest.
double total_return
(final_equity - initial_equity) / initial
int num_trades
Total fills executed.
double profit_factor
Gross gains / gross losses.
double max_drawdown
Peak-to-trough equity drawdown fraction.
double win_rate
Fraction of profitable trades.
double sharpe_ratio
Annualised Sharpe (assuming 252 days)
Confirmation of an executed order.
An order emitted by a Strategy in response to a market event.
double limit_price
Only used for Limit orders.
Running portfolio state tracked by BacktestEngine.
double gross_notional(const std::map< std::string, double > &last_prices) const
Current gross notional exposure.
std::map< std::string, double > positions
symbol -> net quantity
std::vector< double > equity_curve
Equity snapshot after each fill.
Shared primitive types for the Special Relativity in Financial Modeling (SRFM) system.