Update
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# Trading Strategy Simulations
|
||||
|
||||
This directory contains Python scripts for simulating and analyzing advanced trading techniques.
|
||||
|
||||
## Scripts
|
||||
|
||||
### 1. martingale_simulation.py
|
||||
Analyzes the statistical properties and risk of martingale strategies.
|
||||
|
||||
**Key Analyses:**
|
||||
- Ruin probability calculations
|
||||
- Position size growth
|
||||
- Required capital analysis
|
||||
- Monte Carlo simulations
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
python martingale_simulation.py
|
||||
```
|
||||
|
||||
**Output:**
|
||||
- `martingale_analysis.png`: Comprehensive analysis plots
|
||||
- Console output with statistics
|
||||
|
||||
### 2. trailing_stop_analysis.py
|
||||
Compares fixed stop loss vs trailing stop loss performance.
|
||||
|
||||
**Key Analyses:**
|
||||
- Return distribution comparison
|
||||
- Sharpe ratio improvement
|
||||
- Exit timing analysis
|
||||
- Sample price path visualization
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
python trailing_stop_analysis.py
|
||||
```
|
||||
|
||||
**Output:**
|
||||
- `trailing_stop_analysis.png`: Comparison plots
|
||||
- Console output with performance metrics
|
||||
|
||||
### 3. partial_exit_analysis.py
|
||||
Analyzes the statistical benefits of partial exits.
|
||||
|
||||
**Key Analyses:**
|
||||
- Variance reduction calculation
|
||||
- Sharpe ratio optimization
|
||||
- Optimal exit percentage
|
||||
- Return distribution comparison
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
python partial_exit_analysis.py
|
||||
```
|
||||
|
||||
**Output:**
|
||||
- `partial_exit_analysis.png`: Analysis plots
|
||||
- Console output with optimization results
|
||||
|
||||
### 4. grid_trading_analysis.py
|
||||
Analyzes grid trading performance in different market conditions.
|
||||
|
||||
**Key Analyses:**
|
||||
- Mean-reverting vs trending market performance
|
||||
- Optimal grid spacing
|
||||
- Trade frequency analysis
|
||||
- Profit distribution
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
python grid_trading_analysis.py
|
||||
```
|
||||
|
||||
**Output:**
|
||||
- `grid_trading_analysis.png`: Market condition comparison
|
||||
- Console output with performance metrics
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Running All Simulations
|
||||
|
||||
```bash
|
||||
# Run all simulations
|
||||
python martingale_simulation.py
|
||||
python trailing_stop_analysis.py
|
||||
python partial_exit_analysis.py
|
||||
python grid_trading_analysis.py
|
||||
```
|
||||
|
||||
## Output Location
|
||||
|
||||
All figures are saved to `../figures/` directory:
|
||||
- `martingale_analysis.png`
|
||||
- `trailing_stop_analysis.png`
|
||||
- `partial_exit_analysis.png`
|
||||
- `grid_trading_analysis.png`
|
||||
|
||||
## Mathematical Foundations
|
||||
|
||||
These simulations implement:
|
||||
- Geometric Brownian Motion for price simulation
|
||||
- Ornstein-Uhlenbeck process for mean-reverting prices
|
||||
- Monte Carlo methods for statistical analysis
|
||||
- Kelly Criterion for position sizing
|
||||
- Sharpe ratio and other risk-adjusted metrics
|
||||
|
||||
## Notes
|
||||
|
||||
- Simulations use random number generation - results may vary slightly between runs
|
||||
- For reproducible results, set random seeds in scripts
|
||||
- Adjust parameters in each script to match your trading conditions
|
||||
- Results are illustrative - actual trading results will vary
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,250 @@
|
||||
"""
|
||||
Grid Trading Strategy Analysis
|
||||
Analyzes grid trading performance in different market conditions
|
||||
"""
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
matplotlib.use('Agg') # Use non-interactive backend
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
from scipy.stats import norm
|
||||
|
||||
class GridTradingAnalyzer:
|
||||
def __init__(self, initial_price=100, grid_spacing=1.0, num_levels=10):
|
||||
self.initial_price = initial_price
|
||||
self.grid_spacing = grid_spacing
|
||||
self.num_levels = num_levels
|
||||
|
||||
def create_grid(self):
|
||||
"""Create grid price levels"""
|
||||
grid_levels = []
|
||||
for i in range(-self.num_levels, self.num_levels + 1):
|
||||
price = self.initial_price + i * self.grid_spacing
|
||||
grid_levels.append(price)
|
||||
return np.array(grid_levels)
|
||||
|
||||
def simulate_mean_reverting_price(self, num_steps=1000, mean_reversion_speed=0.1,
|
||||
volatility=0.5, mean_price=100):
|
||||
"""Simulate mean-reverting price (Ornstein-Uhlenbeck process)"""
|
||||
prices = [self.initial_price]
|
||||
dt = 1.0 / num_steps
|
||||
|
||||
for _ in range(num_steps):
|
||||
dW = np.random.normal(0, np.sqrt(dt))
|
||||
dS = mean_reversion_speed * (mean_price - prices[-1]) * dt + volatility * dW
|
||||
prices.append(prices[-1] + dS)
|
||||
|
||||
return np.array(prices)
|
||||
|
||||
def simulate_trending_price(self, num_steps=1000, drift=0.01, volatility=0.5):
|
||||
"""Simulate trending price (geometric Brownian motion)"""
|
||||
prices = [self.initial_price]
|
||||
dt = 1.0 / num_steps
|
||||
|
||||
for _ in range(num_steps):
|
||||
dW = np.random.normal(0, np.sqrt(dt))
|
||||
dS = drift * prices[-1] * dt + volatility * prices[-1] * dW
|
||||
prices.append(prices[-1] + dS)
|
||||
|
||||
return np.array(prices)
|
||||
|
||||
def calculate_grid_profits(self, prices, grid_levels, position_size=0.01):
|
||||
"""Calculate profits from grid trading"""
|
||||
positions = {} # Track open positions at each grid level
|
||||
total_profit = 0
|
||||
trades = []
|
||||
|
||||
for price in prices:
|
||||
# Check for grid hits
|
||||
for i, grid_price in enumerate(grid_levels):
|
||||
# Buy signal: price hits grid from above
|
||||
if price <= grid_price + 0.1 and price >= grid_price - 0.1:
|
||||
if i not in positions or positions[i] == 'sell':
|
||||
# Open buy position
|
||||
positions[i] = 'buy'
|
||||
trades.append({
|
||||
'type': 'buy',
|
||||
'price': grid_price,
|
||||
'time': len(trades)
|
||||
})
|
||||
|
||||
# Sell signal: price hits grid from below
|
||||
if price >= grid_price - 0.1 and price <= grid_price + 0.1:
|
||||
if i in positions and positions[i] == 'buy':
|
||||
# Close buy position (profit)
|
||||
profit = (price - grid_price) * position_size
|
||||
total_profit += profit
|
||||
del positions[i]
|
||||
trades.append({
|
||||
'type': 'sell',
|
||||
'price': price,
|
||||
'profit': profit,
|
||||
'time': len(trades)
|
||||
})
|
||||
|
||||
# Close remaining positions at final price
|
||||
final_price = prices[-1]
|
||||
for level, pos_type in positions.items():
|
||||
if pos_type == 'buy':
|
||||
profit = (final_price - grid_levels[level]) * position_size
|
||||
total_profit += profit
|
||||
|
||||
return total_profit, trades
|
||||
|
||||
def analyze_grid_trading(self, num_simulations=100, market_type='mean_reverting'):
|
||||
"""Analyze grid trading performance"""
|
||||
results = []
|
||||
|
||||
for sim in range(num_simulations):
|
||||
if market_type == 'mean_reverting':
|
||||
prices = self.simulate_mean_reverting_price()
|
||||
else:
|
||||
prices = self.simulate_trending_price()
|
||||
|
||||
grid_levels = self.create_grid()
|
||||
profit, trades = self.calculate_grid_profits(prices, grid_levels)
|
||||
|
||||
results.append({
|
||||
'simulation': sim,
|
||||
'profit': profit,
|
||||
'num_trades': len([t for t in trades if t['type'] == 'sell']),
|
||||
'final_price': prices[-1],
|
||||
'price_range': prices.max() - prices.min(),
|
||||
'max_drawdown': self.calculate_max_drawdown(prices)
|
||||
})
|
||||
|
||||
return pd.DataFrame(results)
|
||||
|
||||
def calculate_max_drawdown(self, prices):
|
||||
"""Calculate maximum drawdown"""
|
||||
peak = prices[0]
|
||||
max_dd = 0
|
||||
|
||||
for price in prices:
|
||||
if price > peak:
|
||||
peak = price
|
||||
dd = (peak - price) / peak
|
||||
if dd > max_dd:
|
||||
max_dd = dd
|
||||
|
||||
return max_dd
|
||||
|
||||
def optimize_grid_spacing(self, num_simulations=50, spacing_range=np.arange(0.5, 5.0, 0.5)):
|
||||
"""Find optimal grid spacing"""
|
||||
results = []
|
||||
|
||||
for spacing in spacing_range:
|
||||
self.grid_spacing = spacing
|
||||
df = self.analyze_grid_trading(num_simulations=num_simulations,
|
||||
market_type='mean_reverting')
|
||||
|
||||
results.append({
|
||||
'spacing': spacing,
|
||||
'mean_profit': df['profit'].mean(),
|
||||
'std_profit': df['profit'].std(),
|
||||
'sharpe_ratio': df['profit'].mean() / df['profit'].std() if df['profit'].std() > 0 else 0,
|
||||
'mean_trades': df['num_trades'].mean()
|
||||
})
|
||||
|
||||
return pd.DataFrame(results)
|
||||
|
||||
def plot_analysis(self, num_simulations=100):
|
||||
"""Plot analysis results"""
|
||||
# Analyze in different market conditions
|
||||
mean_reverting_results = self.analyze_grid_trading(num_simulations, 'mean_reverting')
|
||||
trending_results = self.analyze_grid_trading(num_simulations, 'trending')
|
||||
optimization_df = self.optimize_grid_spacing()
|
||||
|
||||
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
|
||||
|
||||
# Plot 1: Profit Distribution Comparison
|
||||
axes[0, 0].hist(mean_reverting_results['profit'], bins=30, alpha=0.5,
|
||||
label='Mean Reverting Market', color='green', edgecolor='black')
|
||||
axes[0, 0].hist(trending_results['profit'], bins=30, alpha=0.5,
|
||||
label='Trending Market', color='red', edgecolor='black')
|
||||
axes[0, 0].axvline(0, color='black', linestyle='--', linewidth=2)
|
||||
axes[0, 0].set_xlabel('Total Profit')
|
||||
axes[0, 0].set_ylabel('Frequency')
|
||||
axes[0, 0].set_title('Grid Trading Profit Distribution by Market Type')
|
||||
axes[0, 0].legend()
|
||||
axes[0, 0].grid(True, alpha=0.3)
|
||||
|
||||
# Plot 2: Sample Price Path with Grid
|
||||
sample_prices = self.simulate_mean_reverting_price()
|
||||
grid_levels = self.create_grid()
|
||||
|
||||
axes[0, 1].plot(sample_prices, 'b-', linewidth=2, label='Price')
|
||||
for level in grid_levels:
|
||||
axes[0, 1].axhline(level, color='gray', linestyle='--', alpha=0.3)
|
||||
axes[0, 1].axhline(self.initial_price, color='red', linestyle='-',
|
||||
linewidth=2, label='Initial Price')
|
||||
axes[0, 1].set_xlabel('Time Step')
|
||||
axes[0, 1].set_ylabel('Price')
|
||||
axes[0, 1].set_title('Sample Price Path with Grid Levels')
|
||||
axes[0, 1].legend()
|
||||
axes[0, 1].grid(True, alpha=0.3)
|
||||
|
||||
# Plot 3: Optimal Grid Spacing
|
||||
axes[1, 0].plot(optimization_df['spacing'], optimization_df['sharpe_ratio'],
|
||||
'b-o', linewidth=2, markersize=8, label='Sharpe Ratio')
|
||||
optimal_idx = optimization_df['sharpe_ratio'].idxmax()
|
||||
optimal_spacing = optimization_df.loc[optimal_idx, 'spacing']
|
||||
axes[1, 0].axvline(optimal_spacing, color='red', linestyle='--',
|
||||
label=f'Optimal: {optimal_spacing:.2f}')
|
||||
axes[1, 0].set_xlabel('Grid Spacing')
|
||||
axes[1, 0].set_ylabel('Sharpe Ratio')
|
||||
axes[1, 0].set_title('Optimal Grid Spacing Analysis')
|
||||
axes[1, 0].legend()
|
||||
axes[1, 0].grid(True, alpha=0.3)
|
||||
|
||||
# Plot 4: Profit vs Number of Trades
|
||||
axes[1, 1].scatter(mean_reverting_results['num_trades'],
|
||||
mean_reverting_results['profit'],
|
||||
alpha=0.5, label='Mean Reverting', color='green')
|
||||
axes[1, 1].scatter(trending_results['num_trades'],
|
||||
trending_results['profit'],
|
||||
alpha=0.5, label='Trending', color='red')
|
||||
axes[1, 1].axhline(0, color='black', linestyle='--', linewidth=1)
|
||||
axes[1, 1].set_xlabel('Number of Trades')
|
||||
axes[1, 1].set_ylabel('Total Profit')
|
||||
axes[1, 1].set_title('Profit vs Trade Frequency')
|
||||
axes[1, 1].legend()
|
||||
axes[1, 1].grid(True, alpha=0.3)
|
||||
|
||||
plt.tight_layout()
|
||||
return fig, mean_reverting_results, trending_results, optimization_df
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyzer = GridTradingAnalyzer(initial_price=100, grid_spacing=1.0, num_levels=10)
|
||||
|
||||
print("Running Grid Trading Analysis...")
|
||||
fig, mr_results, tr_results, opt_df = analyzer.plot_analysis(num_simulations=100)
|
||||
|
||||
print("\n=== Grid Trading Strategy Analysis ===")
|
||||
print(f"\nMean Reverting Market:")
|
||||
print(f" Mean Profit: ${mr_results['profit'].mean():.2f}")
|
||||
print(f" Std Dev: ${mr_results['profit'].std():.2f}")
|
||||
print(f" Win Rate: {(mr_results['profit'] > 0).mean():.2%}")
|
||||
print(f" Mean Trades: {mr_results['num_trades'].mean():.1f}")
|
||||
|
||||
print(f"\nTrending Market:")
|
||||
print(f" Mean Profit: ${tr_results['profit'].mean():.2f}")
|
||||
print(f" Std Dev: ${tr_results['profit'].std():.2f}")
|
||||
print(f" Win Rate: {(tr_results['profit'] > 0).mean():.2%}")
|
||||
print(f" Mean Trades: {tr_results['num_trades'].mean():.1f}")
|
||||
|
||||
optimal_idx = opt_df['sharpe_ratio'].idxmax()
|
||||
print(f"\nOptimal Grid Spacing: {opt_df.loc[optimal_idx, 'spacing']:.2f}")
|
||||
print(f" Optimal Sharpe Ratio: {opt_df.loc[optimal_idx, 'sharpe_ratio']:.4f}")
|
||||
|
||||
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)
|
||||
|
||||
output_path = os.path.join(figures_path, 'grid_trading_analysis.png')
|
||||
plt.savefig(output_path, dpi=300, bbox_inches='tight')
|
||||
print(f"\nFigure saved to {output_path}")
|
||||
plt.close()
|
||||
@@ -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()
|
||||
@@ -0,0 +1,191 @@
|
||||
"""
|
||||
Partial Exit Strategy Analysis
|
||||
Analyzes the statistical benefits of partial exits
|
||||
"""
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
matplotlib.use('Agg') # Use non-interactive backend
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
from scipy.stats import norm
|
||||
|
||||
class PartialExitAnalyzer:
|
||||
def __init__(self, initial_price=100, drift=0.0001, volatility=0.02):
|
||||
self.initial_price = initial_price
|
||||
self.drift = drift
|
||||
self.volatility = volatility
|
||||
|
||||
def simulate_price_path(self, num_steps=1000, dt=1/252):
|
||||
"""Simulate price using geometric Brownian motion"""
|
||||
prices = [self.initial_price]
|
||||
|
||||
for _ in range(num_steps):
|
||||
dW = np.random.normal(0, np.sqrt(dt))
|
||||
dS = self.drift * prices[-1] * dt + self.volatility * prices[-1] * dW
|
||||
prices.append(prices[-1] + dS)
|
||||
|
||||
return np.array(prices)
|
||||
|
||||
def calculate_full_exit_return(self, prices, exit_time):
|
||||
"""Calculate return for full exit at exit_time"""
|
||||
exit_price = prices[exit_time]
|
||||
return (exit_price - self.initial_price) / self.initial_price
|
||||
|
||||
def calculate_partial_exit_return(self, prices, partial_exit_time,
|
||||
partial_exit_pct, final_exit_time):
|
||||
"""Calculate return for partial exit strategy"""
|
||||
partial_exit_price = prices[partial_exit_time]
|
||||
final_exit_price = prices[final_exit_time]
|
||||
|
||||
# Partial exit profit
|
||||
partial_profit = partial_exit_pct * (partial_exit_price - self.initial_price) / self.initial_price
|
||||
|
||||
# Remaining position profit
|
||||
remaining_profit = (1 - partial_exit_pct) * (final_exit_price - self.initial_price) / self.initial_price
|
||||
|
||||
total_return = partial_profit + remaining_profit
|
||||
return total_return, partial_profit, remaining_profit
|
||||
|
||||
def analyze_partial_exit(self, num_simulations=1000, num_steps=1000,
|
||||
partial_exit_pct=0.5, partial_exit_time=500):
|
||||
"""Analyze partial exit strategy"""
|
||||
results = []
|
||||
|
||||
for sim in range(num_simulations):
|
||||
prices = self.simulate_price_path(num_steps)
|
||||
|
||||
# Full exit at end
|
||||
full_return = self.calculate_full_exit_return(prices, len(prices) - 1)
|
||||
|
||||
# Partial exit strategy
|
||||
partial_return, partial_profit, remaining_profit = self.calculate_partial_exit_return(
|
||||
prices, partial_exit_time, partial_exit_pct, len(prices) - 1)
|
||||
|
||||
results.append({
|
||||
'simulation': sim,
|
||||
'final_price': prices[-1],
|
||||
'partial_exit_price': prices[partial_exit_time],
|
||||
'full_return': full_return,
|
||||
'partial_return': partial_return,
|
||||
'partial_profit': partial_profit,
|
||||
'remaining_profit': remaining_profit,
|
||||
'variance_reduction': np.var([partial_profit, remaining_profit]) - np.var([full_return])
|
||||
})
|
||||
|
||||
return pd.DataFrame(results)
|
||||
|
||||
def optimize_exit_percentage(self, num_simulations=500, exit_percentages=np.arange(0.1, 0.9, 0.1)):
|
||||
"""Find optimal partial exit percentage"""
|
||||
results = []
|
||||
|
||||
for exit_pct in exit_percentages:
|
||||
df = self.analyze_partial_exit(num_simulations=num_simulations,
|
||||
partial_exit_pct=exit_pct)
|
||||
|
||||
mean_return = df['partial_return'].mean()
|
||||
std_return = df['partial_return'].std()
|
||||
sharpe = mean_return / std_return if std_return > 0 else 0
|
||||
|
||||
results.append({
|
||||
'exit_percentage': exit_pct,
|
||||
'mean_return': mean_return,
|
||||
'std_return': std_return,
|
||||
'sharpe_ratio': sharpe,
|
||||
'variance_reduction': df['variance_reduction'].mean()
|
||||
})
|
||||
|
||||
return pd.DataFrame(results)
|
||||
|
||||
def plot_analysis(self, num_simulations=1000):
|
||||
"""Plot analysis results"""
|
||||
results_df = self.analyze_partial_exit(num_simulations)
|
||||
optimization_df = self.optimize_exit_percentage()
|
||||
|
||||
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
|
||||
|
||||
# Plot 1: Return Distribution Comparison
|
||||
axes[0, 0].hist(results_df['full_return'], bins=50, alpha=0.5,
|
||||
label='Full Exit', color='red', edgecolor='black')
|
||||
axes[0, 0].hist(results_df['partial_return'], bins=50, alpha=0.5,
|
||||
label='Partial Exit (50%)', color='green', edgecolor='black')
|
||||
axes[0, 0].axvline(0, color='black', linestyle='--', linewidth=1)
|
||||
axes[0, 0].set_xlabel('Return')
|
||||
axes[0, 0].set_ylabel('Frequency')
|
||||
axes[0, 0].set_title('Return Distribution: Full vs Partial Exit')
|
||||
axes[0, 0].legend()
|
||||
axes[0, 0].grid(True, alpha=0.3)
|
||||
|
||||
# Plot 2: Variance Reduction
|
||||
axes[0, 1].hist(results_df['variance_reduction'], bins=50, color='blue',
|
||||
edgecolor='black', alpha=0.7)
|
||||
axes[0, 1].axvline(0, color='red', linestyle='--', linewidth=2)
|
||||
axes[0, 1].axvline(results_df['variance_reduction'].mean(), color='green',
|
||||
linestyle='--', linewidth=2,
|
||||
label=f'Mean: {results_df["variance_reduction"].mean():.6f}')
|
||||
axes[0, 1].set_xlabel('Variance Reduction')
|
||||
axes[0, 1].set_ylabel('Frequency')
|
||||
axes[0, 1].set_title('Variance Reduction from Partial Exit')
|
||||
axes[0, 1].legend()
|
||||
axes[0, 1].grid(True, alpha=0.3)
|
||||
|
||||
# Plot 3: Optimal Exit Percentage
|
||||
axes[1, 0].plot(optimization_df['exit_percentage'],
|
||||
optimization_df['sharpe_ratio'],
|
||||
'b-o', linewidth=2, markersize=8)
|
||||
optimal_idx = optimization_df['sharpe_ratio'].idxmax()
|
||||
optimal_pct = optimization_df.loc[optimal_idx, 'exit_percentage']
|
||||
optimal_sharpe = optimization_df.loc[optimal_idx, 'sharpe_ratio']
|
||||
axes[1, 0].axvline(optimal_pct, color='red', linestyle='--',
|
||||
label=f'Optimal: {optimal_pct:.1%}')
|
||||
axes[1, 0].set_xlabel('Partial Exit Percentage')
|
||||
axes[1, 0].set_ylabel('Sharpe Ratio')
|
||||
axes[1, 0].set_title('Sharpe Ratio vs Exit Percentage')
|
||||
axes[1, 0].legend()
|
||||
axes[1, 0].grid(True, alpha=0.3)
|
||||
|
||||
# Plot 4: Variance Reduction vs Exit Percentage
|
||||
axes[1, 1].plot(optimization_df['exit_percentage'],
|
||||
optimization_df['variance_reduction'],
|
||||
'g-s', linewidth=2, markersize=8)
|
||||
axes[1, 1].axhline(0, color='red', linestyle='--', linewidth=1)
|
||||
axes[1, 1].set_xlabel('Partial Exit Percentage')
|
||||
axes[1, 1].set_ylabel('Variance Reduction')
|
||||
axes[1, 1].set_title('Variance Reduction vs Exit Percentage')
|
||||
axes[1, 1].grid(True, alpha=0.3)
|
||||
|
||||
plt.tight_layout()
|
||||
return fig, results_df, optimization_df
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyzer = PartialExitAnalyzer()
|
||||
|
||||
print("Running Partial Exit Analysis...")
|
||||
fig, results_df, optimization_df = analyzer.plot_analysis(num_simulations=1000)
|
||||
|
||||
print("\n=== Partial Exit Strategy Analysis ===")
|
||||
print(f"\nFull Exit Results:")
|
||||
print(f" Mean Return: {results_df['full_return'].mean():.4f}")
|
||||
print(f" Std Dev: {results_df['full_return'].std():.4f}")
|
||||
print(f" Sharpe Ratio: {results_df['full_return'].mean() / results_df['full_return'].std():.4f}")
|
||||
|
||||
print(f"\nPartial Exit Results (50% exit):")
|
||||
print(f" Mean Return: {results_df['partial_return'].mean():.4f}")
|
||||
print(f" Std Dev: {results_df['partial_return'].std():.4f}")
|
||||
print(f" Sharpe Ratio: {results_df['partial_return'].mean() / results_df['partial_return'].std():.4f}")
|
||||
print(f" Mean Variance Reduction: {results_df['variance_reduction'].mean():.6f}")
|
||||
|
||||
optimal_idx = optimization_df['sharpe_ratio'].idxmax()
|
||||
print(f"\nOptimal Exit Percentage: {optimization_df.loc[optimal_idx, 'exit_percentage']:.1%}")
|
||||
print(f" Optimal Sharpe Ratio: {optimization_df.loc[optimal_idx, 'sharpe_ratio']:.4f}")
|
||||
|
||||
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)
|
||||
|
||||
output_path = os.path.join(figures_path, 'partial_exit_analysis.png')
|
||||
plt.savefig(output_path, dpi=300, bbox_inches='tight')
|
||||
print(f"\nFigure saved to {output_path}")
|
||||
plt.close()
|
||||
@@ -0,0 +1,4 @@
|
||||
numpy>=1.21.0
|
||||
matplotlib>=3.4.0
|
||||
pandas>=1.3.0
|
||||
scipy>=1.7.0
|
||||
@@ -0,0 +1,230 @@
|
||||
"""
|
||||
Trailing Stop Loss Analysis
|
||||
Compares fixed stop loss vs trailing stop loss performance
|
||||
"""
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
matplotlib.use('Agg') # Use non-interactive backend
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
from scipy.stats import norm
|
||||
|
||||
class TrailingStopAnalyzer:
|
||||
def __init__(self, initial_price=100, drift=0.0001, volatility=0.02,
|
||||
trailing_distance=0.02, fixed_stop_distance=0.02):
|
||||
self.initial_price = initial_price
|
||||
self.drift = drift
|
||||
self.volatility = volatility
|
||||
self.trailing_distance = trailing_distance
|
||||
self.fixed_stop_distance = fixed_stop_distance
|
||||
|
||||
def simulate_price_path(self, num_steps=1000, dt=1/252):
|
||||
"""Simulate price using geometric Brownian motion"""
|
||||
prices = [self.initial_price]
|
||||
|
||||
for _ in range(num_steps):
|
||||
dW = np.random.normal(0, np.sqrt(dt))
|
||||
dS = self.drift * prices[-1] * dt + self.volatility * prices[-1] * dW
|
||||
prices.append(prices[-1] + dS)
|
||||
|
||||
return np.array(prices)
|
||||
|
||||
def apply_fixed_stop(self, prices, stop_distance):
|
||||
"""Apply fixed stop loss"""
|
||||
stop_price = self.initial_price - stop_distance * self.initial_price
|
||||
exit_idx = None
|
||||
|
||||
for i, price in enumerate(prices):
|
||||
if price <= stop_price:
|
||||
exit_idx = i
|
||||
break
|
||||
|
||||
if exit_idx is None:
|
||||
exit_price = prices[-1]
|
||||
exit_idx = len(prices) - 1
|
||||
else:
|
||||
exit_price = stop_price
|
||||
|
||||
return exit_idx, exit_price
|
||||
|
||||
def apply_trailing_stop(self, prices, trailing_distance):
|
||||
"""Apply trailing stop loss"""
|
||||
stop_price = self.initial_price - trailing_distance * self.initial_price
|
||||
exit_idx = None
|
||||
|
||||
for i, price in enumerate(prices):
|
||||
# Update trailing stop (only moves up for long positions)
|
||||
new_stop = price - trailing_distance * price
|
||||
if new_stop > stop_price:
|
||||
stop_price = new_stop
|
||||
|
||||
# Check if stop is hit
|
||||
if price <= stop_price:
|
||||
exit_idx = i
|
||||
break
|
||||
|
||||
if exit_idx is None:
|
||||
exit_price = prices[-1]
|
||||
exit_idx = len(prices) - 1
|
||||
else:
|
||||
exit_price = stop_price
|
||||
|
||||
return exit_idx, exit_price, stop_price
|
||||
|
||||
def compare_strategies(self, num_simulations=1000, num_steps=1000):
|
||||
"""Compare fixed vs trailing stop"""
|
||||
results = []
|
||||
|
||||
for sim in range(num_simulations):
|
||||
prices = self.simulate_price_path(num_steps)
|
||||
|
||||
# Fixed stop
|
||||
fixed_exit_idx, fixed_exit_price = self.apply_fixed_stop(
|
||||
prices, self.fixed_stop_distance)
|
||||
fixed_return = (fixed_exit_price - self.initial_price) / self.initial_price
|
||||
|
||||
# Trailing stop
|
||||
trailing_exit_idx, trailing_exit_price, final_stop = self.apply_trailing_stop(
|
||||
prices, self.trailing_distance)
|
||||
trailing_return = (trailing_exit_price - self.initial_price) / self.initial_price
|
||||
|
||||
results.append({
|
||||
'simulation': sim,
|
||||
'final_price': prices[-1],
|
||||
'fixed_return': fixed_return,
|
||||
'trailing_return': trailing_return,
|
||||
'fixed_exit_time': fixed_exit_idx,
|
||||
'trailing_exit_time': trailing_exit_idx,
|
||||
'improvement': trailing_return - fixed_return
|
||||
})
|
||||
|
||||
return pd.DataFrame(results)
|
||||
|
||||
def plot_comparison(self, num_simulations=1000):
|
||||
"""Plot comparison results"""
|
||||
results_df = self.compare_strategies(num_simulations)
|
||||
|
||||
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
|
||||
|
||||
# Plot 1: Return Distribution Comparison
|
||||
axes[0, 0].hist(results_df['fixed_return'], bins=50, alpha=0.5,
|
||||
label='Fixed Stop', color='red', edgecolor='black')
|
||||
axes[0, 0].hist(results_df['trailing_return'], bins=50, alpha=0.5,
|
||||
label='Trailing Stop', color='green', edgecolor='black')
|
||||
axes[0, 0].axvline(0, color='black', linestyle='--', linewidth=1)
|
||||
axes[0, 0].set_xlabel('Return')
|
||||
axes[0, 0].set_ylabel('Frequency')
|
||||
axes[0, 0].set_title('Return Distribution Comparison')
|
||||
axes[0, 0].legend()
|
||||
axes[0, 0].grid(True, alpha=0.3)
|
||||
|
||||
# Plot 2: Improvement Distribution
|
||||
axes[0, 1].hist(results_df['improvement'], bins=50, color='blue',
|
||||
edgecolor='black', alpha=0.7)
|
||||
axes[0, 1].axvline(0, color='red', linestyle='--', linewidth=2,
|
||||
label='No Improvement')
|
||||
axes[0, 1].axvline(results_df['improvement'].mean(), color='green',
|
||||
linestyle='--', linewidth=2,
|
||||
label=f'Mean: {results_df["improvement"].mean():.4f}')
|
||||
axes[0, 1].set_xlabel('Improvement (Trailing - Fixed)')
|
||||
axes[0, 1].set_ylabel('Frequency')
|
||||
axes[0, 1].set_title('Trailing Stop Improvement Distribution')
|
||||
axes[0, 1].legend()
|
||||
axes[0, 1].grid(True, alpha=0.3)
|
||||
|
||||
# Plot 3: Sample Price Path with Stops
|
||||
sample_prices = self.simulate_price_path(500)
|
||||
_, fixed_exit = self.apply_fixed_stop(sample_prices, self.fixed_stop_distance)
|
||||
trailing_stops = []
|
||||
current_stop = self.initial_price - self.trailing_distance * self.initial_price
|
||||
|
||||
for price in sample_prices:
|
||||
new_stop = price - self.trailing_distance * price
|
||||
if new_stop > current_stop:
|
||||
current_stop = new_stop
|
||||
trailing_stops.append(current_stop)
|
||||
|
||||
axes[1, 0].plot(sample_prices, 'b-', label='Price', linewidth=2)
|
||||
axes[1, 0].axhline(self.initial_price - self.fixed_stop_distance * self.initial_price,
|
||||
color='red', linestyle='--', label='Fixed Stop', linewidth=2)
|
||||
axes[1, 0].plot(trailing_stops, 'g--', label='Trailing Stop', linewidth=2)
|
||||
axes[1, 0].set_xlabel('Time Step')
|
||||
axes[1, 0].set_ylabel('Price')
|
||||
axes[1, 0].set_title('Sample Price Path with Stop Losses')
|
||||
axes[1, 0].legend()
|
||||
axes[1, 0].grid(True, alpha=0.3)
|
||||
|
||||
# Plot 4: Performance Metrics Comparison
|
||||
metrics = ['Mean Return', 'Std Dev', 'Sharpe Ratio', 'Win Rate', 'Max Return']
|
||||
fixed_vals = [
|
||||
results_df['fixed_return'].mean(),
|
||||
results_df['fixed_return'].std(),
|
||||
results_df['fixed_return'].mean() / results_df['fixed_return'].std() if results_df['fixed_return'].std() > 0 else 0,
|
||||
(results_df['fixed_return'] > 0).mean(),
|
||||
results_df['fixed_return'].max()
|
||||
]
|
||||
trailing_vals = [
|
||||
results_df['trailing_return'].mean(),
|
||||
results_df['trailing_return'].std(),
|
||||
results_df['trailing_return'].mean() / results_df['trailing_return'].std() if results_df['trailing_return'].std() > 0 else 0,
|
||||
(results_df['trailing_return'] > 0).mean(),
|
||||
results_df['trailing_return'].max()
|
||||
]
|
||||
|
||||
x = np.arange(len(metrics))
|
||||
width = 0.35
|
||||
axes[1, 1].bar(x - width/2, fixed_vals, width, label='Fixed Stop', color='red', alpha=0.7)
|
||||
axes[1, 1].bar(x + width/2, trailing_vals, width, label='Trailing Stop', color='green', alpha=0.7)
|
||||
axes[1, 1].set_xlabel('Metric')
|
||||
axes[1, 1].set_ylabel('Value')
|
||||
axes[1, 1].set_title('Performance Metrics Comparison')
|
||||
axes[1, 1].set_xticks(x)
|
||||
axes[1, 1].set_xticklabels(metrics, rotation=45, ha='right')
|
||||
axes[1, 1].legend()
|
||||
axes[1, 1].grid(True, alpha=0.3, axis='y')
|
||||
|
||||
plt.tight_layout()
|
||||
return fig, results_df
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Create analyzer
|
||||
analyzer = TrailingStopAnalyzer(
|
||||
initial_price=100,
|
||||
drift=0.0001,
|
||||
volatility=0.02,
|
||||
trailing_distance=0.02,
|
||||
fixed_stop_distance=0.02
|
||||
)
|
||||
|
||||
print("Running Trailing Stop Analysis...")
|
||||
fig, results_df = analyzer.plot_comparison(num_simulations=1000)
|
||||
|
||||
print("\n=== Trailing Stop vs Fixed Stop Analysis ===")
|
||||
print(f"\nFixed Stop Results:")
|
||||
print(f" Mean Return: {results_df['fixed_return'].mean():.4f}")
|
||||
print(f" Std Dev: {results_df['fixed_return'].std():.4f}")
|
||||
print(f" Sharpe Ratio: {results_df['fixed_return'].mean() / results_df['fixed_return'].std():.4f}")
|
||||
print(f" Win Rate: {(results_df['fixed_return'] > 0).mean():.2%}")
|
||||
|
||||
print(f"\nTrailing Stop Results:")
|
||||
print(f" Mean Return: {results_df['trailing_return'].mean():.4f}")
|
||||
print(f" Std Dev: {results_df['trailing_return'].std():.4f}")
|
||||
print(f" Sharpe Ratio: {results_df['trailing_return'].mean() / results_df['trailing_return'].std():.4f}")
|
||||
print(f" Win Rate: {(results_df['trailing_return'] > 0).mean():.2%}")
|
||||
|
||||
print(f"\nImprovement:")
|
||||
improvement = results_df['trailing_return'].mean() - results_df['fixed_return'].mean()
|
||||
print(f" Mean Improvement: {improvement:.4f} ({improvement/results_df['fixed_return'].mean()*100:.1f}%)")
|
||||
print(f" Improvement Frequency: {(results_df['improvement'] > 0).mean():.2%}")
|
||||
|
||||
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)
|
||||
|
||||
output_path = os.path.join(figures_path, 'trailing_stop_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