@@ -31,6 +31,9 @@ class BacktestSummaryMetrics:
3131 average_growth (float): Average growth across multiple backtests.
3232 average_growth_percentage (float): Average growth percentage across
3333 multiple backtests.
34+ average_loss (float): Average loss across multiple backtests.
35+ average_loss_percentage (float): Average loss percentage across
36+ multiple backtests.
3437 average_trade_return (float): Average return per trade.
3538 average_trade_return_percentage (float): Average return percentage
3639 per trade.
@@ -63,13 +66,14 @@ class BacktestSummaryMetrics:
6366 average_net_gain_percentage : float = None
6467 average_growth : float = None
6568 average_growth_percentage : float = None
69+ average_loss : float = None
70+ average_loss_percentage : float = None
6671 average_trade_return : float = None
6772 average_trade_return_percentage : float = None
6873 average_trade_loss : float = None
6974 average_trade_loss_percentage : float = None
7075 average_trade_gain : float = None
7176 average_trade_gain_percentage : float = None
72- trades_average_return : float = None
7377 cagr : float = None
7478 sharpe_ratio : float = None
7579 sortino_ratio : float = None
@@ -96,6 +100,8 @@ def to_dict(self) -> dict:
96100 "total_growth_percentage" : self .total_growth_percentage ,
97101 "total_loss" : self .total_loss ,
98102 "total_loss_percentage" : self .total_loss_percentage ,
103+ "average_loss" : self .average_loss ,
104+ "average_loss_percentage" : self .average_loss_percentage ,
99105 "average_net_gain" : self .average_net_gain ,
100106 "average_net_gain_percentage" : self .average_net_gain_percentage ,
101107 "average_growth" : self .average_growth ,
@@ -145,215 +151,6 @@ def open(file_path: str | Path) -> 'BacktestSummaryMetrics':
145151
146152 return BacktestSummaryMetrics (** data )
147153
148- def add (self , other : BacktestMetrics ) -> None :
149- """
150- Update this summary with another BacktestMetrics.
151- Averages ratios, sums trade counts, and takes worst drawdowns.
152- """
153- def safe_mean (a , b ):
154- vals = [v for v in [a , b ] if v is not None ]
155- return mean (vals ) if vals else 0.0
156-
157- if self .cumulative_exposure is None :
158- self .cumulative_exposure = other .cumulative_exposure
159- else :
160- self .cumulative_exposure = safe_mean (
161- self .cumulative_exposure ,
162- other .cumulative_exposure
163- )
164-
165- if self .exposure_ratio is None :
166- self .exposure_ratio = other .exposure_ratio
167- else :
168- self .exposure_ratio = safe_mean (
169- self .exposure_ratio , other .exposure_ratio
170- )
171-
172- if self .total_net_gain is None :
173- self .total_net_gain = other .total_net_gain
174- else :
175- self .total_net_gain += other .total_net_gain
176-
177- if self .total_net_gain_percentage is None :
178- self .total_net_gain_percentage = other .total_net_gain_percentage
179- else :
180- self .total_net_gain_percentage = safe_mean (
181- self .total_net_gain_percentage , other .total_net_gain_percentage
182- )
183-
184- if self .total_loss is None :
185- self .total_loss = other .total_loss
186- else :
187- self .total_loss = safe_mean (self .total_loss , other .total_loss )
188-
189- if self .total_growth is None :
190- self .total_growth = other .total_growth
191- else :
192- self .total_growth = safe_mean (
193- self .total_growth , other .total_growth
194- )
195-
196- if self .total_growth_percentage is None :
197- self .total_growth_percentage = other .total_growth_percentage
198- else :
199- self .total_growth_percentage = safe_mean (
200- self .total_growth_percentage , other .total_growth_percentage
201- )
202-
203- if self .average_trade_return is None :
204- self .average_trade_return = other .average_trade_return
205- else :
206- self .average_trade_return = safe_mean (
207- self .average_trade_return , other .average_trade_return
208- )
209-
210- if self .average_trade_return_percentage is None :
211- self .average_trade_return_percentage = \
212- other .average_trade_return_percentage
213- else :
214- self .average_trade_return_percentage = safe_mean (
215- self .average_trade_return_percentage ,
216- other .average_trade_return_percentage
217- )
218-
219- if self .average_trade_loss is None :
220- self .average_trade_loss = other .average_trade_loss
221- else :
222- self .average_trade_loss = safe_mean (
223- self .average_trade_loss , other .average_trade_loss
224- )
225-
226- if self .average_trade_loss_percentage is None :
227- self .average_trade_loss_percentage = \
228- other .average_trade_loss_percentage
229- else :
230- self .average_trade_loss_percentage = safe_mean (
231- self .average_trade_loss_percentage ,
232- other .average_trade_loss_percentage
233- )
234-
235- if self .average_trade_gain is None :
236- self .average_trade_gain = other .average_trade_gain
237- else :
238- self .average_trade_gain = safe_mean (
239- self .average_trade_gain , other .average_trade_gain
240- )
241-
242- if self .average_trade_gain_percentage is None :
243- self .average_trade_gain_percentage = \
244- other .average_trade_gain_percentage
245- else :
246- self .average_trade_gain_percentage = safe_mean (
247- self .average_trade_gain_percentage ,
248- other .average_trade_gain_percentage
249- )
250-
251- if self .cagr is None :
252- self .cagr = other .cagr
253- else :
254- self .cagr = safe_mean (self .cagr , other .cagr )
255-
256- if self .sharpe_ratio is None :
257- self .sharpe_ratio = other .sharpe_ratio
258- else :
259- self .sharpe_ratio = safe_mean (
260- self .sharpe_ratio , other .sharpe_ratio
261- )
262-
263- if self .sortino_ratio is None :
264- self .sortino_ratio = other .sortino_ratio
265- else :
266- self .sortino_ratio = safe_mean (
267- self .sortino_ratio , other .sortino_ratio
268- )
269-
270- if self .calmar_ratio is None :
271- self .calmar_ratio = other .calmar_ratio
272- else :
273- self .calmar_ratio = safe_mean (
274- self .calmar_ratio , other .calmar_ratio
275- )
276-
277- if self .profit_factor is None :
278- self .profit_factor = other .profit_factor
279- else :
280- self .profit_factor = safe_mean (
281- self .profit_factor , other .profit_factor
282- )
283-
284- if self .annual_volatility is None :
285- self .annual_volatility = other .annual_volatility
286- else :
287- self .annual_volatility = safe_mean (
288- self .annual_volatility , other .annual_volatility
289- )
290-
291- if self .max_drawdown is None :
292- self .max_drawdown = other .max_drawdown
293- else :
294- self .max_drawdown = min (self .max_drawdown , other .max_drawdown )
295-
296- if self .max_drawdown_duration is None :
297- self .max_drawdown_duration = other .max_drawdown_duration
298- else :
299- self .max_drawdown_duration = max (
300- self .max_drawdown_duration , other .max_drawdown_duration
301- )
302-
303- if self .trades_per_year is None :
304- self .trades_per_year = other .trades_per_year
305- else :
306- self .trades_per_year = safe_mean (
307- self .trades_per_year , other .trades_per_year
308- )
309-
310- if self .win_rate is None :
311- self .win_rate = other .win_rate
312- else :
313- self .win_rate = safe_mean (self .win_rate , other .win_rate )
314-
315- if self .win_loss_ratio is None :
316- self .win_loss_ratio = other .win_loss_ratio
317- else :
318- self .win_loss_ratio = safe_mean (
319- self .win_loss_ratio , other .win_loss_ratio
320- )
321-
322- if self .number_of_trades is None :
323- self .number_of_trades = other .number_of_trades
324- else :
325- self .number_of_trades += other .number_of_trades
326-
327- if self .average_net_gain is None :
328- self .average_net_gain = other .total_net_gain
329- else :
330- self .average_net_gain = safe_mean (
331- self .average_net_gain , other .total_net_gain
332- )
333-
334- if self .average_net_gain_percentage is None :
335- self .average_net_gain_percentage = other .total_net_gain_percentage
336- else :
337- self .average_net_gain_percentage = safe_mean (
338- self .average_net_gain_percentage ,
339- other .total_net_gain_percentage
340- )
341-
342- if self .average_growth is None :
343- self .average_growth = other .total_growth
344- else :
345- self .average_growth = safe_mean (
346- self .average_growth , other .total_growth
347- )
348-
349- if self .average_growth_percentage is None :
350- self .average_growth_percentage = other .total_growth_percentage
351- else :
352- self .average_growth_percentage = safe_mean (
353- self .average_growth_percentage ,
354- other .total_growth_percentage
355- )
356-
357154 def __repr__ (self ):
358155 return json .dumps (
359156 self .to_dict (), indent = 4 , sort_keys = True , default = str
0 commit comments