192 lines
8.6 KiB
Python
192 lines
8.6 KiB
Python
|
|
"""
|
||
|
|
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()
|