{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Analisi Completa di un Iron Condor\n", "\n", "**Companion notebook per \"Trading con le Opzioni - Strategie Operative\"** \n", "di Pierpaolo Marturano (Core Matrix S.r.l.)\n", "\n", "Questo notebook analizza un iron condor con:\n", "- Payoff a scadenza e breakeven\n", "- Profilo delle greche aggregate\n", "- Scenari what-if (volatilità, tempo, movimento del sottostante)\n", "- Probabilità di profitto via Monte Carlo\n", "\n", "Requisiti: `numpy`, `scipy`, `matplotlib`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from scipy.stats import norm\n", "import matplotlib.pyplot as plt\n", "\n", "from greche import delta, gamma, theta, vega, bs_price, all_greeks\n", "from payoff import iron_condor, Strategy\n", "\n", "plt.style.use('seaborn-v0_8-whitegrid')\n", "plt.rcParams['figure.dpi'] = 100" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Setup dell'Iron Condor\n", "\n", "Configuriamo un iron condor standard su SPX a 45 DTE:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# === PARAMETRI DELL'IRON CONDOR ===\n", "S = 5500 # Prezzo SPX\n", "r = 0.045 # Risk-free rate\n", "sigma = 0.16 # IV media\n", "T = 45 / 365 # 45 DTE\n", "\n", "# Strike dell'iron condor (16-delta)\n", "put_long_K = 5300 # Long put (ala inferiore)\n", "put_short_K = 5350 # Short put\n", "call_short_K = 5650 # Short call\n", "call_long_K = 5700 # Long call (ala superiore)\n", "\n", "# Credito netto ricevuto\n", "net_credit = 4.50\n", "\n", "# Calcolo metriche\n", "wing_width = put_short_K - put_long_K # = call_long_K - call_short_K\n", "max_profit = net_credit\n", "max_loss = wing_width - net_credit\n", "be_lower = put_short_K - net_credit\n", "be_upper = call_short_K + net_credit\n", "\n", "print(\"=\" * 50)\n", "print(\"IRON CONDOR SPX — Riepilogo\")\n", "print(\"=\" * 50)\n", "print(f\"\\nStrike: {put_long_K}/{put_short_K} — {call_short_K}/{call_long_K}\")\n", "print(f\"Larghezza ali: ${wing_width}\")\n", "print(f\"Credito netto: ${net_credit:.2f}\")\n", "print(f\"\\nMax profitto: ${max_profit:.2f} ({max_profit/wing_width*100:.1f}% del rischio)\")\n", "print(f\"Max perdita: ${max_loss:.2f}\")\n", "print(f\"Breakeven: ${be_lower:.2f} — ${be_upper:.2f}\")\n", "print(f\"Range sicuro: {(be_upper - be_lower)/S*100:.1f}% del prezzo\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Payoff a Scadenza" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ic = iron_condor(put_long_K, put_short_K, call_short_K, call_long_K, net_credit)\n", "ic.plot(S_range=(5200, 5800))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Greche Aggregate dell'Iron Condor" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "S_range = np.linspace(5200, 5800, 200)\n", "\n", "def ic_greeks(S_val, T_val):\n", " \"\"\"Calcola le greche aggregate dell'iron condor.\"\"\"\n", " # Long put (put_long_K)\n", " g1 = all_greeks(S_val, put_long_K, T_val, r, sigma, 'put')\n", " # Short put (put_short_K)\n", " g2 = all_greeks(S_val, put_short_K, T_val, r, sigma, 'put')\n", " # Short call (call_short_K)\n", " g3 = all_greeks(S_val, call_short_K, T_val, r, sigma, 'call')\n", " # Long call (call_long_K)\n", " g4 = all_greeks(S_val, call_long_K, T_val, r, sigma, 'call')\n", " \n", " result = {}\n", " for key in g1:\n", " result[key] = g1[key] - g2[key] - g3[key] + g4[key]\n", " return result\n", "\n", "# Calcola greche per ogni prezzo\n", "greeks_data = {key: [] for key in ['delta', 'gamma', 'theta', 'vega']}\n", "for s in S_range:\n", " g = ic_greeks(s, T)\n", " for key in greeks_data:\n", " greeks_data[key].append(g[key])\n", "\n", "fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n", "fig.suptitle('Greche dell\\'Iron Condor al variare del prezzo', fontweight='bold', fontsize=13)\n", "\n", "titles = ['Delta (posizionale)', 'Gamma (rischio)', 'Theta ($/giorno)', 'Vega ($/1% IV)']\n", "colors = ['#10b981', '#ef4444', '#f59e0b', '#3b82f6']\n", "\n", "for ax, key, title, color in zip(axes.flat, greeks_data, titles, colors):\n", " ax.plot(S_range, greeks_data[key], linewidth=2, color=color)\n", " ax.axhline(y=0, color='gray', linestyle='-', alpha=0.3)\n", " ax.axvline(x=put_short_K, color='gray', linestyle='--', alpha=0.4)\n", " ax.axvline(x=call_short_K, color='gray', linestyle='--', alpha=0.4)\n", " ax.axvline(x=S, color='black', linestyle=':', alpha=0.5, label='Spot')\n", " ax.set_title(title, fontweight='bold')\n", " ax.set_xlabel('Prezzo ($)')\n", " ax.grid(True, alpha=0.3)\n", " ax.legend()\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "# Greche attuali\n", "current = ic_greeks(S, T)\n", "print(f\"\\nGreche attuali (S={S}, {T*365:.0f} DTE):\")\n", "print(f\" Delta: {current['delta']:+.4f} (quasi neutrale)\")\n", "print(f\" Gamma: {current['gamma']:+.6f} (negativo = short gamma)\")\n", "print(f\" Theta: {current['theta']:+.4f} $/giorno (positivo = incassiamo tempo)\")\n", "print(f\" Vega: {current['vega']:+.4f} $/1% IV (negativo = short vega)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Scenari What-If: evoluzione nel tempo" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# P&L teorico a diversi DTE residui\n", "fig, ax = plt.subplots(figsize=(12, 6))\n", "\n", "S_plot = np.linspace(5250, 5750, 300)\n", "\n", "for dte_remaining in [45, 35, 25, 15, 7, 0]:\n", " T_i = max(dte_remaining / 365, 0.001)\n", " \n", " if dte_remaining == 0:\n", " # A scadenza: payoff intrinseco\n", " payoff = ic.total_payoff(S_plot)\n", " else:\n", " # Prima della scadenza: valore teorico delle posizioni\n", " pnl = []\n", " for s in S_plot:\n", " # Valore corrente delle 4 gambe\n", " val_put_long = bs_price(s, put_long_K, T_i, r, sigma, 'put')\n", " val_put_short = bs_price(s, put_short_K, T_i, r, sigma, 'put')\n", " val_call_short = bs_price(s, call_short_K, T_i, r, sigma, 'call')\n", " val_call_long = bs_price(s, call_long_K, T_i, r, sigma, 'call')\n", " \n", " # P&L = credito iniziale - costo di chiusura\n", " close_cost = -val_put_long + val_put_short + val_call_short - val_call_long\n", " pnl.append(net_credit - close_cost)\n", " payoff = np.array(pnl)\n", " \n", " alpha = 0.4 if dte_remaining > 0 else 1.0\n", " lw = 1.5 if dte_remaining > 0 else 2.5\n", " ax.plot(S_plot, payoff, linewidth=lw, alpha=alpha,\n", " label=f'{dte_remaining} DTE')\n", "\n", "ax.axhline(y=0, color='gray', linewidth=0.8)\n", "ax.axhline(y=net_credit, color='green', linewidth=0.8, linestyle=':', alpha=0.5)\n", "ax.axvline(x=S, color='black', linestyle=':', alpha=0.3)\n", "ax.set_title('P&L dell\\'Iron Condor nel tempo', fontweight='bold', fontsize=13)\n", "ax.set_xlabel('Prezzo SPX ($)')\n", "ax.set_ylabel('P&L ($)')\n", "ax.legend(loc='lower right')\n", "ax.grid(True, alpha=0.3)\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "print(\"Osservazione: con il passare del tempo, la 'tenda' del profitto si appiattisce\")\n", "print(\"verso il profitto massimo, purché il prezzo resti tra gli strike short.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. Scenario: impatto della variazione di IV" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(12, 6))\n", "\n", "# P&L con diversi livelli di IV (a 30 DTE residui)\n", "T_scenario = 30 / 365\n", "\n", "for iv_scenario in [0.12, 0.14, 0.16, 0.20, 0.25, 0.30]:\n", " pnl = []\n", " for s in S_plot:\n", " val_put_long = bs_price(s, put_long_K, T_scenario, r, iv_scenario, 'put')\n", " val_put_short = bs_price(s, put_short_K, T_scenario, r, iv_scenario, 'put')\n", " val_call_short = bs_price(s, call_short_K, T_scenario, r, iv_scenario, 'call')\n", " val_call_long = bs_price(s, call_long_K, T_scenario, r, iv_scenario, 'call')\n", " \n", " close_cost = -val_put_long + val_put_short + val_call_short - val_call_long\n", " pnl.append(net_credit - close_cost)\n", " \n", " style = '-' if iv_scenario <= sigma else '--'\n", " ax.plot(S_plot, pnl, linewidth=2, linestyle=style,\n", " label=f'IV={iv_scenario*100:.0f}%')\n", "\n", "ax.axhline(y=0, color='gray', linewidth=0.8)\n", "ax.axvline(x=S, color='black', linestyle=':', alpha=0.3)\n", "ax.set_title(f'Impatto della IV sul P&L (a 30 DTE, entry IV={sigma*100:.0f}%)',\n", " fontweight='bold', fontsize=13)\n", "ax.set_xlabel('Prezzo SPX ($)')\n", "ax.set_ylabel('P&L ($)')\n", "ax.legend()\n", "ax.grid(True, alpha=0.3)\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "print(\"L'iron condor è short vega: beneficia dalla diminuzione della IV.\")\n", "print(\"Un aumento di IV (es. durante sell-off) danneggia la posizione anche\")\n", "print(\"se il prezzo non ha ancora raggiunto gli strike short.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. Probabilità di Profitto (Monte Carlo)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from montecarlo import monte_carlo_strategy, OptionLeg, plot_results\n", "\n", "# Setup per Monte Carlo\n", "legs = [\n", " OptionLeg(\"put\", put_long_K, 0, 1),\n", " OptionLeg(\"put\", put_short_K, net_credit/2, -1),\n", " OptionLeg(\"call\", call_short_K, net_credit/2, -1),\n", " OptionLeg(\"call\", call_long_K, 0, 1),\n", "]\n", "\n", "results = monte_carlo_strategy(S, r, sigma, T, legs,\n", " n_simulations=200_000, seed=42)\n", "\n", "plot_results(results, f\"Iron Condor SPX {put_short_K}/{call_short_K}\")\n", "\n", "print(f\"\\nRisultati Monte Carlo (200,000 simulazioni):\")\n", "print(f\" Probabilità di profitto: {results['prob_profit']*100:.1f}%\")\n", "print(f\" P&L atteso: ${results['expected_pnl']:.2f}\")\n", "print(f\" Ratio rendimento/rischio: {max_profit/max_loss:.2f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7. Regola del 50%: quando chiudere?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Simulazione della regola del 50%: chiudi quando hai catturato il 50% del credito\n", "from montecarlo import simulate_gbm\n", "\n", "n_sims = 10_000\n", "target_pct = 0.50 # Chiudi al 50% del credito\n", "target_pnl = net_credit * target_pct\n", "\n", "# Simula percorsi giornalieri\n", "paths = simulate_gbm(S, r, sigma, T, n_sims, n_steps=45, seed=42)\n", "\n", "results_50pct = []\n", "exit_days = []\n", "\n", "for path in paths:\n", " closed_early = False\n", " for day in range(1, 46):\n", " S_day = path[day]\n", " T_remaining = (45 - day) / 365\n", " \n", " if T_remaining < 0.001:\n", " T_remaining = 0.001\n", " \n", " # Valore di chiusura\n", " close_val = (\n", " -bs_price(S_day, put_long_K, T_remaining, r, sigma, 'put')\n", " + bs_price(S_day, put_short_K, T_remaining, r, sigma, 'put')\n", " + bs_price(S_day, call_short_K, T_remaining, r, sigma, 'call')\n", " - bs_price(S_day, call_long_K, T_remaining, r, sigma, 'call')\n", " )\n", " \n", " current_pnl = net_credit - close_val\n", " \n", " if current_pnl >= target_pnl:\n", " results_50pct.append(current_pnl)\n", " exit_days.append(day)\n", " closed_early = True\n", " break\n", " \n", " if not closed_early:\n", " # A scadenza\n", " S_final = path[-1]\n", " payoff = ic.total_payoff(np.array([S_final]))[0]\n", " results_50pct.append(payoff)\n", " exit_days.append(45)\n", "\n", "results_50pct = np.array(results_50pct)\n", "exit_days = np.array(exit_days)\n", "\n", "print(f\"Regola del 50% — Risultati ({n_sims:,} simulazioni):\")\n", "print(f\" % chiuse prima della scadenza: {np.mean(exit_days < 45)*100:.1f}%\")\n", "print(f\" Giorni medi di permanenza: {np.mean(exit_days):.1f}\")\n", "print(f\" Win rate: {np.mean(results_50pct > 0)*100:.1f}%\")\n", "print(f\" P&L medio: ${np.mean(results_50pct):.2f}\")\n", "print(f\" P&L mediano: ${np.median(results_50pct):.2f}\")\n", "\n", "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n", "\n", "axes[0].hist(exit_days, bins=45, color='#0d5c4d', alpha=0.7, edgecolor='white')\n", "axes[0].set_title('Distribuzione dei giorni di permanenza', fontweight='bold')\n", "axes[0].set_xlabel('Giorni')\n", "axes[0].set_ylabel('Frequenza')\n", "axes[0].axvline(x=np.mean(exit_days), color='red', linestyle='--',\n", " label=f'Media: {np.mean(exit_days):.0f} giorni')\n", "axes[0].legend()\n", "\n", "axes[1].hist(results_50pct, bins=50, color='#0d5c4d', alpha=0.7, edgecolor='white')\n", "axes[1].set_title('Distribuzione P&L con regola 50%', fontweight='bold')\n", "axes[1].set_xlabel('P&L ($)')\n", "axes[1].axvline(x=0, color='gray', linewidth=1)\n", "axes[1].axvline(x=np.mean(results_50pct), color='red', linestyle='--',\n", " label=f'Media: ${np.mean(results_50pct):.2f}')\n", "axes[1].legend()\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 8. Riepilogo e takeaway\n", "\n", "**Punti chiave dell'Iron Condor:**\n", "\n", "1. **Theta positivo**: incassiamo tempo ogni giorno\n", "2. **Short vega**: un aumento di IV ci danneggia\n", "3. **Short gamma**: movimenti bruschi sono il nemico\n", "4. **Regola del 50%**: chiudere al 50% del credito migliora il win rate e libera capitale\n", "5. **Ciclo 45→21 DTE**: cattura il decadimento temporale nella fase più efficiente" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.10.0" } }, "nbformat": 4, "nbformat_minor": 4 }