Update
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
"""
|
||||
Martingale Strategy Simulation
|
||||
Analyzes the statistical properties and risk of martingale strategies
|
||||
"""
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
matplotlib.use('Agg') # Use non-interactive backend
|
||||
import matplotlib.pyplot as plt
|
||||
from scipy import stats
|
||||
import pandas as pd
|
||||
|
||||
class MartingaleSimulator:
|
||||
def __init__(self, initial_balance=10000, base_lot=0.01, win_prob=0.5,
|
||||
win_amount=10, loss_amount=10, max_losses=10):
|
||||
self.initial_balance = initial_balance
|
||||
self.base_lot = base_lot
|
||||
self.win_prob = win_prob
|
||||
self.win_amount = win_amount
|
||||
self.loss_amount = loss_amount
|
||||
self.max_losses = max_losses
|
||||
|
||||
def calculate_position_size(self, consecutive_losses):
|
||||
"""Calculate position size after n consecutive losses"""
|
||||
return self.base_lot * (2 ** consecutive_losses)
|
||||
|
||||
def calculate_required_capital(self, consecutive_losses):
|
||||
"""Calculate total capital needed after n losses"""
|
||||
return self.base_lot * (2 ** (consecutive_losses + 1) - 1)
|
||||
|
||||
def simulate_trade_sequence(self, num_trades=1000):
|
||||
"""Simulate a sequence of trades"""
|
||||
balance = self.initial_balance
|
||||
consecutive_losses = 0
|
||||
trades = []
|
||||
ruin = False
|
||||
|
||||
for i in range(num_trades):
|
||||
if balance <= 0:
|
||||
ruin = True
|
||||
break
|
||||
|
||||
# Calculate position size
|
||||
position_size = self.calculate_position_size(consecutive_losses)
|
||||
required_capital = self.calculate_required_capital(consecutive_losses)
|
||||
|
||||
# Check if we have enough capital
|
||||
if required_capital > balance:
|
||||
ruin = True
|
||||
break
|
||||
|
||||
# Simulate trade outcome
|
||||
is_win = np.random.random() < self.win_prob
|
||||
|
||||
if is_win:
|
||||
# Win: recover all previous losses
|
||||
profit = position_size * self.win_amount
|
||||
balance += profit
|
||||
consecutive_losses = 0
|
||||
outcome = 'Win'
|
||||
else:
|
||||
# Loss: add to consecutive losses
|
||||
loss = position_size * self.loss_amount
|
||||
balance -= loss
|
||||
consecutive_losses += 1
|
||||
outcome = 'Loss'
|
||||
|
||||
trades.append({
|
||||
'trade': i + 1,
|
||||
'balance': balance,
|
||||
'position_size': position_size,
|
||||
'consecutive_losses': consecutive_losses,
|
||||
'outcome': outcome,
|
||||
'profit': profit if is_win else -loss
|
||||
})
|
||||
|
||||
return pd.DataFrame(trades), ruin
|
||||
|
||||
def monte_carlo_analysis(self, num_simulations=1000, num_trades=100):
|
||||
"""Run Monte Carlo simulation"""
|
||||
results = []
|
||||
ruin_count = 0
|
||||
|
||||
for sim in range(num_simulations):
|
||||
trades_df, ruin = self.simulate_trade_sequence(num_trades)
|
||||
if ruin:
|
||||
ruin_count += 1
|
||||
final_balance = 0
|
||||
else:
|
||||
final_balance = trades_df['balance'].iloc[-1]
|
||||
|
||||
results.append({
|
||||
'simulation': sim,
|
||||
'final_balance': final_balance,
|
||||
'ruin': ruin,
|
||||
'total_trades': len(trades_df),
|
||||
'max_consecutive_losses': trades_df['consecutive_losses'].max() if len(trades_df) > 0 else 0
|
||||
})
|
||||
|
||||
return pd.DataFrame(results), ruin_count / num_simulations
|
||||
|
||||
def plot_simulation_results(self, num_simulations=100):
|
||||
"""Plot simulation results"""
|
||||
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
|
||||
|
||||
# Run simulations
|
||||
results_df, ruin_prob = self.monte_carlo_analysis(num_simulations)
|
||||
|
||||
# Plot 1: Final Balance Distribution
|
||||
axes[0, 0].hist(results_df['final_balance'], bins=50, edgecolor='black')
|
||||
axes[0, 0].axvline(self.initial_balance, color='red', linestyle='--',
|
||||
label=f'Initial Balance: ${self.initial_balance:,.0f}')
|
||||
axes[0, 0].set_xlabel('Final Balance ($)')
|
||||
axes[0, 0].set_ylabel('Frequency')
|
||||
axes[0, 0].set_title(f'Final Balance Distribution\nRuin Probability: {ruin_prob:.2%}')
|
||||
axes[0, 0].legend()
|
||||
axes[0, 0].grid(True, alpha=0.3)
|
||||
|
||||
# Plot 2: Ruin Probability vs Consecutive Losses
|
||||
max_losses_range = range(1, self.max_losses + 1)
|
||||
ruin_probs = []
|
||||
for n in max_losses_range:
|
||||
required = self.calculate_required_capital(n)
|
||||
ruin_probs.append(1.0 if required > self.initial_balance else 0.0)
|
||||
|
||||
axes[0, 1].plot(max_losses_range, ruin_probs, 'ro-', linewidth=2, markersize=8)
|
||||
axes[0, 1].set_xlabel('Consecutive Losses')
|
||||
axes[0, 1].set_ylabel('Ruin Probability')
|
||||
axes[0, 1].set_title('Ruin Probability vs Consecutive Losses')
|
||||
axes[0, 1].grid(True, alpha=0.3)
|
||||
axes[0, 1].set_ylim([-0.1, 1.1])
|
||||
|
||||
# Plot 3: Position Size Growth
|
||||
losses_range = range(0, self.max_losses + 1)
|
||||
position_sizes = [self.calculate_position_size(n) for n in losses_range]
|
||||
required_capital = [self.calculate_required_capital(n) for n in losses_range]
|
||||
|
||||
ax3_twin = axes[1, 0].twinx()
|
||||
line1 = axes[1, 0].plot(losses_range, position_sizes, 'b-o',
|
||||
label='Position Size', linewidth=2)
|
||||
line2 = ax3_twin.plot(losses_range, required_capital, 'r-s',
|
||||
label='Required Capital', linewidth=2)
|
||||
|
||||
axes[1, 0].set_xlabel('Consecutive Losses')
|
||||
axes[1, 0].set_ylabel('Position Size (Lots)', color='b')
|
||||
ax3_twin.set_ylabel('Required Capital ($)', color='r')
|
||||
axes[1, 0].set_title('Position Size and Capital Requirements')
|
||||
axes[1, 0].grid(True, alpha=0.3)
|
||||
|
||||
# Combine legends
|
||||
lines = line1 + line2
|
||||
labels = [l.get_label() for l in lines]
|
||||
axes[1, 0].legend(lines, labels, loc='upper left')
|
||||
|
||||
# Plot 4: Sample Trade Sequence
|
||||
sample_trades, _ = self.simulate_trade_sequence(50)
|
||||
axes[1, 1].plot(sample_trades['trade'], sample_trades['balance'],
|
||||
'g-', linewidth=2, label='Balance')
|
||||
axes[1, 1].axhline(self.initial_balance, color='red', linestyle='--',
|
||||
label='Initial Balance')
|
||||
axes[1, 1].set_xlabel('Trade Number')
|
||||
axes[1, 1].set_ylabel('Balance ($)')
|
||||
axes[1, 1].set_title('Sample Trade Sequence (50 trades)')
|
||||
axes[1, 1].legend()
|
||||
axes[1, 1].grid(True, alpha=0.3)
|
||||
|
||||
plt.tight_layout()
|
||||
return fig
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Create simulator
|
||||
simulator = MartingaleSimulator(
|
||||
initial_balance=10000,
|
||||
base_lot=0.01,
|
||||
win_prob=0.5,
|
||||
win_amount=10,
|
||||
loss_amount=10,
|
||||
max_losses=10
|
||||
)
|
||||
|
||||
# Run analysis
|
||||
print("Running Martingale Simulation...")
|
||||
results_df, ruin_prob = simulator.monte_carlo_analysis(num_simulations=1000, num_trades=100)
|
||||
|
||||
print(f"\n=== Martingale Strategy Analysis ===")
|
||||
print(f"Initial Balance: ${simulator.initial_balance:,.2f}")
|
||||
print(f"Win Probability: {simulator.win_prob:.1%}")
|
||||
print(f"\nMonte Carlo Results (1000 simulations):")
|
||||
print(f"Ruin Probability: {ruin_prob:.2%}")
|
||||
print(f"Mean Final Balance: ${results_df['final_balance'].mean():,.2f}")
|
||||
print(f"Median Final Balance: ${results_df['final_balance'].median():,.2f}")
|
||||
print(f"Std Dev Final Balance: ${results_df['final_balance'].std():,.2f}")
|
||||
print(f"Max Final Balance: ${results_df['final_balance'].max():,.2f}")
|
||||
print(f"Min Final Balance: ${results_df['final_balance'].min():,.2f}")
|
||||
|
||||
# Calculate statistics
|
||||
profitable_sims = (results_df['final_balance'] > simulator.initial_balance).sum()
|
||||
print(f"\nProfitable Simulations: {profitable_sims}/{len(results_df)} ({profitable_sims/len(results_df):.1%})")
|
||||
print(f"Average Max Consecutive Losses: {results_df['max_consecutive_losses'].mean():.2f}")
|
||||
|
||||
# Generate plots
|
||||
import os
|
||||
# Get the script directory and construct path to figures
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
figures_dir = os.path.join(script_dir, '..', 'figures')
|
||||
figures_path = os.path.abspath(figures_dir)
|
||||
os.makedirs(figures_path, exist_ok=True)
|
||||
|
||||
fig = simulator.plot_simulation_results(num_simulations=100)
|
||||
output_path = os.path.join(figures_path, 'martingale_analysis.png')
|
||||
plt.savefig(output_path, dpi=300, bbox_inches='tight')
|
||||
print(f"\nFigure saved to {output_path}")
|
||||
plt.close()
|
||||
Reference in New Issue
Block a user