jetpackjules Claude commited on
Commit
0beff90
ยท
1 Parent(s): 44926bc

Add comprehensive trading statistics and analysis buttons

Browse files

Added 6 powerful calculation buttons with detailed analytics:

๐Ÿ“ˆ Sequential Reinvestment P&L% - Simulates reinvesting profits sequentially
โš–๏ธ Equal Weight Portfolio P&L% - Calculates returns if equal $ invested in all
๐Ÿ† Best vs Worst Performers - Ranks top/bottom 5 with win/loss stats
๐ŸŽฏ Win Rate & Avg Returns - Shows hit rate, average wins/losses, risk/reward
โš ๏ธ Risk Metrics & Volatility - Standard deviation, Sharpe ratio, drawdowns
โฐ Time-based Performance - Monthly breakdown and trend analysis

Each button provides detailed mathematical analysis of trading performance with:
- Actual order history processing
- Current position values
- Statistical calculations (mean, median, std dev)
- Risk metrics and concentration analysis
- Time-series trend analysis

๐Ÿค– Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +533 -0
app.py CHANGED
@@ -764,6 +764,493 @@ def debug_account_info():
764
  except Exception as e:
765
  return f"ERROR getting account info: {str(e)}"
766
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
767
  def clear_terminal():
768
  """Clear terminal output"""
769
  return "๐Ÿ–ฅ๏ธ VM Terminal Ready\n$ "
@@ -1053,6 +1540,26 @@ def create_dashboard():
1053
  )
1054
  refresh_investment_btn = gr.Button("๐Ÿ”„ Refresh Investment Performance", variant="primary", size="lg")
1055
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1056
  gr.Markdown("### ๐Ÿ”ง Debug API Calls")
1057
  debug_output = gr.Textbox(
1058
  label="Debug Output",
@@ -1205,6 +1712,32 @@ def create_dashboard():
1205
  outputs=[debug_output]
1206
  )
1207
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1208
  # VM Terminal tab - RADICAL FIX: Use <pre> instead of <br> and force scroll with element replacement
1209
  def run_and_clear(cmd, output, history):
1210
  new_output, _, new_history = run_vm_command(cmd, output, history)
 
764
  except Exception as e:
765
  return f"ERROR getting account info: {str(e)}"
766
 
