WU Trading Library 0.2.0
A backtesting and trading strategy library
Loading...
Searching...
No Matches
stats.c
Go to the documentation of this file.
1#include <stdlib.h>
2#include <string.h>
3#include <stdio.h>
4#include <time.h>
5#include "wu.h"
6
7static char* wu_strdup(const char* s) {
8 if (!s) return NULL;
9 size_t len = strlen(s) + 1;
10 char* copy = malloc(len);
11 if (copy) memcpy(copy, s, len);
12 return copy;
13}
14
15static void stats_update(struct WU_PortfolioStats_* stats, double cash,
16 double portfolio_value, WU_TimeStamp timestamp) {
17 if (!stats) return;
18 stats->current_cash = cash;
19 stats->portfolio_value = portfolio_value;
20 stats->last_update = timestamp;
21 // Create performance update struct for time-aware metrics
23 .portfolio_value = portfolio_value,
24 .timestamp = timestamp
25 };
26 // Update performance indicators
27 wu_indicator_update(stats->max_drawdown, portfolio_value);
31}
32
33static void stats_record_trade(struct WU_PortfolioStats_* stats, double pnl,
34 WU_CloseReason reason) {
35 if (!stats) return;
36 stats->total_trades++;
37 stats->accum_pnl += pnl;
38 // Update PnL statistics
39 // if (stats->pnl_stats)
40 // stats->pnl_stats->update(stats->pnl_stats, pnl);
41 if (stats->mean) wu_indicator_update(stats->mean, pnl);
42 if (stats->stdev) wu_indicator_update(stats->stdev, pnl);
43 if (pnl > 0) {
44 stats->winning_trades++;
45 stats->total_profit += pnl;
46 if (pnl > stats->max_win)
47 stats->max_win = pnl;
48 } else if (pnl < 0) {
49 stats->losing_trades++;
50 stats->total_loss += pnl;
51 if (pnl < stats->max_loss)
52 stats->max_loss = pnl;
53 }
54 if (reason == WU_CLOSE_REASON_STOP_LOSS)
55 stats->stop_loss_exits++;
56 else if (reason == WU_CLOSE_REASON_TAKE_PROFIT)
57 stats->take_profit_exits++;
58}
59
60static void stats_update_position(struct WU_PortfolioStats_* stats, int asset_index,
61 const char* symbol, double quantity, double value, double last_price) {
62 if (!stats) return;
63 if (asset_index < 0) return;
64 // Expand capacity if needed
65 if (asset_index >= stats->capacity) {
66 int new_capacity = asset_index + 1;
67 stats->symbols = realloc(stats->symbols, new_capacity * sizeof(char*));
68 stats->quantities = realloc(stats->quantities, new_capacity * sizeof(double));
69 stats->values = realloc(stats->values, new_capacity * sizeof(double));
70 stats->last_prices = realloc(stats->last_prices, new_capacity * sizeof(double));
71 for (int i = stats->capacity; i < new_capacity; i++) {
72 stats->symbols[i] = NULL;
73 stats->quantities[i] = 0.0;
74 stats->values[i] = 0.0;
75 stats->last_prices[i] = 0.0;
76 }
77 stats->capacity = new_capacity;
78 }
79 if (asset_index >= stats->num_assets)
80 stats->num_assets = asset_index + 1;
81 // Update or set symbol
82 if (symbol && (!stats->symbols[asset_index] ||
83 strcmp(stats->symbols[asset_index], symbol) != 0)) {
84 free(stats->symbols[asset_index]);
85 stats->symbols[asset_index] = wu_strdup(symbol);
86 }
87 stats->quantities[asset_index] = quantity;
88 stats->values[asset_index] = value;
89 stats->last_prices[asset_index] = last_price;
90}
91
92static char* stats_to_keyvalue(struct WU_PortfolioStats_* stats) {
93 if (!stats) return NULL;
94 double pnl = stats->portfolio_value - stats->initial_cash;
95 double pnl_pct = (pnl / stats->initial_cash) * 100.0;
96 double win_rate = stats->total_trades > 0 ?
97 (stats->winning_trades * 100.0) / stats->total_trades : 0.0;
98 double avg_pnl = stats->total_trades > 0 ? stats->accum_pnl / stats->total_trades : 0.0;
99 // double pnl_mean = stats->mean ? wu_indicator_get(stats->mean) : NAN;
100 double pnl_stdev = stats->stdev ? wu_indicator_get(stats->stdev) : NAN;
101 char* result = malloc(8192);
102 if (!result) return NULL;
103 int offset = snprintf(result, 8192,
104 "initial_cash=%.2f current_cash=%.2f portfolio_value=%.2f "
105 "pnl=%.2f pnl_pct=%.2f tx_fees=%.2f borrow_interest=%.2f "
106 "total_trades=%ld winning_trades=%ld losing_trades=%ld "
107 "win_rate=%.2f max_win=%.2f max_loss=%.2f avg_pnl=%.2f pnl_stdev=%.2f "
108 "stop_loss_exits=%ld take_profit_exits=%ld",
109 stats->initial_cash, stats->current_cash, stats->portfolio_value,
110 pnl, pnl_pct, stats->accum_tx_fees, stats->accum_borrow_interest,
111 stats->total_trades, stats->winning_trades, stats->losing_trades,
112 win_rate, stats->max_win, stats->max_loss, avg_pnl,
113 isnan(pnl_stdev) ? 0.0 : pnl_stdev,
114 stats->stop_loss_exits, stats->take_profit_exits);
115 // Add performance metrics
116 if (stats->max_drawdown) {
117 double mdd = wu_indicator_get(stats->max_drawdown);
118 offset += snprintf(result + offset, 8192 - offset,
119 " max_drawdown=%.4f", mdd);
120 }
121 if (stats->sharpe_ratio) {
122 double sharpe = wu_indicator_get(stats->sharpe_ratio);
123 offset += snprintf(result + offset, 8192 - offset,
124 " sharpe_ratio=%.4f", isnan(sharpe) ? 0.0 : sharpe);
125 }
126 if (stats->sortino_ratio) {
127 double sortino = wu_indicator_get(stats->sortino_ratio);
128 offset += snprintf(result + offset, 8192 - offset,
129 " sortino_ratio=%.4f", isnan(sortino) ? 0.0 : sortino);
130 }
131 if (stats->calmar_ratio) {
132 double calmar = wu_indicator_get(stats->calmar_ratio);
133 offset += snprintf(result + offset, 8192 - offset,
134 " calmar_ratio=%.4f", isnan(calmar) ? 0.0 : calmar);
135 }
136 // Add position data
137 for (int i = 0; i < stats->num_assets; i++) {
138 if (stats->symbols[i]) {
139 offset += snprintf(result + offset, 8192 - offset,
140 " %s_quantity=%.4f %s_value=%.2f %s_price=%.2f",
141 stats->symbols[i], stats->quantities[i],
142 stats->symbols[i], stats->values[i],
143 stats->symbols[i], stats->last_prices[i]);
144 }
145 }
146 return result;
147}
148
149static char* stats_to_json(struct WU_PortfolioStats_* stats, bool pretty) {
150 if (!stats) return NULL;
151 const char* nl = pretty ? "\n" : "";
152 const char* indent1 = pretty ? " " : "";
153 const char* indent2 = pretty ? " " : "";
154 const char* space = pretty ? " " : "";
155 double pnl = stats->portfolio_value - stats->initial_cash;
156 double pnl_pct = (pnl / stats->initial_cash) * 100.0;
157 double win_rate = stats->total_trades > 0 ?
158 (stats->winning_trades * 100.0) / stats->total_trades : 0.0;
159 double avg_pnl = stats->total_trades > 0 ? stats->accum_pnl / stats->total_trades : 0.0;
160 // double pnl_mean = stats->mean ? wu_indicator_get(stats->mean) : NAN;
161 double pnl_stdev = stats->stdev ? wu_indicator_get(stats->stdev) : NAN;
162 char* result = malloc(16384);
163 if (!result) return NULL;
164 int offset = snprintf(result, 16384,
165 "{%s"
166 "%s\"portfolio\":%s{%s"
167 "%s\"initial_cash\":%s%.2f,%s"
168 "%s\"current_cash\":%s%.2f,%s"
169 "%s\"portfolio_value\":%s%.2f,%s"
170 "%s\"pnl\":%s%.2f,%s"
171 "%s\"pnl_pct\":%s%.2f,%s"
172 "%s\"tx_fees\":%s%.2f,%s"
173 "%s\"borrow_interest\":%s%.2f%s"
174 "%s},%s"
175 "%s\"trades\":%s{%s"
176 "%s\"total\":%s%ld,%s"
177 "%s\"winning\":%s%ld,%s"
178 "%s\"losing\":%s%ld,%s"
179 "%s\"win_rate\":%s%.2f,%s"
180 "%s\"max_win\":%s%.2f,%s"
181 "%s\"max_loss\":%s%.2f,%s"
182 "%s\"avg_pnl\":%s%.2f,%s"
183 "%s\"pnl_stdev\":%s%.2f,%s"
184 "%s\"stop_loss_exits\":%s%ld,%s"
185 "%s\"take_profit_exits\":%s%ld%s"
186 "%s},%s"
187 "%s\"performance\":%s{%s"
188 "%s\"max_drawdown\":%s%.4f,%s"
189 "%s\"sharpe_ratio\":%s%.4f,%s"
190 "%s\"sortino_ratio\":%s%.4f,%s"
191 "%s\"calmar_ratio\":%s%.4f%s"
192 "%s}",
193 nl,
194 indent1, space, nl,
195 indent2, space, stats->initial_cash, nl,
196 indent2, space, stats->current_cash, nl,
197 indent2, space, stats->portfolio_value, nl,
198 indent2, space, pnl, nl,
199 indent2, space, pnl_pct, nl,
200 indent2, space, stats->accum_tx_fees, nl,
201 indent2, space, stats->accum_borrow_interest, nl,
202 indent1, nl,
203 indent1, space, nl,
204 indent2, space, stats->total_trades, nl,
205 indent2, space, stats->winning_trades, nl,
206 indent2, space, stats->losing_trades, nl,
207 indent2, space, win_rate, nl,
208 indent2, space, stats->max_win, nl,
209 indent2, space, stats->max_loss, nl,
210 indent2, space, avg_pnl, nl,
211 indent2, space, pnl_stdev, nl,
212 indent2, space, stats->stop_loss_exits, nl,
213 indent2, space, stats->take_profit_exits, nl,
214 indent1, nl,
215 indent1, space, nl,
216 indent2, space, (stats->max_drawdown
217 ? wu_indicator_get(stats->max_drawdown) : 0.0), nl,
218 indent2, space, (stats->sharpe_ratio
219 ? (isnan(wu_indicator_get(stats->sharpe_ratio)) ? 0.0
220 : wu_indicator_get(stats->sharpe_ratio)) : 0.0), nl,
221 indent2, space, (stats->sortino_ratio ? (isnan(
222 wu_indicator_get(stats->sortino_ratio)) ? 0.0
223 : wu_indicator_get(stats->sortino_ratio)) : 0.0), nl,
224 indent2, space, (stats->calmar_ratio
225 ? (isnan(wu_indicator_get(stats->calmar_ratio)) ? 0.0
226 : wu_indicator_get(stats->calmar_ratio)) : 0.0), nl,
227 indent1);
228 // Add positions array
229 if (stats->num_assets > 0) {
230 offset += snprintf(result + offset, 16384 - offset,
231 ",%s%s\"positions\":%s[", nl, indent1, space);
232 for (int i = 0; i < stats->num_assets; i++) {
233 if (stats->symbols[i]) {
234 if (i > 0) offset += snprintf(result + offset, 16384 - offset, ",");
235 offset += snprintf(result + offset, 16384 - offset,
236 "%s%s{%s\"symbol\": \"%s\",%s\"quantity\": %.4f,%s\"value\": %.2f,%s\"last_price\": %.2f%s}",
237 nl, indent2,
238 space, stats->symbols[i],
239 space, stats->quantities[i],
240 space, stats->values[i],
241 space, stats->last_prices[i],
242 nl);
243 }
244 }
245 offset += snprintf(result + offset, 16384 - offset,
246 "%s%s]", nl, indent1);
247 }
248 offset += snprintf(result + offset, 16384 - offset, "%s}", nl);
249 return result;
250}
251
252static void stats_reset(struct WU_PortfolioStats_* stats) {
253 if (!stats) return;
254 stats->current_cash = stats->initial_cash;
255 stats->portfolio_value = stats->initial_cash;
256 stats->last_update = (WU_TimeStamp){.mark = 0, .units = WU_TIME_UNIT_SECONDS};
257 stats->accum_tx_fees = 0.0;
258 stats->accum_borrow_interest = 0.0;
259 stats->total_trades = 0;
260 stats->winning_trades = 0;
261 stats->losing_trades = 0;
262 stats->stop_loss_exits = 0;
263 stats->take_profit_exits = 0;
264 stats->total_profit = 0.0;
265 stats->total_loss = 0.0;
266 stats->max_win = 0.0;
267 stats->max_loss = 0.0;
268 stats->accum_pnl = 0.0;
269 for (int i = 0; i < stats->num_assets; i++) {
270 stats->quantities[i] = 0.0;
271 stats->values[i] = 0.0;
272 stats->last_prices[i] = 0.0;
273 }
274}
275
276static void stats_free(struct WU_PortfolioStats_* stats) {
277 if (!stats) return;
278 for (int i = 0; i < stats->capacity; i++) {
279 free(stats->symbols[i]);
280 }
281 free(stats->symbols);
282 free(stats->quantities);
283 free(stats->values);
284 free(stats->last_prices);
285 // Free performance indicators
292 free(stats);
293}
294
295WU_PortfolioStats wu_portfolio_stats_new(double initial_cash, double risk_free_rate) {
296 WU_PortfolioStats stats = malloc(sizeof(struct WU_PortfolioStats_));
297 if (!stats) return NULL;
298 stats->update = stats_update;
299 stats->record_trade = stats_record_trade;
300 stats->update_position = stats_update_position;
301 stats->to_keyvalue = stats_to_keyvalue;
302 stats->to_json = stats_to_json;
303 stats->reset = stats_reset;
304 stats->delete = stats_free;
305 stats->initial_cash = initial_cash;
306 stats->current_cash = initial_cash;
307 stats->portfolio_value = initial_cash;
308 stats->last_update = (WU_TimeStamp){.mark = 0, .units = WU_TIME_UNIT_SECONDS};
309 stats->accum_tx_fees = 0.0;
310 stats->accum_borrow_interest = 0.0;
311 stats->total_trades = 0;
312 stats->winning_trades = 0;
313 stats->losing_trades = 0;
314 stats->stop_loss_exits = 0;
315 stats->take_profit_exits = 0;
316 stats->total_profit = 0.0;
317 stats->total_loss = 0.0;
318 stats->max_win = 0.0;
319 stats->max_loss = 0.0;
320 stats->accum_pnl = 0.0;
321 stats->symbols = NULL;
322 stats->quantities = NULL;
323 stats->values = NULL;
324 stats->last_prices = NULL;
325 stats->num_assets = 0;
326 stats->capacity = 0;
327 // TODO: clean-up in case of getting any NULL
328 stats->max_drawdown = wu_max_drawdown_new();
329 stats->sharpe_ratio = wu_sharpe_ratio_new(initial_cash, risk_free_rate);
330 stats->sortino_ratio = wu_sortino_ratio_new(initial_cash, risk_free_rate);
331 stats->calmar_ratio = wu_calmar_ratio_new(initial_cash);
332 // stats->pnl_stats = wu_pnl_stats_new();
333 stats->mean = wu_mean_new();
334 stats->stdev = wu_stdev_new(1);
335 return stats;
336}
#define wu_indicator_get(indicator)
Get the current value of the indicator.
Definition indicators.h:49
#define wu_indicator_delete(indicator)
Delete the indicator and free any resources allocated by it.
Definition indicators.h:56
#define wu_indicator_update(indicator, value)
Header file for technical indicators.
Definition indicators.h:41
WU_StDev wu_stdev_new(int dof)
Creates a new standard deviation indicator.
Definition var.c:49
WU_Mean wu_mean_new(void)
Creates a new WU_Mean indicator.
Definition mean.c:15
WU_MaxDrawdown wu_max_drawdown_new(void)
Creates a new WU_MaxDrawdown indicator.
Definition maxdrawdown.c:20
static char * stats_to_json(struct WU_PortfolioStats_ *stats, bool pretty)
Definition stats.c:149
static char * stats_to_keyvalue(struct WU_PortfolioStats_ *stats)
Definition stats.c:92
static void stats_free(struct WU_PortfolioStats_ *stats)
Definition stats.c:276
static void stats_update_position(struct WU_PortfolioStats_ *stats, int asset_index, const char *symbol, double quantity, double value, double last_price)
Definition stats.c:60
WU_PortfolioStats wu_portfolio_stats_new(double initial_cash, double risk_free_rate)
Creates a new WU_PortfolioStats instance.
Definition stats.c:295
static void stats_reset(struct WU_PortfolioStats_ *stats)
Definition stats.c:252
static void stats_update(struct WU_PortfolioStats_ *stats, double cash, double portfolio_value, WU_TimeStamp timestamp)
Definition stats.c:15
static char * wu_strdup(const char *s)
Definition stats.c:7
static void stats_record_trade(struct WU_PortfolioStats_ *stats, double pnl, WU_CloseReason reason)
Definition stats.c:33
WU_CalmarRatio wu_calmar_ratio_new(double initial_value)
Definition calmar.c:112
WU_SharpeRatio wu_sharpe_ratio_new(double initial_value, double risk_free_rate)
Creates a new Sharpe Ratio calculator.
Definition sharpe.c:106
WU_SortinoRatio wu_sortino_ratio_new(double initial_value, double risk_free_rate)
Creates a new Sortino Ratio calculator.
Definition sortino.c:105
Performance Update - value and timestamp for performance metric calculations.
Definition indicators.h:239
WU_PortfolioStats tracks portfolio state, positions, and trading statistics.
Definition stats.h:68
int64_t losing_trades
Definition stats.h:93
double total_loss
Definition stats.h:97
double * quantities
Definition stats.h:104
double * values
Definition stats.h:105
double initial_cash
Definition stats.h:81
WU_StDev stdev
Definition stats.h:116
double total_profit
Definition stats.h:96
double accum_pnl
Definition stats.h:100
int64_t take_profit_exits
Definition stats.h:95
int64_t stop_loss_exits
Definition stats.h:94
WU_SortinoRatio sortino_ratio
Definition stats.h:113
int64_t winning_trades
Definition stats.h:92
WU_TimeStamp last_update
Definition stats.h:84
double portfolio_value
Definition stats.h:83
double current_cash
Definition stats.h:82
WU_MaxDrawdown max_drawdown
Definition stats.h:111
WU_SharpeRatio sharpe_ratio
Definition stats.h:112
char ** symbols
Definition stats.h:103
double * last_prices
Definition stats.h:106
int64_t total_trades
Definition stats.h:91
WU_CalmarRatio calmar_ratio
Definition stats.h:114
double max_loss
Definition stats.h:99
double accum_borrow_interest
Definition stats.h:88
double accum_tx_fees
Definition stats.h:87
double max_win
Definition stats.h:98
A timestamp represent a mark in time given relative to unix epoch.
Definition timeutils.h:21
@ WU_TIME_UNIT_SECONDS
Definition timeutils.h:11
WU_CloseReason
WU_CloseReason represents the reason for closing a position, which can be a trading signal,...
Definition types.h:53
@ WU_CLOSE_REASON_TAKE_PROFIT
Definition types.h:56
@ WU_CLOSE_REASON_STOP_LOSS
Definition types.h:55