@@ -57,6 +57,8 @@ def _get_plots():
5757from dateutil .relativedelta import relativedelta
5858from io import StringIO
5959from pathlib import Path
60+ import tempfile
61+ import webbrowser
6062
6163try :
6264 from IPython .display import display as iDisplay , HTML as iHTML
@@ -95,6 +97,44 @@ def _get_trading_periods(periods_per_year=252):
9597 return periods_per_year , half_year
9698
9799
100+ def _print_parameters_table (
101+ benchmark_title = None ,
102+ periods_per_year = 252 ,
103+ rf = 0.0 ,
104+ compounded = True ,
105+ match_dates = True ,
106+ ):
107+ """
108+ Print a formatted parameters table for terminal/console output.
109+
110+ Parameters
111+ ----------
112+ benchmark_title : str or None
113+ Benchmark name/ticker
114+ periods_per_year : int
115+ Number of trading periods per year
116+ rf : float
117+ Risk-free rate
118+ compounded : bool
119+ Whether returns are compounded
120+ match_dates : bool
121+ Whether dates are matched with benchmark
122+ """
123+ width = 40
124+ print ("=" * width )
125+ print (" Parameters" )
126+ print ("-" * width )
127+ if benchmark_title :
128+ print (f"{ 'Benchmark' :<25} { benchmark_title .upper ():>15} " )
129+ print (f"{ 'Periods/Year' :<25} { periods_per_year :>15} " )
130+ print (f"{ 'Risk-Free Rate' :<25} { rf :>14.1%} " )
131+ print (f"{ 'Compounded' :<25} { 'Yes' if compounded else 'No' :>15} " )
132+ if benchmark_title :
133+ print (f"{ 'Match Dates' :<25} { 'Yes' if match_dates else 'No' :>15} " )
134+ print ("=" * width )
135+ print ()
136+
137+
98138def _match_dates (returns , benchmark ):
99139 """
100140 Align returns and benchmark data to start from the same date.
@@ -203,15 +243,9 @@ def html(
203243
204244 Raises
205245 ------
206- ValueError
207- If output is None and not running in notebook environment
208246 FileNotFoundError
209247 If custom template_path doesn't exist
210248 """
211- # Check if output parameter is required (not in notebook environment)
212- if output is None and not _get_utils ()._in_notebook ():
213- raise ValueError ("`output` must be specified" )
214-
215249 # Clean returns data by removing NaN values if date matching is enabled
216250 if match_dates :
217251 returns = returns .dropna ()
@@ -290,10 +324,13 @@ def html(
290324 # Format date range for display in template
291325 date_range = returns .index .strftime ("%e %b, %Y" )
292326 tpl = tpl .replace ("{{date_range}}" , date_range [0 ] + " - " + date_range [- 1 ])
293- tpl = tpl .replace ("{{title}}" , title )
327+
328+ # Build title with compounding indicator (only show if compounded)
329+ full_title = f"{ title } (Compounded)" if compounded else title
330+ tpl = tpl .replace ("{{title}}" , full_title )
294331 tpl = tpl .replace ("{{v}}" , __version__ )
295332
296- # Build parameters string for subtitle (feature #472)
333+ # Build parameters string for subtitle
297334 params_parts = []
298335
299336 # Add user-provided parameters first if present
@@ -302,15 +339,12 @@ def html(
302339 for key , value in user_params .items ():
303340 params_parts .append (f"{ key } : { value } " )
304341
305- # Add auto-detected parameters
342+ # Add auto-detected parameters (always show key params)
306343 if benchmark_title :
307344 params_parts .append (f"Benchmark: { benchmark_title .upper ()} " )
308- if rf != 0 :
309- params_parts .append (f"RF: { rf :.1%} " )
310- if periods_per_year != 252 :
311- params_parts .append (f"Periods: { periods_per_year } " )
312- if not compounded :
313- params_parts .append ("Simple Returns" )
345+ params_parts .append (f"Periods/Year: { periods_per_year } " )
346+ params_parts .append (f"RF: { rf :.1%} " )
347+
314348 params_str = " • " .join (params_parts )
315349 if params_str :
316350 params_str += " | "
@@ -725,8 +759,16 @@ def html(
725759
726760 # Handle output - either download in browser or save to file
727761 if output is None :
728- # _open_html(tpl)
729- _download_html (tpl , download_filename )
762+ if _get_utils ()._in_notebook ():
763+ _download_html (tpl , download_filename )
764+ else :
765+ # Save to temp file and open in browser
766+ with tempfile .NamedTemporaryFile (
767+ mode = "w" , suffix = ".html" , delete = False , encoding = "utf-8"
768+ ) as f :
769+ f .write (tpl )
770+ temp_path = f .name
771+ webbrowser .open ("file://" + temp_path )
730772 return
731773
732774 # Write HTML content to specified output file
@@ -888,6 +930,13 @@ def full(
888930 iDisplay (iHTML ("<h4>Strategy Visualization</h4>" ))
889931 else :
890932 # Display in console/terminal environment
933+ _print_parameters_table (
934+ benchmark_title = benchmark_title ,
935+ periods_per_year = periods_per_year ,
936+ rf = rf ,
937+ compounded = compounded ,
938+ match_dates = match_dates ,
939+ )
891940 print ("[Performance Metrics]\n " )
892941 metrics (
893942 returns = returns ,
@@ -1043,6 +1092,13 @@ def basic(
10431092 iDisplay (iHTML ("<h4>Strategy Visualization</h4>" ))
10441093 else :
10451094 # Display in console/terminal environment
1095+ _print_parameters_table (
1096+ benchmark_title = benchmark_title ,
1097+ periods_per_year = periods_per_year ,
1098+ rf = rf ,
1099+ compounded = compounded ,
1100+ match_dates = match_dates ,
1101+ )
10461102 print ("[Performance Metrics]\n " )
10471103 metrics (
10481104 returns = returns ,
@@ -1383,9 +1439,24 @@ def metrics(
13831439 metrics ["Skew" ] = _get_stats ().skew (df , prepare_returns = False )
13841440 metrics ["Kurtosis" ] = _get_stats ().kurtosis (df , prepare_returns = False )
13851441
1442+ # Additional ratios
1443+ metrics ["Ulcer Performance Index" ] = _get_stats ().ulcer_performance_index (df , rf )
1444+ metrics ["Risk-Adjusted Return %" ] = _get_stats ().rar (df , rf ) * pct
1445+ metrics ["Risk-Return Ratio" ] = _get_stats ().risk_return_ratio (df , prepare_returns = False )
1446+
13861447 # Add separator
13871448 metrics ["~~~~~~~~~~" ] = blank
13881449
1450+ # Average return metrics
1451+ metrics ["Avg. Return %" ] = _get_stats ().avg_return (df , prepare_returns = False ) * pct
1452+ metrics ["Avg. Win %" ] = _get_stats ().avg_win (df , prepare_returns = False ) * pct
1453+ metrics ["Avg. Loss %" ] = _get_stats ().avg_loss (df , prepare_returns = False ) * pct
1454+ metrics ["Win/Loss Ratio" ] = _get_stats ().win_loss_ratio (df , prepare_returns = False )
1455+ metrics ["Profit Ratio" ] = _get_stats ().profit_ratio (df , prepare_returns = False )
1456+
1457+ # Add separator
1458+ metrics ["~~~~~~~~~~~" ] = blank
1459+
13891460 # Expected returns at different frequencies
13901461 metrics ["Expected Daily %%" ] = (
13911462 _get_stats ().expected_return (df , compounded = compounded , prepare_returns = False )
0 commit comments