Skip to content

Commit 46da1da

Browse files
committed
Add detailed backtesting data structures and metrics implementation
Introduced several structs and modules to support comprehensive backtesting analysis, including trade statistics, drawdown analysis, capital utilization, and performance metrics. Added serialization support and detailed documentation for each component, enabling robust strategy evaluation and reporting.
1 parent a7fd41e commit 46da1da

5 files changed

Lines changed: 968 additions & 6 deletions

File tree

src/backtesting/metrics.rs

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/******************************************************************************
2+
Author: Joaquín Béjar García
3+
Email: jb@taunais.com
4+
Date: 26/4/25
5+
******************************************************************************/
6+
use crate::Positive;
7+
use chrono::NaiveDateTime;
8+
use rust_decimal::Decimal;
9+
use serde::{Deserialize, Serialize};
10+
11+
/// Represents core performance metrics applicable to various trading strategies.
12+
///
13+
/// This struct aggregates common financial metrics used to evaluate the
14+
/// effectiveness and risk profile of a trading strategy based on its historical
15+
/// or simulated performance.
16+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
17+
pub struct GeneralPerformanceMetrics {
18+
// === Return Metrics ===
19+
/// The total percentage return generated by the strategy over the entire period.
20+
pub total_return: Decimal,
21+
/// The geometric average amount of money earned by an investment each year over a given time period.
22+
pub annualized_return: Decimal,
23+
24+
// === Risk Metrics ===
25+
/// A statistical measure of the dispersion of returns for a given security or market index.
26+
/// It represents the degree of variation of a trading price series over time.
27+
/// Expressed as a positive decimal value. `None` if calculation is not possible (e.g., insufficient data).
28+
pub volatility: Option<Positive>,
29+
/// A measure of downside risk, focusing only on returns that fall below a minimum acceptable return (MAR),
30+
/// typically the risk-free rate or zero. It quantifies the volatility of negative returns.
31+
/// Expressed as a positive decimal value. `None` if calculation is not possible.
32+
pub downside_deviation: Option<Positive>,
33+
34+
// === Risk-Adjusted Return Metrics ===
35+
/// Measures the performance of an investment compared to a risk-free asset, after adjusting for its risk.
36+
/// It is defined as the difference between the returns of the investment and the risk-free return,
37+
/// divided by the standard deviation of the investment (volatility).
38+
/// `None` if calculation is not possible (e.g., zero volatility or insufficient data).
39+
pub sharpe_ratio: Option<Decimal>,
40+
/// Similar to the Sharpe ratio, but it only penalizes returns falling below a user-specified target
41+
/// or required rate of return (using downside deviation instead of total volatility).
42+
/// `None` if calculation is not possible (e.g., zero downside deviation or insufficient data).
43+
pub sortino_ratio: Option<Decimal>,
44+
/// A risk-adjusted return metric based on maximum drawdown. It is calculated as the annualized return
45+
/// divided by the absolute value of the maximum drawdown.
46+
/// `None` if calculation is not possible (e.g., zero or positive maximum drawdown, insufficient data).
47+
pub calmar_ratio: Option<Decimal>,
48+
49+
// === Win/Loss Metrics ===
50+
/// The percentage of trades that resulted in a profit. Calculated as (Number of Winning Trades / Total Number of Trades).
51+
/// `None` if there are no trades.
52+
pub win_rate: Option<Decimal>,
53+
/// The ratio of the total profit from winning trades to the total loss from losing trades.
54+
/// Calculated as (Gross Profits / Gross Losses).
55+
/// `None` if there are no losses.
56+
pub profit_factor: Option<Decimal>,
57+
/// The average profit amount for all winning trades.
58+
/// `None` if there are no winning trades.
59+
pub avg_gain: Option<Decimal>,
60+
/// The average loss amount for all losing trades.
61+
/// `None` if there are no losing trades.
62+
pub avg_loss: Option<Decimal>,
63+
/// The ratio of the average gain per trade to the average loss per trade.
64+
/// Calculated as (Average Gain / Average Loss).
65+
/// `None` if there are no losses or no gains.
66+
pub gain_loss_ratio: Option<Decimal>,
67+
}
68+
69+
/// Represents metrics specifically tailored for evaluating options trading strategies.
70+
///
71+
/// This struct encapsulates various performance and risk exposure metrics
72+
/// that are particularly relevant when analyzing strategies involving options contracts.
73+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
74+
pub struct OptionsSpecificMetrics {
75+
/// The return generated based on the margin required to maintain the positions.
76+
/// Calculated as `Total PnL / Maximum Margin Used`.
77+
pub return_on_margin: Option<Decimal>,
78+
/// The return generated based on the net premium received or paid for the options.
79+
/// Calculated as `Total PnL / Net Premium`.
80+
pub return_on_premium: Option<Decimal>,
81+
/// The percentage of the initial potential profit (net premium for short strategies)
82+
/// that was actually realized.
83+
/// Calculated as `Actual PnL / Initial Potential Profit`. Relevant primarily for premium selling strategies.
84+
pub premium_capture: Option<Decimal>,
85+
/// The average sensitivity of the portfolio's value to changes in the underlying asset's price (Delta).
86+
pub avg_delta_exposure: Option<Decimal>,
87+
/// The average rate of change of the portfolio's Delta with respect to the underlying asset's price (Gamma).
88+
pub avg_gamma_exposure: Option<Decimal>,
89+
/// The average sensitivity of the portfolio's value to the passage of time (Theta).
90+
/// Represents the daily time decay.
91+
pub avg_theta_exposure: Option<Decimal>,
92+
/// The average sensitivity of the portfolio's value to changes in the implied volatility of the underlying asset (Vega).
93+
pub avg_vega_exposure: Option<Decimal>,
94+
/// The percentage of trades or positions involving call options.
95+
pub calls_percentage: Option<Decimal>,
96+
/// The percentage of trades or positions involving put options.
97+
pub puts_percentage: Option<Decimal>,
98+
/// The percentage of trades or positions that are long options (bought).
99+
pub long_percentage: Option<Decimal>,
100+
/// The percentage of trades or positions that are short options (sold/written).
101+
pub short_percentage: Option<Decimal>,
102+
}
103+
104+
/// Represents metrics related to market conditions observed during a specific period, typically a backtest.
105+
///
106+
/// This struct captures counts of days categorized by different market trends (bull, bear, sideways)
107+
/// and volatility levels (high, low). It also stores the average market volatility over the period.
108+
/// The counts are represented using the `Positive` type, ensuring they are non-negative decimal values.
109+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
110+
pub struct MarketConditionMetrics {
111+
/// The number of days identified as being in a bull market trend.
112+
pub bull_market_days: Positive,
113+
/// The number of days identified as being in a bear market trend.
114+
pub bear_market_days: Positive,
115+
/// The number of days identified as being in a sideways or range-bound market trend.
116+
pub sideways_market_days: Positive,
117+
/// The number of days identified as having high market volatility.
118+
pub high_volatility_days: Positive,
119+
/// The number of days identified as having low market volatility.
120+
pub low_volatility_days: Positive,
121+
/// The calculated average market volatility over the period. This might be `None` if volatility couldn't be calculated.
122+
pub avg_market_volatility: Option<Decimal>,
123+
}
124+
125+
/// Represents rolling window metrics calculated over a specific period.
126+
///
127+
/// This struct holds various performance metrics calculated using a sliding window approach,
128+
/// allowing for the analysis of how a strategy's performance characteristics change over time.
129+
#[derive(Debug, Clone, Serialize, Deserialize)]
130+
pub struct RollingMetrics {
131+
/// The size of the rolling window used for calculations.
132+
/// This indicates the number of data points included in each rolling calculation.
133+
/// It must be a positive value.
134+
pub window_size: Positive,
135+
/// A vector of timestamps corresponding to the end of each rolling window period.
136+
pub timestamps: Vec<NaiveDateTime>,
137+
/// A vector of rolling returns calculated for each window.
138+
pub rolling_returns: Vec<Decimal>,
139+
/// A vector of rolling volatility (standard deviation of returns) calculated for each window.
140+
pub rolling_volatility: Vec<Decimal>,
141+
/// A vector of rolling Sharpe ratios calculated for each window.
142+
/// The Sharpe ratio measures risk-adjusted return, typically using volatility as the risk measure.
143+
pub rolling_sharpe: Vec<Decimal>,
144+
/// A vector of rolling Sortino ratios calculated for each window.
145+
/// The Sortino ratio is similar to the Sharpe ratio but only considers downside volatility.
146+
pub rolling_sortino: Vec<Decimal>,
147+
/// A vector of rolling win rates calculated for each window.
148+
/// The win rate represents the percentage of winning trades or periods within the window.
149+
pub rolling_win_rate: Vec<Decimal>,
150+
}
151+
152+
/// Represents the results of comparing a portfolio's performance against a specified benchmark.
153+
///
154+
/// This struct holds various statistical metrics commonly used in finance to evaluate
155+
/// how well a portfolio has performed relative to a market benchmark index.
156+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
157+
pub struct BenchmarkComparison {
158+
/// The name or identifier of the benchmark used for comparison (e.g., "S&P 500").
159+
pub benchmark_name: String,
160+
/// The total return of the benchmark over the evaluated period.
161+
pub benchmark_return: Decimal,
162+
/// Alpha represents the excess return of the portfolio relative to the return of the benchmark,
163+
/// after adjusting for risk (as measured by beta). A positive alpha suggests the portfolio
164+
/// outperformed the benchmark on a risk-adjusted basis.
165+
pub alpha: Decimal,
166+
/// Beta measures the volatility or systematic risk of the portfolio in comparison to the benchmark.
167+
/// A beta greater than 1 indicates the portfolio is more volatile than the benchmark, while a beta
168+
/// less than 1 indicates less volatility.
169+
pub beta: Decimal,
170+
/// Correlation measures the degree to which the portfolio's returns move in relation to the benchmark's returns.
171+
/// A value close to 1 indicates a strong positive relationship, -1 a strong negative relationship,
172+
/// and 0 indicates no linear relationship.
173+
pub correlation: Decimal,
174+
/// Tracking Error measures the standard deviation of the difference between the portfolio's returns
175+
/// and the benchmark's returns. It quantifies how closely the portfolio's performance follows the benchmark.
176+
/// A lower tracking error indicates the portfolio is closely tracking the benchmark.
177+
pub tracking_error: Decimal,
178+
/// The Information Ratio measures the portfolio's risk-adjusted returns relative to the benchmark.
179+
/// It is typically calculated as the portfolio's alpha divided by its tracking error.
180+
/// A higher information ratio suggests better risk-adjusted performance relative to the benchmark.
181+
/// This is optional as it cannot be calculated if the tracking error is zero.
182+
pub information_ratio: Option<Decimal>,
183+
/// Up Capture Ratio measures the portfolio's performance relative to the benchmark during periods
184+
/// when the benchmark had positive returns. A value greater than 100 suggests the portfolio
185+
/// outperformed the benchmark during up-market periods.
186+
pub up_capture: Option<Decimal>,
187+
/// Down Capture Ratio measures the portfolio's performance relative to the benchmark during periods
188+
/// when the benchmark had negative returns. A value less than 100 suggests the portfolio
189+
/// lost less than the benchmark during down-market periods.
190+
pub down_capture: Option<Decimal>,
191+
}
192+
193+
/// Represents a collection of advanced risk metrics for assessing investment performance,
194+
/// focusing on downside risk and tail events beyond simple standard deviation.
195+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
196+
pub struct AdvancedRiskMetrics {
197+
/// Value at Risk (VaR) at the 95% confidence level.
198+
/// Represents the maximum potential loss over a specific time period
199+
/// with 95% confidence. `None` if not calculated.
200+
pub value_at_risk_95: Option<Decimal>,
201+
/// Value at Risk (VaR) at the 99% confidence level.
202+
/// Represents the maximum potential loss over a specific time period
203+
/// with 99% confidence. `None` if not calculated.
204+
pub value_at_risk_99: Option<Decimal>,
205+
/// Expected Shortfall (ES), also known as Conditional Value at Risk (CVaR).
206+
/// Represents the expected loss given that the loss exceeds the VaR threshold
207+
/// (typically calculated at the 95% or 99% level). `None` if not calculated.
208+
pub expected_shortfall: Option<Decimal>,
209+
/// The ratio of the 95th percentile gain to the absolute value of the 5th percentile loss.
210+
/// Provides insight into the symmetry of the return distribution's tails. `None` if not calculated.
211+
pub tail_ratio: Option<Decimal>,
212+
/// The maximum number of consecutive periods (e.g., days, weeks) with negative returns.
213+
pub max_consecutive_losses: usize,
214+
/// The Ulcer Index, measuring the depth and duration of drawdowns from peak values.
215+
/// Higher values indicate more significant and prolonged drawdowns. `None` if not calculated.
216+
pub ulcer_index: Option<Decimal>,
217+
/// The Pain Index, similar to the Ulcer Index, measuring the average depth and duration
218+
/// of drawdowns. `None` if not calculated.
219+
pub pain_index: Option<Decimal>,
220+
}

0 commit comments

Comments
 (0)