Add comprehensive trading statistics and analysis buttons
Browse filesAdded 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>
|
@@ -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)
|