1717import os
1818from datetime import datetime
1919from typing import Dict , List , Tuple
20- import sys
21- sys .path .insert (0 , os .path .dirname (os .path .abspath (__file__ )))
20+ from globals_utils import setup_imports , save_json_file , ensure_directory
21+
22+ # Setup imports
23+ setup_imports (__file__ )
2224from utils import load_trades_index , load_account_config
2325
2426# Constants
@@ -319,6 +321,161 @@ def calculate_kelly_criterion(trades: List[Dict]) -> float:
319321 return round (kelly * 100 , 1 )
320322
321323
324+ def calculate_sharpe_ratio (trades : List [Dict ], risk_free_rate : float = 0.0 ) -> float :
325+ """
326+ Calculate Sharpe Ratio - risk-adjusted returns
327+
328+ Sharpe Ratio = (Average Return - Risk Free Rate) / Standard Deviation of Returns
329+
330+ Args:
331+ trades: List of trade dictionaries
332+ risk_free_rate: Risk-free rate (default 0% for simplicity)
333+
334+ Returns:
335+ float: Sharpe ratio
336+ """
337+ if not trades or len (trades ) < 2 :
338+ return 0.0
339+
340+ # Get returns as percentages
341+ returns = [t .get ("pnl_percent" , 0 ) for t in trades ]
342+
343+ # Calculate average return
344+ avg_return = sum (returns ) / len (returns )
345+
346+ # Calculate standard deviation
347+ variance = sum ((r - avg_return ) ** 2 for r in returns ) / len (returns )
348+ std_dev = variance ** 0.5
349+
350+ if std_dev == 0 :
351+ return 0.0
352+
353+ sharpe = (avg_return - risk_free_rate ) / std_dev
354+
355+ return round (sharpe , 2 )
356+
357+
358+ def calculate_r_multiple_distribution (trades : List [Dict ]) -> Dict :
359+ """
360+ Calculate R-Multiple distribution - returns in risk units
361+
362+ R-Multiple = (Exit Price - Entry Price) / (Entry Price - Stop Loss)
363+ This shows how many times your initial risk you made or lost
364+
365+ Args:
366+ trades: List of trade dictionaries
367+
368+ Returns:
369+ Dict: R-multiple distribution data
370+ """
371+ if not trades :
372+ return {
373+ "labels" : [],
374+ "data" : [],
375+ "avg_r_multiple" : 0 ,
376+ "median_r_multiple" : 0
377+ }
378+
379+ r_multiples = []
380+
381+ for trade in trades :
382+ entry_price = trade .get ("entry_price" , 0 )
383+ exit_price = trade .get ("exit_price" , 0 )
384+ stop_loss = trade .get ("stop_loss" , 0 )
385+ direction = trade .get ("direction" , "LONG" )
386+
387+ if entry_price == 0 or stop_loss == 0 :
388+ continue
389+
390+ # Calculate risk (distance from entry to stop)
391+ if direction == "LONG" :
392+ risk = entry_price - stop_loss
393+ gain = exit_price - entry_price
394+ else : # SHORT
395+ risk = stop_loss - entry_price
396+ gain = entry_price - exit_price
397+
398+ if risk <= 0 :
399+ continue
400+
401+ # R-multiple is gain divided by risk
402+ r_multiple = gain / risk
403+ r_multiples .append (r_multiple )
404+
405+ if not r_multiples :
406+ return {
407+ "labels" : [],
408+ "data" : [],
409+ "avg_r_multiple" : 0 ,
410+ "median_r_multiple" : 0
411+ }
412+
413+ # Create histogram buckets
414+ buckets = {
415+ "< -2R" : 0 ,
416+ "-2R to -1R" : 0 ,
417+ "-1R to 0R" : 0 ,
418+ "0R to 1R" : 0 ,
419+ "1R to 2R" : 0 ,
420+ "2R to 3R" : 0 ,
421+ "> 3R" : 0
422+ }
423+
424+ for r in r_multiples :
425+ if r < - 2 :
426+ buckets ["< -2R" ] += 1
427+ elif r < - 1 :
428+ buckets ["-2R to -1R" ] += 1
429+ elif r < 0 :
430+ buckets ["-1R to 0R" ] += 1
431+ elif r < 1 :
432+ buckets ["0R to 1R" ] += 1
433+ elif r < 2 :
434+ buckets ["1R to 2R" ] += 1
435+ elif r < 3 :
436+ buckets ["2R to 3R" ] += 1
437+ else :
438+ buckets ["> 3R" ] += 1
439+
440+ # Calculate statistics
441+ avg_r = sum (r_multiples ) / len (r_multiples )
442+ sorted_r = sorted (r_multiples )
443+ median_r = (
444+ sorted_r [len (sorted_r ) // 2 ]
445+ if len (sorted_r ) % 2 == 1
446+ else (sorted_r [len (sorted_r ) // 2 - 1 ] + sorted_r [len (sorted_r ) // 2 ]) / 2
447+ )
448+
449+ return {
450+ "labels" : list (buckets .keys ()),
451+ "data" : list (buckets .values ()),
452+ "avg_r_multiple" : round (avg_r , 2 ),
453+ "median_r_multiple" : round (median_r , 2 )
454+ }
455+
456+
457+ def calculate_mae_mfe_analysis (trades : List [Dict ]) -> Dict :
458+ """
459+ Calculate MAE (Mean Adverse Excursion) and MFE (Mean Favorable Excursion)
460+
461+ Note: This requires intraday data collection which is not currently available.
462+ For now, we'll return a placeholder indicating this feature requires additional data.
463+
464+ Args:
465+ trades: List of trade dictionaries
466+
467+ Returns:
468+ Dict: MAE/MFE analysis placeholder
469+ """
470+ return {
471+ "available" : False ,
472+ "message" : "MAE/MFE analysis requires intraday price data collection. This feature will be available once intraday high/low prices are tracked for each trade." ,
473+ "mae_avg" : 0 ,
474+ "mfe_avg" : 0 ,
475+ "note" : "To enable this metric, add 'intraday_high' and 'intraday_low' fields to trade entries."
476+ }
477+
478+
322479def aggregate_by_tag (trades : List [Dict ], tag_field : str ) -> Dict :
323480 """
324481 Aggregate statistics by a tag field (strategy, setup, etc.)
@@ -463,6 +620,11 @@ def main():
463620 )
464621 kelly = calculate_kelly_criterion (sorted_trades )
465622
623+ # Calculate advanced analytics
624+ sharpe_ratio = calculate_sharpe_ratio (sorted_trades )
625+ r_multiple_dist = calculate_r_multiple_distribution (sorted_trades )
626+ mae_mfe = calculate_mae_mfe_analysis (sorted_trades )
627+
466628 # Calculate percentage-based returns metrics
467629 returns_metrics = calculate_returns_metrics (
468630 sorted_trades ,
@@ -484,6 +646,9 @@ def main():
484646 "max_drawdown" : max_drawdown ,
485647 "max_drawdown_percent" : returns_metrics ["max_drawdown_percent" ],
486648 "kelly_criterion" : kelly ,
649+ "sharpe_ratio" : sharpe_ratio ,
650+ "r_multiple_distribution" : r_multiple_dist ,
651+ "mae_mfe_analysis" : mae_mfe ,
487652 "by_strategy" : by_strategy ,
488653 "by_setup" : by_setup ,
489654 "by_session" : by_session ,
@@ -504,11 +669,10 @@ def main():
504669 }
505670
506671 # Save analytics data
507- os . makedirs ("index.directory/assets/charts" , exist_ok = True )
672+ ensure_directory ("index.directory/assets/charts" )
508673 output_file = "index.directory/assets/charts/analytics-data.json"
509674
510- with open (output_file , "w" , encoding = "utf-8" ) as f :
511- json .dump (analytics , f , indent = 2 )
675+ save_json_file (output_file , analytics )
512676
513677 print (f"Analytics written to { output_file } " )
514678 print (f"Expectancy: ${ analytics ['expectancy' ]} " )
0 commit comments