tzutrader
A composable C++ backtesting library for trading strategies (experimental)
Loading...
Searching...
No Matches
stats.h
Go to the documentation of this file.
1#ifndef STATS_H
2#define STATS_H
3
4#include <algorithm>
5#include <cmath>
6#include <cstdint>
7#include <numeric>
8#include <vector>
9
10namespace tzu {
11
13 private:
14 int64_t init_timestamp = 0;
15 int64_t last_timestamp = 0;
16 double init_cash = 0.0;
17 double init_price = std::nan("");
18 double last_price = std::nan("");
19 std::vector<std::pair<int64_t, double>> equity_curve;
20 std::vector<Trade> trades;
21 uint16_t num_trades = 0;
22 uint16_t num_stop_loss = 0;
23 uint16_t num_take_profit = 0;
24 double total_costs = 0.0;
25
26 public:
27 void initialize(int64_t timestamp, double cash, double price) {
28 init_timestamp = timestamp;
29 last_timestamp = timestamp;
30 init_cash = cash;
31 init_price = price;
32 last_price = price;
33 equity_curve.emplace_back(timestamp, cash);
34 }
35
36 bool is_initialized() const { return init_timestamp != 0; }
37
38 void record_equity(int64_t timestamp, double total_value, double price) {
39 last_timestamp = timestamp;
40 last_price = price;
41 equity_curve.emplace_back(timestamp, total_value);
42 }
43
44 void record_trade_open(int64_t timestamp, double quantity, double price) {
45 trades.push_back(Trade{timestamp, 0, price, 0.0, quantity, 0.0, false});
46 }
47
48 void record_trade_close(int64_t timestamp, double quantity,
49 double open_price, double close_price,
50 double profit, bool is_stop_loss,
51 bool is_take_profit) {
52 if (is_stop_loss) ++num_stop_loss;
53 if (is_take_profit) ++num_take_profit;
54 trades.push_back(Trade{0, timestamp, open_price, close_price, quantity,
55 profit, true});
56 }
57
58 void increment_trades() { ++num_trades; }
59
60 void add_costs(double cost) { total_costs += cost; }
61
62 uint16_t get_num_wins() const {
63 uint16_t wins = 0;
64 for (const auto& trade : trades) {
65 if (trade.closed && trade.profit > 0) ++wins;
66 }
67 return wins;
68 }
69
70 uint16_t get_num_losses() const {
71 uint16_t losses = 0;
72 for (const auto& trade : trades) {
73 if (trade.closed && trade.profit <= 0) ++losses;
74 }
75 return losses;
76 }
77
78 double get_win_rate() const {
79 uint16_t wins = get_num_wins();
80 uint16_t losses = get_num_losses();
81 uint16_t total = wins + losses;
82 return total > 0 ? static_cast<double>(wins) / total : 0.0;
83 }
84
85 void print_summary(std::ostream& os, double curr_cash, double holdings,
86 double qty, double total_value) const;
87};
88
93 const std::vector<std::pair<int64_t, double>>& equity_curve) {
94 if (equity_curve.empty()) return 0.0;
95
96 double peak = equity_curve.front().second;
97 double max_dd = 0.0;
98 for (const auto& point : equity_curve) {
99 if (point.second > peak) peak = point.second;
100 double dd = (peak - point.second) / peak;
101 if (dd > max_dd) max_dd = dd;
102 }
103 return max_dd;
104}
105
111inline double compute_sharpe_ratio(const std::vector<double>& returns,
112 double years) {
113 if (returns.size() < 2 || years <= 0.0) return 0.0;
114
115 size_t n = returns.size();
116 double mean = std::accumulate(returns.begin(), returns.end(), 0.0) / n;
117
118 double var = 0.0;
119 for (double r : returns) {
120 var += (r - mean) * (r - mean);
121 }
122 var /= (n - 1);
123 double stddev = std::sqrt(var);
124
125 if (stddev <= 0.0) return 0.0;
126
127 double periods_per_year = static_cast<double>(n) / years;
128 return (mean / stddev) * std::sqrt(periods_per_year);
129}
130
134inline std::vector<double> compute_returns(
135 const std::vector<std::pair<int64_t, double>>& equity_curve) {
136 std::vector<double> returns;
137 if (equity_curve.size() < 2) return returns;
138
139 returns.reserve(equity_curve.size() - 1);
140 for (size_t i = 1; i < equity_curve.size(); ++i) {
141 double r = (equity_curve[i].second / equity_curve[i - 1].second) - 1.0;
142 returns.push_back(r);
143 }
144 return returns;
145}
146
151 const std::vector<std::pair<int64_t, double>>& equity_curve) {
152 PerformanceMetrics metrics;
153 if (equity_curve.empty()) return metrics;
154
155 double start_value = equity_curve.front().second;
156 double end_value = equity_curve.back().second;
157 metrics.total_return = (end_value / start_value) - 1.0;
158
159 // Compute time period in years
160 if (equity_curve.size() >= 2) {
161 double seconds = static_cast<double>(equity_curve.back().first -
162 equity_curve.front().first);
163 metrics.years = seconds / (365.0 * 24.0 * 3600.0);
164 }
165
166 // Annualized return only for periods > 30 days
167 const double min_period_years = 30.0 / 365.0;
168 if (metrics.years >= min_period_years) {
169 metrics.annual_return =
170 std::pow(end_value / start_value, 1.0 / metrics.years) - 1.0;
171 metrics.has_annual_return = true;
172 }
173
174 // Maximum drawdown
175 metrics.max_drawdown = compute_max_drawdown(equity_curve);
176
177 // Sharpe ratio
178 if (equity_curve.size() >= 2) {
179 std::vector<double> returns = compute_returns(equity_curve);
180 metrics.sharpe_ratio = compute_sharpe_ratio(returns, metrics.years);
181 }
182
183 return metrics;
184}
185
190 double init_price,
191 double final_price,
192 double years) {
193 BuyAndHoldMetrics metrics;
194
195 if (std::isnan(init_price) || init_price <= 0.0 ||
196 std::isnan(final_price) || final_price <= 0.0) {
197 return metrics;
198 }
199
200 metrics.quantity = std::floor(init_cash / init_price);
201 metrics.cash_left = init_cash - (metrics.quantity * init_price);
202 metrics.final_value = metrics.quantity * final_price + metrics.cash_left;
203 metrics.total_return = (metrics.final_value / init_cash) - 1.0;
204
205 const double min_period_years = 30.0 / 365.0;
206 if (years >= min_period_years) {
207 metrics.annual_return =
208 std::pow(metrics.final_value / init_cash, 1.0 / years) - 1.0;
209 metrics.has_annual_return = true;
210 }
211
212 metrics.valid = true;
213 return metrics;
214}
215
216inline void PortfolioStats::print_summary(std::ostream& os, double curr_cash,
217 double holdings, double qty,
218 double total_value) const {
219 double profit_loss = total_value - init_cash;
220
221 os << std::fixed << std::setprecision(4) << "init_time:" << init_timestamp
222 << " curr_time:" << last_timestamp << " init_cash:" << init_cash
223 << " curr_cash:" << curr_cash << " num_trades:" << num_trades
224 << " num_closed:" << (get_num_wins() + get_num_losses())
225 << " num_wins:" << get_num_wins() << " num_losses:" << get_num_losses()
226 << " win_rate:" << get_win_rate() << " num_stop_loss:" << num_stop_loss
227 << " num_take_profit:" << num_take_profit << " quantity:" << qty
228 << " holdings:" << holdings << " valuation:" << total_value
229 << " total_costs:" << total_costs << " profit:" << profit_loss;
230
232 double adjusted_total_return = (total_value / init_cash) - 1.0;
233 double adjusted_annual_return = 0.0;
234 bool has_adjusted_annual_return = false;
235
236 const double min_period_years = 30.0 / 365.0;
237 if (perf.years >= min_period_years) {
238 adjusted_annual_return =
239 std::pow(total_value / init_cash, 1.0 / perf.years) - 1.0;
240 has_adjusted_annual_return = true;
241 }
242
243 os << " total_return:" << adjusted_total_return;
244
245 if (has_adjusted_annual_return) {
246 os << " annual_return:" << adjusted_annual_return;
247 } else {
248 os << " annual_return:N/A";
249 }
250
251 BuyAndHoldMetrics bh = compute_buy_and_hold_metrics(init_cash, init_price,
252 last_price, perf.years);
253
254 if (bh.valid) {
255 os << " buy_and_hold_return:" << bh.total_return;
256 if (bh.has_annual_return) {
257 os << " buy_and_hold_annual:" << bh.annual_return;
258 }
259 }
260
261 os << " max_drawdown:" << perf.max_drawdown
262 << " sharpe:" << perf.sharpe_ratio;
263}
264
265} // namespace tzu
266
267#endif // STATS_H
Definition stats.h:12
bool is_initialized() const
Definition stats.h:36
void record_trade_close(int64_t timestamp, double quantity, double open_price, double close_price, double profit, bool is_stop_loss, bool is_take_profit)
Definition stats.h:48
void add_costs(double cost)
Definition stats.h:60
uint16_t get_num_wins() const
Definition stats.h:62
void initialize(int64_t timestamp, double cash, double price)
Definition stats.h:27
void record_equity(int64_t timestamp, double total_value, double price)
Definition stats.h:38
void record_trade_open(int64_t timestamp, double quantity, double price)
Definition stats.h:44
void increment_trades()
Definition stats.h:58
uint16_t get_num_losses() const
Definition stats.h:70
void print_summary(std::ostream &os, double curr_cash, double holdings, double qty, double total_value) const
Definition stats.h:216
double get_win_rate() const
Definition stats.h:78
Definition defs.h:20
BuyAndHoldMetrics compute_buy_and_hold_metrics(double init_cash, double init_price, double final_price, double years)
Definition stats.h:189
PerformanceMetrics compute_performance_metrics(const std::vector< std::pair< int64_t, double > > &equity_curve)
Definition stats.h:150
std::vector< double > compute_returns(const std::vector< std::pair< int64_t, double > > &equity_curve)
Definition stats.h:134
double compute_max_drawdown(const std::vector< std::pair< int64_t, double > > &equity_curve)
Definition stats.h:92
double compute_sharpe_ratio(const std::vector< double > &returns, double years)
Definition stats.h:111
Definition defs.h:199
double final_value
Definition defs.h:202
double cash_left
Definition defs.h:201
double annual_return
Definition defs.h:204
bool has_annual_return
Definition defs.h:205
double total_return
Definition defs.h:203
bool valid
Definition defs.h:206
double quantity
Definition defs.h:200
Definition defs.h:187
double annual_return
Definition defs.h:189
double years
Definition defs.h:192
double total_return
Definition defs.h:188
double sharpe_ratio
Definition defs.h:191
double max_drawdown
Definition defs.h:190
bool has_annual_return
Definition defs.h:193
Definition defs.h:168