import random import matplotlib.pyplot as plt import numpy as np from scipy.stats import gaussian_kde import tkinter as tk from tkinter import ttk, messagebox from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import json from datetime import datetime import os def trading_simulation(initial_capital, investment_percent, iterations, returns, probs): """Simulate a single person's trading strategy with custom returns and probabilities""" capital = initial_capital capital_history = [capital] total_prob = sum(probs) if total_prob != 1: probs = [p / total_prob for p in probs] for _ in range(iterations): investment = capital * investment_percent outcome = random.choices(range(len(returns)), weights=probs, k=1)[0] capital += investment * (returns[outcome] - 1) capital_history.append(capital) return capital_history def simulate_multiple_people(initial_capital, investment_percent, people, iterations, returns, probs): """Simulate multiple people trading""" return [trading_simulation(initial_capital, investment_percent, iterations, returns, probs) for _ in range(people)] def create_plots(all_histories, investment_percent, iterations, initial_capital): """Create evolution and distribution plots with larger size""" fig1, ax1 = plt.subplots(figsize=(8, 5)) for history in all_histories: ax1.plot(history, alpha=0.3) ax1.set_title(f'Capital Evolution ({len(all_histories)} People, {investment_percent * 100}%, {iterations} Trades)') ax1.set_xlabel('Number of Trades') ax1.set_ylabel('Capital') ax1.grid(True) final_capitals = np.array([history[-1] for history in all_histories]) capital_changes = ((final_capitals - initial_capital) / initial_capital) * 100 log_changes = np.where( capital_changes >= 0, np.log10(1 + capital_changes / 100), -np.log10(1 + np.abs(capital_changes) / 100) ) kde = gaussian_kde(log_changes) x_min, x_max = np.percentile(log_changes, [5, 95]) if x_max - x_min < 0.1: x_min, x_max = min(log_changes, default=-2), max(log_changes, default=2) x_range = np.linspace(x_min, x_max, 200) density = kde(x_range) fig2, ax2 = plt.subplots(figsize=(8, 5)) ax2.plot(x_range, density, color='blue', label='Probability Density') ax2.fill_between(x_range, 0, density, where=(x_range < 0), color='red', alpha=0.3, label='Loss') ax2.fill_between(x_range, 0, density, where=(x_range > 0), color='green', alpha=0.3, label='Profit') ax2.axvline(x=0, color='black', linestyle='--', alpha=0.5, label='No Change (0%)') ax2.set_title(f'Final Capital Change Distribution ({investment_percent * 100}%)') ax2.set_xlabel('Log of % Change') ax2.set_ylabel('Density') ax2.grid(True) ax2.legend() ax2.minorticks_on() ax2.grid(which='minor', alpha=0.2) return fig1, fig2 def run_simulation(): """Run the simulation with user inputs""" try: initial_capital = float(entry_initial_capital.get()) investment_percent = float(entry_investment_percent.get()) / 100 people = int(entry_people.get()) iterations = int(entry_iterations.get()) returns = [] probs = [] for return_entry, prob_entry in probability_entries: return_val = return_entry.get().strip() prob_val = prob_entry.get().strip() if not return_val or not prob_val: raise ValueError("All return and probability fields must be filled.") returns.append(float(return_val)) probs.append(float(prob_val) / 100) # 只检查概率是否为负数,不限制return if any(p < 0 for p in probs): raise ValueError("Probabilities must be non-negative.") if initial_capital < 0 or investment_percent < 0 or people <= 0 or iterations <= 0: raise ValueError("Initial capital, investment %, people, and iterations must be positive.") all_histories = simulate_multiple_people(initial_capital, investment_percent, people, iterations, returns, probs) final_capitals = np.array([history[-1] for history in all_histories]) result_text.delete(1.0, tk.END) result_text.insert(tk.END, f"Initial Capital: {initial_capital:.2f}\n") result_text.insert(tk.END, f"Investment Percent: {investment_percent * 100:.2f}%\n") result_text.insert(tk.END, f"Number of People: {people}\n") result_text.insert(tk.END, f"Number of Trades: {iterations}\n") result_text.insert(tk.END, f"Average Final Capital: {np.mean(final_capitals):.2f}\n") result_text.insert(tk.END, f"Median Final Capital: {np.median(final_capitals):.2f}\n") result_text.insert(tk.END, f"Max Final Capital: {np.max(final_capitals):.2f}\n") result_text.insert(tk.END, f"Min Final Capital: {np.min(final_capitals):.2f}\n") result_text.insert(tk.END, f"Average Capital Change: {((np.mean(final_capitals) - initial_capital) / initial_capital * 100):.2f}%\n") if save_to_history.get(): # Only save if checkbox is checked run_data = { "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "initial_capital": initial_capital, "investment_percent": investment_percent * 100, "people": people, "iterations": iterations, "returns": returns, "probs": [p * 100 for p in probs], "avg_final_capital": float(np.mean(final_capitals)) } run_history.append(run_data) save_history_to_file() update_history_dropdown() for widget in tab1.winfo_children(): widget.destroy() for widget in tab2.winfo_children(): widget.destroy() fig1, fig2 = create_plots(all_histories, investment_percent, iterations, initial_capital) canvas1 = FigureCanvasTkAgg(fig1, master=tab1) canvas1.draw() canvas1.get_tk_widget().pack(fill=tk.BOTH, expand=1) canvas2 = FigureCanvasTkAgg(fig2, master=tab2) canvas2.draw() canvas2.get_tk_widget().pack(fill=tk.BOTH, expand=1) except ValueError as e: messagebox.showerror("Input Error", f"Invalid input: {e}") def add_probability_pair(return_val="", prob_val=""): """Add a new return-probability pair with optional default values""" row = len(probability_entries) + 3 return_label = tk.Label(inner_input_frame, text=f"Return {len(probability_entries) + 1}:") return_label.grid(row=row, column=0, padx=5, pady=2, sticky="e") return_entry = tk.Entry(inner_input_frame, width=10) return_entry.insert(0, return_val) return_entry.grid(row=row, column=1, padx=5, pady=2) prob_label = tk.Label(inner_input_frame, text=f"Prob {len(probability_entries) + 1} (%):") prob_label.grid(row=row, column=2, padx=5, pady=2, sticky="e") prob_entry = tk.Entry(inner_input_frame, width=10) prob_entry.insert(0, prob_val) prob_entry.grid(row=row, column=3, padx=5, pady=2) probability_entries.append((return_entry, prob_entry)) update_input_scrollbar() def remove_probability_pair(): """Remove the last return-probability pair""" if probability_entries: last_return_entry, last_prob_entry = probability_entries.pop() last_return_entry.master.grid_forget() # Remove label last_prob_entry.master.grid_forget() # Remove label last_return_entry.destroy() last_prob_entry.destroy() update_input_scrollbar() def update_input_scrollbar(): """Update the scrollbar for input frame""" inner_input_frame.update_idletasks() input_canvas.configure(scrollregion=input_canvas.bbox("all")) def save_history_to_file(): """Save run history to a text file""" with open("cache/run_history.txt", "w") as f: json.dump(run_history, f, indent=4) def load_history_from_file(): """Load run history from a text file""" global run_history if os.path.exists("cache/run_history.txt"): with open("cache/run_history.txt", "r") as f: run_history = json.load(f) else: run_history = [] def update_history_dropdown(): """Update the history dropdown with saved runs""" history_menu['menu'].delete(0, 'end') for i, run in enumerate(run_history): label = f"{run['timestamp']} - Avg: {run['avg_final_capital']:.2f}" history_menu['menu'].add_command(label=label, command=lambda idx=i: load_history(idx)) def load_history(index): """Load a previous run's parameters""" run = run_history[index] entry_initial_capital.delete(0, tk.END) entry_initial_capital.insert(0, str(run['initial_capital'])) entry_investment_percent.delete(0, tk.END) entry_investment_percent.insert(0, str(run['investment_percent'])) entry_people.delete(0, tk.END) entry_people.insert(0, str(run['people'])) entry_iterations.delete(0, tk.END) entry_iterations.insert(0, str(run['iterations'])) for return_entry, prob_entry in probability_entries: return_entry.master.grid_forget() prob_entry.master.grid_forget() return_entry.destroy() prob_entry.destroy() probability_entries.clear() for r, p in zip(run['returns'], run['probs']): add_probability_pair(str(r), str(p)) # Create GUI window root = tk.Tk() root.title("Trading Simulation with Custom Returns") root.geometry("1200x900") window_height = 900 top_height = int(window_height * 0.3) plot_height = int(window_height * 0.7) # Load history at startup run_history = [] load_history_from_file() # Top frame top_frame = tk.Frame(root, height=top_height) top_frame.pack(side=tk.TOP, fill=tk.X, padx=10, pady=10) top_frame.pack_propagate(False) # Input frame with scrollbar input_frame = tk.LabelFrame(top_frame, text="Simulation Parameters", padx=10, pady=10) input_frame.pack(side=tk.LEFT, fill=tk.Y, expand=1) input_canvas = tk.Canvas(input_frame) input_scrollbar = tk.Scrollbar(input_frame, orient="vertical", command=input_canvas.yview) inner_input_frame = tk.Frame(input_canvas) input_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) input_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) input_canvas.create_window((0, 0), window=inner_input_frame, anchor="nw") input_canvas.configure(yscrollcommand=input_scrollbar.set) tk.Label(inner_input_frame, text="Initial Capital:").grid(row=0, column=0, padx=5, pady=5, sticky="e") entry_initial_capital = tk.Entry(inner_input_frame, width=15) entry_initial_capital.insert(0, "10000.0") entry_initial_capital.grid(row=0, column=1, padx=5, pady=5) tk.Label(inner_input_frame, text="Investment %:").grid(row=0, column=2, padx=5, pady=5, sticky="e") entry_investment_percent = tk.Entry(inner_input_frame, width=15) entry_investment_percent.insert(0, "10.0") entry_investment_percent.grid(row=0, column=3, padx=5, pady=5) tk.Label(inner_input_frame, text="People:").grid(row=1, column=0, padx=5, pady=5, sticky="e") entry_people = tk.Entry(inner_input_frame, width=15) entry_people.insert(0, "100") entry_people.grid(row=1, column=1, padx=5, pady=5) tk.Label(inner_input_frame, text="Trades:").grid(row=1, column=2, padx=5, pady=5, sticky="e") entry_iterations = tk.Entry(inner_input_frame, width=15) entry_iterations.insert(0, "100") entry_iterations.grid(row=1, column=3, padx=5, pady=5) run_button = tk.Button(inner_input_frame, text="Run Simulation", command=run_simulation) run_button.grid(row=2, column=0, columnspan=2, pady=5) add_button = tk.Button(inner_input_frame, text="+", command=lambda: add_probability_pair()) add_button.grid(row=2, column=3, padx=5, pady=5) remove_button = tk.Button(inner_input_frame, text="-", command=remove_probability_pair) remove_button.grid(row=2, column=2, padx=5, pady=5) default_returns = [20.0, 10.0, 5.0, 2.0, 1.5, 1.2, 1.05, 1.0, 0.9, 0.5, 0.4, 0.2, 0.1, -10.0] default_probs = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 20.0, 20.0, 9.0, 15.0, 15.0, 10.0] probability_entries = [] for r, p in zip(default_returns, default_probs): add_probability_pair(str(r), str(p)) inner_input_frame.bind("", lambda e: update_input_scrollbar()) # Result frame result_frame = tk.LabelFrame(top_frame, text="Results", padx=10, pady=10) result_frame.pack(side=tk.RIGHT, fill=tk.Y, expand=1) result_text = tk.Text(result_frame, height=10, width=40) result_text.pack(side=tk.TOP, fill=tk.BOTH, expand=1) result_scrollbar = tk.Scrollbar(result_frame, orient="vertical", command=result_text.yview) result_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) result_text.configure(yscrollcommand=result_scrollbar.set) history_frame = tk.Frame(result_frame) history_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5) tk.Label(history_frame, text="Run History:").pack(side=tk.LEFT, padx=5) history_var = tk.StringVar() history_menu = tk.OptionMenu(history_frame, history_var, "No runs yet") history_menu.pack(side=tk.LEFT, fill=tk.X, expand=1) # Checkbox for saving to history save_to_history = tk.BooleanVar(value=True) # Default checked save_checkbox = tk.Checkbutton(history_frame, text="Save to History", variable=save_to_history) save_checkbox.pack(side=tk.LEFT, padx=5) # Populate history dropdown with loaded data update_history_dropdown() # Plot frame with tabs plot_outer_frame = tk.LabelFrame(root, text="Plots", padx=10, pady=10, height=plot_height) plot_outer_frame.pack(fill=tk.BOTH, expand=1) plot_outer_frame.pack_propagate(False) notebook = ttk.Notebook(plot_outer_frame) notebook.pack(fill=tk.BOTH, expand=1) tab1 = ttk.Frame(notebook) tab2 = ttk.Frame(notebook) notebook.add(tab1, text="Capital Evolution") notebook.add(tab2, text="Distribution") root.mainloop()