767
+ def calculate_sequential_reinvestment():
768
+ """Calculate P&L% if reinvesting same amount sequentially in each stock"""
769
+ try:
770
+ orders = get_order_history()
771
+ if not orders:
772
+ return "No order data available for calculation"
773
+
774
+ # Get unique symbols with their first buy date
775
+ symbols_by_date = {}
776
+ for order in orders:
777
+ if order.side.value == 'buy' and order.status.value == 'filled':
778
+ symbol = order.symbol
779
+ fill_date = order.filled_at
780
+ if symbol not in symbols_by_date or fill_date < symbols_by_date[symbol]:
781
+ symbols_by_date[symbol] = fill_date
782
+
783
+ # Sort by first buy date
784
+ sorted_symbols = sorted(symbols_by_date.items(), key=lambda x: x[1])
785
+
786
+ # Calculate sequential reinvestment returns
787
+ initial_investment = 1000 # Start with $1000
788
+ current_value = initial_investment
789
+
790
+ results = []
791
+ total_return = 0
792
+
793
+ for symbol, first_date in sorted_symbols:
794
+ # Get all orders for this symbol
795
+ symbol_orders = [o for o in orders if o.symbol == symbol]
796
+ buy_orders = [o for o in symbol_orders if o.side.value == 'buy']
797
+ sell_orders = [o for o in symbol_orders if o.side.value == 'sell']
798
+
799
+ if buy_orders:
800
+ # Calculate actual P&L for this symbol
801
+ total_bought = sum(float(o.filled_qty or 0) for o in buy_orders)
802
+ total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders)
803
+
804
+ if sell_orders:
805
+ # Sold - use actual sell proceeds
806
+ sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders)
807
+ pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0
808
+ else:
809
+ # Still holding - estimate current value
810
+ positions = get_current_positions()
811
+ pos = next((p for p in positions if p['symbol'] == symbol), None)
812
+ if pos:
813
+ current_price = pos['current_price']
814
+ current_symbol_value = total_bought * current_price
815
+ pl_percent = ((current_symbol_value - total_cost) / total_cost) if total_cost > 0 else 0
816
+ else:
817
+ pl_percent = 0
818
+
819
+ # Apply return to current value
820
+ new_value = current_value * (1 + pl_percent)
821
+ gain_loss = new_value - current_value
822
+
823
+ results.append(f"{symbol}: {pl_percent*100:+.2f}% | ${current_value:.2f} โ†’ ${new_value:.2f} ({gain_loss:+.2f})")
824
+ current_value = new_value
825
+ total_return += pl_percent
826
+
827
+ final_return_pct = ((current_value - initial_investment) / initial_investment) * 100
828
+
829
+ output = f"๐Ÿงฎ SEQUENTIAL REINVESTMENT ANALYSIS\n"
830
+ output += f"Starting Investment: ${initial_investment:.2f}\n"
831
+ output += f"Final Value: ${current_value:.2f}\n"
832
+ output += f"Total Return: {final_return_pct:+.2f}%\n"
833
+ output += f"Number of Trades: {len(sorted_symbols)}\n\n"
834
+ output += "Trade Sequence:\n"
835
+ output += "\n".join(results)
836
+
837
+ return output
838
+
839
+ except Exception as e:
840
+ return f"ERROR calculating sequential reinvestment: {str(e)}"
841
+
842
+ def calculate_equal_weight_portfolio():
843
+ """Calculate P&L% if investing equal amounts in all stocks simultaneously"""
844
+ try:
845
+ orders = get_order_history()
846
+ if not orders:
847
+ return "No order data available for calculation"
848
+
849
+ # Get unique symbols
850
+ symbols = set()
851
+ for order in orders:
852
+ if order.side.value == 'buy':
853
+ symbols.add(order.symbol)
854
+
855
+ total_pl = 0
856
+ valid_symbols = 0
857
+ results = []
858
+
859
+ for symbol in sorted(symbols):
860
+ symbol_orders = [o for o in orders if o.symbol == symbol]
861
+ buy_orders = [o for o in symbol_orders if o.side.value == 'buy']
862
+ sell_orders = [o for o in symbol_orders if o.side.value == 'sell']
863
+
864
+ if buy_orders:
865
+ total_bought = sum(float(o.filled_qty or 0) for o in buy_orders)
866
+ total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders)
867
+ avg_buy_price = total_cost / total_bought if total_bought > 0 else 0
868
+
869
+ if sell_orders:
870
+ # Sold
871
+ sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders)
872
+ pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0
873
+ status = "SOLD"
874
+ else:
875
+ # Still holding
876
+ positions = get_current_positions()
877
+ pos = next((p for p in positions if p['symbol'] == symbol), None)
878
+ if pos:
879
+ current_price = pos['current_price']
880
+ current_value = total_bought * current_price
881
+ pl_percent = ((current_value - total_cost) / total_cost) if total_cost > 0 else 0
882
+ status = "HOLDING"
883
+ else:
884
+ pl_percent = 0
885
+ status = "UNKNOWN"
886
+
887
+ total_pl += pl_percent
888
+ valid_symbols += 1
889
+
890
+ results.append(f"{symbol}: {pl_percent*100:+.2f}% ({status})")
891
+
892
+ avg_return = (total_pl / valid_symbols) * 100 if valid_symbols > 0 else 0
893
+
894
+ output = f"โš–๏ธ EQUAL WEIGHT PORTFOLIO ANALYSIS\n"
895
+ output += f"Total Symbols: {valid_symbols}\n"
896
+ output += f"Average Return per Symbol: {avg_return:+.2f}%\n"
897
+ output += f"Portfolio Return (equal weights): {avg_return:+.2f}%\n\n"
898
+ output += "Individual Returns:\n"
899
+ output += "\n".join(results)
900
+
901
+ return output
902
+
903
+ except Exception as e:
904
+ return f"ERROR calculating equal weight portfolio: {str(e)}"
905
+
906
+ def calculate_best_worst_performers():
907
+ """Find best and worst performing stocks"""
908
+ try:
909
+ orders = get_order_history()
910
+ if not orders:
911
+ return "No order data available for calculation"
912
+
913
+ symbols = set()
914
+ for order in orders:
915
+ if order.side.value == 'buy':
916
+ symbols.add(order.symbol)
917
+
918
+ performance = []
919
+
920
+ for symbol in symbols:
921
+ symbol_orders = [o for o in orders if o.symbol == symbol]
922
+ buy_orders = [o for o in symbol_orders if o.side.value == 'buy']
923
+ sell_orders = [o for o in symbol_orders if o.side.value == 'sell']
924
+
925
+ if buy_orders:
926
+ total_bought = sum(float(o.filled_qty or 0) for o in buy_orders)
927
+ total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders)
928
+
929
+ if sell_orders:
930
+ sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders)
931
+ pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0
932
+ pl_dollars = sold_value - total_cost
933
+ status = "SOLD"
934
+ else:
935
+ positions = get_current_positions()
936
+ pos = next((p for p in positions if p['symbol'] == symbol), None)
937
+ if pos:
938
+ current_price = pos['current_price']
939
+ current_value = total_bought * current_price
940
+ pl_percent = ((current_value - total_cost) / total_cost) if total_cost > 0 else 0
941
+ pl_dollars = current_value - total_cost
942
+ status = "HOLDING"
943
+ else:
944
+ pl_percent = 0
945
+ pl_dollars = 0
946
+ status = "UNKNOWN"
947
+
948
+ performance.append({
949
+ 'symbol': symbol,
950
+ 'pl_percent': pl_percent,
951
+ 'pl_dollars': pl_dollars,
952
+ 'investment': total_cost,
953
+ 'status': status
954
+ })
955
+
956
+ # Sort by percentage return
957
+ performance.sort(key=lambda x: x['pl_percent'], reverse=True)
958
+
959
+ output = f"๐Ÿ† BEST vs WORST PERFORMERS\n\n"
960
+
961
+ if performance:
962
+ output += "๐Ÿฅ‡ TOP 5 PERFORMERS:\n"
963
+ for i, perf in enumerate(performance[:5]):
964
+ output += f"{i+1}. {perf['symbol']}: {perf['pl_percent']*100:+.2f}% (${perf['pl_dollars']:+.2f}) - {perf['status']}\n"
965
+
966
+ output += "\n๐Ÿฅ‰ BOTTOM 5 PERFORMERS:\n"
967
+ for i, perf in enumerate(performance[-5:]):
968
+ rank = len(performance) - 4 + i
969
+ output += f"{rank}. {perf['symbol']}: {perf['pl_percent']*100:+.2f}% (${perf['pl_dollars']:+.2f}) - {perf['status']}\n"
970
+
971
+ # Calculate some stats
972
+ total_winners = len([p for p in performance if p['pl_percent'] > 0])
973
+ total_losers = len([p for p in performance if p['pl_percent'] < 0])
974
+
975
+ output += f"\n๐Ÿ“Š SUMMARY:\n"
976
+ output += f"Winners: {total_winners}/{len(performance)} ({total_winners/len(performance)*100:.1f}%)\n"
977
+ output += f"Losers: {total_losers}/{len(performance)} ({total_losers/len(performance)*100:.1f}%)\n"
978
+
979
+ return output
980
+
981
+ except Exception as e:
982
+ return f"ERROR calculating best/worst performers: {str(e)}"
983
+
984
+ def calculate_win_rate_metrics():
985
+ """Calculate win rate and average returns"""
986
+ try:
987
+ orders = get_order_history()
988
+ if not orders:
989
+ return "No order data available for calculation"
990
+
991
+ symbols = set()
992
+ for order in orders:
993
+ if order.side.value == 'buy':
994
+ symbols.add(order.symbol)
995
+
996
+ performance = []
997
+ total_investment = 0
998
+
999
+ for symbol in symbols:
1000
+ symbol_orders = [o for o in orders if o.symbol == symbol]
1001
+ buy_orders = [o for o in symbol_orders if o.side.value == 'buy']
1002
+ sell_orders = [o for o in symbol_orders if o.side.value == 'sell']
1003
+
1004
+ if buy_orders:
1005
+ total_bought = sum(float(o.filled_qty or 0) for o in buy_orders)
1006
+ total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders)
1007
+ total_investment += total_cost
1008
+
1009
+ if sell_orders:
1010
+ sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders)
1011
+ pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0
1012
+ pl_dollars = sold_value - total_cost
1013
+ else:
1014
+ positions = get_current_positions()
1015
+ pos = next((p for p in positions if p['symbol'] == symbol), None)
1016
+ if pos:
1017
+ current_price = pos['current_price']
1018
+ current_value = total_bought * current_price
1019
+ pl_percent = ((current_value - total_cost) / total_cost) if total_cost > 0 else 0
1020
+ pl_dollars = current_value - total_cost
1021
+ else:
1022
+ pl_percent = 0
1023
+ pl_dollars = 0
1024
+
1025
+ performance.append({
1026
+ 'symbol': symbol,
1027
+ 'pl_percent': pl_percent,
1028
+ 'pl_dollars': pl_dollars,
1029
+ 'investment': total_cost
1030
+ })
1031
+
1032
+ if not performance:
1033
+ return "No performance data available"
1034
+
1035
+ # Calculate metrics
1036
+ winners = [p for p in performance if p['pl_percent'] > 0]
1037
+ losers = [p for p in performance if p['pl_percent'] < 0]
1038
+ breakeven = [p for p in performance if p['pl_percent'] == 0]
1039
+
1040
+ win_rate = len(winners) / len(performance) * 100
1041
+ avg_win = sum(p['pl_percent'] for p in winners) / len(winners) * 100 if winners else 0
1042
+ avg_loss = sum(p['pl_percent'] for p in losers) / len(losers) * 100 if losers else 0
1043
+
1044
+ total_pl_dollars = sum(p['pl_dollars'] for p in performance)
1045
+ total_pl_percent = (total_pl_dollars / total_investment) * 100 if total_investment > 0 else 0
1046
+
1047
+ # Risk/Reward ratio
1048
+ risk_reward = abs(avg_win / avg_loss) if avg_loss != 0 else float('inf')
1049
+
1050
+ output = f"๐ŸŽฏ WIN RATE & AVERAGE RETURNS\n\n"
1051
+ output += f"Total Trades: {len(performance)}\n"
1052
+ output += f"Win Rate: {win_rate:.1f}% ({len(winners)} winners)\n"
1053
+ output += f"Loss Rate: {len(losers)/len(performance)*100:.1f}% ({len(losers)} losers)\n"
1054
+ output += f"Breakeven: {len(breakeven)} trades\n\n"
1055
+
1056
+ output += f"๐Ÿ“ˆ AVERAGE PERFORMANCE:\n"
1057
+ output += f"Average Winner: +{avg_win:.2f}%\n"
1058
+ output += f"Average Loser: {avg_loss:.2f}%\n"
1059
+ output += f"Risk/Reward Ratio: {risk_reward:.2f}:1\n\n"
1060
+
1061
+ output += f"๐Ÿ’ฐ TOTAL PERFORMANCE:\n"
1062
+ output += f"Total Invested: ${total_investment:.2f}\n"
1063
+ output += f"Total P&L: ${total_pl_dollars:+.2f}\n"
1064
+ output += f"Total Return: {total_pl_percent:+.2f}%\n"
1065
+
1066
+ return output
1067
+
1068
+ except Exception as e:
1069
+ return f"ERROR calculating win rate metrics: {str(e)}"
1070
+
1071
+ def calculate_risk_metrics():
1072
+ """Calculate risk metrics and volatility"""
1073
+ try:
1074
+ orders = get_order_history()
1075
+ if not orders:
1076
+ return "No order data available for calculation"
1077
+
1078
+ symbols = set()
1079
+ for order in orders:
1080
+ if order.side.value == 'buy':
1081
+ symbols.add(order.symbol)
1082
+
1083
+ returns = []
1084
+ investments = []
1085
+
1086
+ for symbol in symbols:
1087
+ symbol_orders = [o for o in orders if o.symbol == symbol]
1088
+ buy_orders = [o for o in symbol_orders if o.side.value == 'buy']
1089
+ sell_orders = [o for o in symbol_orders if o.side.value == 'sell']
1090
+
1091
+ if buy_orders:
1092
+ total_bought = sum(float(o.filled_qty or 0) for o in buy_orders)
1093
+ total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders)
1094
+ investments.append(total_cost)
1095
+
1096
+ if sell_orders:
1097
+ sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders)
1098
+ pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0
1099
+ else:
1100
+ positions = get_current_positions()
1101
+ pos = next((p for p in positions if p['symbol'] == symbol), None)
1102
+ if pos:
1103
+ current_price = pos['current_price']
1104
+ current_value = total_bought * current_price
1105
+ pl_percent = ((current_value - total_cost) / total_cost) if total_cost > 0 else 0
1106
+ else:
1107
+ pl_percent = 0
1108
+
1109
+ returns.append(pl_percent)
1110
+
1111
+ if not returns:
1112
+ return "No return data available"
1113
+
1114
+ # Calculate statistics
1115
+ import statistics
1116
+ avg_return = statistics.mean(returns) * 100
1117
+ median_return = statistics.median(returns) * 100
1118
+ volatility = statistics.stdev(returns) * 100 if len(returns) > 1 else 0
1119
+
1120
+ # Sharpe-like ratio (assuming risk-free rate = 0)
1121
+ sharpe = avg_return / volatility if volatility > 0 else 0
1122
+
1123
+ # Max drawdown
1124
+ max_return = max(returns) * 100
1125
+ min_return = min(returns) * 100
1126
+ max_drawdown = max_return - min_return
1127
+
1128
+ # Portfolio concentration
1129
+ total_investment = sum(investments)
1130
+ avg_position_size = statistics.mean(investments)
1131
+ largest_position = max(investments)
1132
+ concentration = (largest_position / total_investment) * 100 if total_investment > 0 else 0
1133
+
1134
+ output = f"โš ๏ธ RISK METRICS & VOLATILITY\n\n"
1135
+ output += f"๐Ÿ“Š RETURN STATISTICS:\n"
1136
+ output += f"Average Return: {avg_return:+.2f}%\n"
1137
+ output += f"Median Return: {median_return:+.2f}%\n"
1138
+ output += f"Volatility (StdDev): {volatility:.2f}%\n"
1139
+ output += f"Sharpe-like Ratio: {sharpe:.2f}\n\n"
1140
+
1141
+ output += f"๐Ÿ“‰ RISK MEASURES:\n"
1142
+ output += f"Best Trade: +{max_return:.2f}%\n"
1143
+ output += f"Worst Trade: {min_return:.2f}%\n"
1144
+ output += f"Max Range: {max_drawdown:.2f}%\n\n"
1145
+
1146
+ output += f"๐ŸŽฏ POSITION SIZING:\n"
1147
+ output += f"Average Position: ${avg_position_size:.2f}\n"
1148
+ output += f"Largest Position: ${largest_position:.2f}\n"
1149
+ output += f"Concentration Risk: {concentration:.1f}% in largest\n"
1150
+
1151
+ return output
1152
+
1153
+ except Exception as e:
1154
+ return f"ERROR calculating risk metrics: {str(e)}"
1155
+
1156
+ def calculate_time_analysis():
1157
+ """Analyze performance by time periods"""
1158
+ try:
1159
+ orders = get_order_history()
1160
+ if not orders:
1161
+ return "No order data available for calculation"
1162
+
1163
+ from datetime import datetime, timezone
1164
+
1165
+ # Group orders by month
1166
+ monthly_performance = {}
1167
+
1168
+ for order in orders:
1169
+ if order.side.value == 'buy' and order.status.value == 'filled':
1170
+ month_key = order.filled_at.strftime('%Y-%m')
1171
+ if month_key not in monthly_performance:
1172
+ monthly_performance[month_key] = {'symbols': set(), 'investment': 0, 'returns': []}
1173
+
1174
+ symbol = order.symbol
1175
+ monthly_performance[month_key]['symbols'].add(symbol)
1176
+
1177
+ # Calculate returns for each month
1178
+ symbols = set()
1179
+ for order in orders:
1180
+ if order.side.value == 'buy':
1181
+ symbols.add(order.symbol)
1182
+
1183
+ symbol_performance = {}
1184
+ for symbol in symbols:
1185
+ symbol_orders = [o for o in orders if o.symbol == symbol]
1186
+ buy_orders = [o for o in symbol_orders if o.side.value == 'buy']
1187
+ sell_orders = [o for o in symbol_orders if o.side.value == 'sell']
1188
+
1189
+ if buy_orders:
1190
+ first_buy = min(buy_orders, key=lambda x: x.filled_at)
1191
+ month_key = first_buy.filled_at.strftime('%Y-%m')
1192
+
1193
+ total_bought = sum(float(o.filled_qty or 0) for o in buy_orders)
1194
+ total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders)
1195
+
1196
+ if sell_orders:
1197
+ sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders)
1198
+ pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0
1199
+ else:
1200
+ positions = get_current_positions()
1201
+ pos = next((p for p in positions if p['symbol'] == symbol), None)
1202
+ if pos:
1203
+ current_price = pos['current_price']
1204
+ current_value = total_bought * current_price
1205
+ pl_percent = ((current_value - total_cost) / total_cost) if total_cost > 0 else 0
1206
+ else:
1207
+ pl_percent = 0
1208
+
1209
+ if month_key in monthly_performance:
1210
+ monthly_performance[month_key]['investment'] += total_cost
1211
+ monthly_performance[month_key]['returns'].append(pl_percent)
1212
+
1213
+ output = f"โฐ TIME-BASED PERFORMANCE ANALYSIS\n\n"
1214
+
1215
+ for month in sorted(monthly_performance.keys()):
1216
+ data = monthly_performance[month]
1217
+ if data['returns']:
1218
+ avg_return = sum(data['returns']) / len(data['returns']) * 100
1219
+ total_investment = data['investment']
1220
+ num_trades = len(data['returns'])
1221
+
1222
+ output += f"๐Ÿ“… {month}: {avg_return:+.2f}% avg return\n"
1223
+ output += f" โ€ข {num_trades} trades, ${total_investment:.2f} invested\n"
1224
+
1225
+ # Calculate recent vs early performance
1226
+ sorted_months = sorted(monthly_performance.keys())
1227
+ if len(sorted_months) >= 2:
1228
+ early_months = sorted_months[:len(sorted_months)//2]
1229
+ recent_months = sorted_months[len(sorted_months)//2:]
1230
+
1231
+ early_returns = []
1232
+ recent_returns = []
1233
+
1234
+ for month in early_months:
1235
+ early_returns.extend(monthly_performance[month]['returns'])
1236
+
1237
+ for month in recent_months:
1238
+ recent_returns.extend(monthly_performance[month]['returns'])
1239
+
1240
+ if early_returns and recent_returns:
1241
+ early_avg = sum(early_returns) / len(early_returns) * 100
1242
+ recent_avg = sum(recent_returns) / len(recent_returns) * 100
1243
+
1244
+ output += f"\n๐Ÿ“ˆ TREND ANALYSIS:\n"
1245
+ output += f"Early Period Avg: {early_avg:+.2f}% ({len(early_returns)} trades)\n"
1246
+ output += f"Recent Period Avg: {recent_avg:+.2f}% ({len(recent_returns)} trades)\n"
1247
+ output += f"Improvement: {recent_avg - early_avg:+.2f}% difference\n"
1248
+
1249
+ return output
1250
+
1251
+ except Exception as e:
1252
+ return f"ERROR calculating time analysis: {str(e)}"
1253
+
1254
  def clear_terminal():
1255
  """Clear terminal output"""
1256
  return "๐Ÿ–ฅ๏ธ VM Terminal Ready\n$ "
 
1540
  )
1541
  refresh_investment_btn = gr.Button("๐Ÿ”„ Refresh Investment Performance", variant="primary", size="lg")
1542
 
1543
+ gr.Markdown("### ๐Ÿงฎ Trading Statistics & Analysis")
1544
+ gr.Markdown("Calculate interesting metrics from your trading data")
1545
+
1546
+ with gr.Row():
1547
+ calc_sequential_btn = gr.Button("๐Ÿ“ˆ Sequential Reinvestment P&L%", variant="secondary", size="sm")
1548
+ calc_equal_weight_btn = gr.Button("โš–๏ธ Equal Weight Portfolio P&L%", variant="secondary", size="sm")
1549
+ calc_best_worst_btn = gr.Button("๐Ÿ† Best vs Worst Performers", variant="secondary", size="sm")
1550
+
1551
+ with gr.Row():
1552
+ calc_win_rate_btn = gr.Button("๐ŸŽฏ Win Rate & Avg Returns", variant="secondary", size="sm")
1553
+ calc_risk_metrics_btn = gr.Button("โš ๏ธ Risk Metrics & Volatility", variant="secondary", size="sm")
1554
+ calc_time_analysis_btn = gr.Button("โฐ Time-based Performance", variant="secondary", size="sm")
1555
+
1556
+ stats_output = gr.Textbox(
1557
+ label="Statistical Analysis Results",
1558
+ lines=8,
1559
+ interactive=False,
1560
+ elem_classes=["gr-textbox"]
1561
+ )
1562
+
1563
  gr.Markdown("### ๐Ÿ”ง Debug API Calls")
1564
  debug_output = gr.Textbox(
1565
  label="Debug Output",
 
1712
  outputs=[debug_output]
1713
  )
1714
 
1715
+ # Trading Statistics buttons
1716
+ calc_sequential_btn.click(
1717
+ fn=calculate_sequential_reinvestment,
1718
+ outputs=[stats_output]
1719
+ )
1720
+ calc_equal_weight_btn.click(
1721
+ fn=calculate_equal_weight_portfolio,
1722
+ outputs=[stats_output]
1723
+ )
1724
+ calc_best_worst_btn.click(
1725
+ fn=calculate_best_worst_performers,
1726
+ outputs=[stats_output]
1727
+ )
1728
+ calc_win_rate_btn.click(
1729
+ fn=calculate_win_rate_metrics,
1730
+ outputs=[stats_output]
1731
+ )
1732
+ calc_risk_metrics_btn.click(
1733
+ fn=calculate_risk_metrics,
1734
+ outputs=[stats_output]
1735
+ )
1736
+ calc_time_analysis_btn.click(
1737
+ fn=calculate_time_analysis,
1738
+ outputs=[stats_output]
1739
+ )
1740
+
1741
  # VM Terminal tab - RADICAL FIX: Use <pre> instead of <br> and force scroll with element replacement
1742
  def run_and_clear(cmd, output, history):
1743
  new_output, _, new_history = run_vm_command(cmd, output, history)