Not a member of GistPad yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="it">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Vertical Spread Tracker</title>
- <style>
- :root {
- --bg: #0f1117; --surface: #1a1d2e; --surface2: #22263a;
- --border: #2e3250; --accent: #6c63ff; --green: #00d395;
- --red: #ff4d6d; --yellow: #f9c74f; --text: #e2e8f0;
- --muted: #8892b0; --white: #ffffff;
- }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { background:var(--bg); color:var(--text); font-family:'Segoe UI',sans-serif; font-size:14px; min-height:100vh; }
- /* HEADER */
- header { background:var(--surface); border-bottom:1px solid var(--border); padding:14px 24px; display:flex; align-items:center; justify-content:space-between; }
- header h1 { font-size:18px; font-weight:700; color:var(--white); display:flex; align-items:center; gap:10px; }
- header h1 span { background:var(--accent); border-radius:8px; padding:4px 10px; font-size:13px; }
- .header-actions { display:flex; gap:10px; }
- /* BUTTONS */
- .btn { padding:8px 16px; border-radius:8px; border:none; cursor:pointer; font-size:13px; font-weight:600; transition:all .2s; }
- .btn-primary { background:var(--accent); color:#fff; }
- .btn-primary:hover { background:#7c75ff; }
- .btn-success { background:var(--green); color:#000; }
- .btn-success:hover { opacity:.85; }
- .btn-danger { background:var(--red); color:#fff; }
- .btn-outline { background:transparent; border:1px solid var(--border); color:var(--text); }
- .btn-outline:hover { border-color:var(--accent); color:var(--accent); }
- .btn-sm { padding:5px 10px; font-size:12px; }
- /* LAYOUT */
- .container { max-width:1400px; margin:0 auto; padding:20px; }
- .grid-4 { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:24px; }
- .grid-2 { display:grid; grid-template-columns:repeat(2,1fr); gap:16px; margin-bottom:24px; }
- /* KPI CARDS */
- .kpi { background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:18px; }
- .kpi.highlight { border-color:var(--accent); }
- .kpi label { font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:.5px; display:block; margin-bottom:8px; }
- .kpi .val { font-size:26px; font-weight:700; }
- .kpi .sub { font-size:11px; color:var(--muted); margin-top:4px; }
- .green { color:var(--green); } .red { color:var(--red); } .yellow { color:var(--yellow); }
- /* SECTION */
- .section { background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:20px; margin-bottom:24px; }
- .section-title { font-size:15px; font-weight:700; color:var(--white); margin-bottom:16px; display:flex; align-items:center; justify-content:space-between; }
- /* FORM */
- .form-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(160px,1fr)); gap:12px; }
- .form-group { display:flex; flex-direction:column; gap:5px; }
- .form-group label { font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:.5px; }
- .form-group input, .form-group select { background:var(--surface2); border:1px solid var(--border); border-radius:8px; padding:8px 12px; color:var(--text); font-size:13px; outline:none; transition:.2s; }
- .form-group input:focus, .form-group select:focus { border-color:var(--accent); }
- .form-group select option { background:var(--surface2); }
- .form-row { display:flex; gap:12px; align-items:flex-end; flex-wrap:wrap; margin-top:12px; }
- /* CALC PREVIEW */
- .calc-preview { background:var(--surface2); border:1px solid var(--border); border-radius:10px; padding:14px; display:grid; grid-template-columns:repeat(5,1fr); gap:10px; margin-top:14px; }
- .calc-item label { font-size:10px; color:var(--muted); display:block; margin-bottom:4px; }
- .calc-item span { font-size:15px; font-weight:700; }
- /* TABLE */
- .table-wrap { overflow-x:auto; }
- table { width:100%; border-collapse:collapse; font-size:13px; }
- thead tr { background:var(--surface2); }
- th { padding:10px 12px; text-align:left; color:var(--muted); font-size:11px; text-transform:uppercase; letter-spacing:.5px; white-space:nowrap; border-bottom:1px solid var(--border); }
- td { padding:10px 12px; border-bottom:1px solid var(--border); white-space:nowrap; vertical-align:middle; }
- tr:hover td { background:rgba(108,99,255,.05); }
- .badge { display:inline-block; padding:3px 8px; border-radius:20px; font-size:11px; font-weight:600; }
- .badge-call { background:rgba(108,99,255,.2); color:#a29bff; }
- .badge-put { background:rgba(249,199,79,.15); color:var(--yellow); }
- .badge-open { background:rgba(0,211,149,.15); color:var(--green); }
- .badge-closed { background:rgba(142,142,142,.15); color:#999; }
- .badge-stop { background:rgba(255,77,109,.15); color:var(--red); }
- .badge-win { background:rgba(0,211,149,.2); color:var(--green); }
- .badge-loss { background:rgba(255,77,109,.2); color:var(--red); }
- /* MODAL */
- .modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,.7); z-index:100; align-items:center; justify-content:center; }
- .modal-overlay.active { display:flex; }
- .modal { background:var(--surface); border:1px solid var(--border); border-radius:16px; padding:28px; width:90%; max-width:600px; }
- .modal h2 { font-size:17px; margin-bottom:20px; color:var(--white); }
- /* TABS */
- .tabs { display:flex; gap:4px; background:var(--surface2); border-radius:10px; padding:4px; margin-bottom:20px; width:fit-content; }
- .tab { padding:7px 18px; border-radius:8px; cursor:pointer; font-size:13px; font-weight:600; color:var(--muted); transition:.2s; border:none; background:transparent; }
- .tab.active { background:var(--accent); color:#fff; }
- /* ALERT */
- .alert { padding:10px 14px; border-radius:8px; font-size:13px; margin-bottom:12px; display:none; }
- .alert.success { background:rgba(0,211,149,.12); border:1px solid var(--green); color:var(--green); display:block; }
- .alert.error { background:rgba(255,77,109,.12); border:1px solid var(--red); color:var(--red); display:block; }
- /* TOAST NOTIFICATION */
- .toast { position:fixed; bottom:28px; right:28px; z-index:999; padding:14px 22px; border-radius:12px; font-size:14px; font-weight:600; display:flex; align-items:center; gap:10px; box-shadow:0 8px 32px rgba(0,0,0,.4); transform:translateY(80px); opacity:0; transition:all .35s cubic-bezier(.21,1.02,.73,1); pointer-events:none; }
- .toast.show { transform:translateY(0); opacity:1; }
- .toast.success { background:#00d395; color:#000; }
- .toast.error { background:#ff4d6d; color:#fff; }
- .toast.info { background:#4cc9f0; color:#000; }
- /* AUTO-SAVE INDICATOR */
- .autosave-dot { width:8px; height:8px; border-radius:50%; background:var(--green); display:inline-block; margin-left:8px; animation:pulse 2s infinite; }
- @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.3} }
- /* INPUT ERROR STATE */
- .input-error { border-color:var(--red) !important; background:rgba(255,77,109,.08) !important; animation:shake .3s ease; }
- @keyframes shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-4px)} 75%{transform:translateX(4px)} }
- /* TABLE RESPONSIVE β no horizontal scroll */
- .table-wrap { overflow-x:visible; }
- table { width:100%; border-collapse:collapse; font-size:12px; table-layout:fixed; }
- th, td { padding:8px 6px; overflow:hidden; text-overflow:ellipsis; }
- th { white-space:normal; word-break:break-word; }
- td { white-space:normal; word-break:break-word; }
- /* NAV */
- nav { background:var(--surface); border-bottom:1px solid var(--border); padding:0 24px; display:flex; gap:4px; }
- .nav-btn { padding:12px 20px; background:none; border:none; border-bottom:3px solid transparent; color:var(--muted); font-size:13px; font-weight:600; cursor:pointer; transition:.2s; margin-bottom:-1px; }
- .nav-btn:hover { color:var(--text); }
- .nav-btn.active { color:var(--accent); border-bottom-color:var(--accent); }
- /* PAGES */
- .page { display:none; }
- .page.active { display:block; }
- /* ANALYTICS */
- .grid-3 { display:grid; grid-template-columns:repeat(3,1fr); gap:16px; margin-bottom:24px; }
- .grid-2-1 { display:grid; grid-template-columns:2fr 1fr; gap:16px; margin-bottom:24px; }
- .grid-1-1 { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:24px; }
- .chart-box { background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:20px; }
- .chart-box .chart-title { font-size:13px; font-weight:700; color:var(--white); margin-bottom:4px; }
- .chart-box .chart-sub { font-size:11px; color:var(--muted); margin-bottom:16px; }
- .chart-box canvas { max-height:260px; }
- .chart-box.tall canvas { max-height:320px; }
- /* STAT TABLE */
- .stat-row { display:flex; justify-content:space-between; align-items:center; padding:9px 0; border-bottom:1px solid var(--border); font-size:13px; }
- .stat-row:last-child { border-bottom:none; }
- .stat-label { color:var(--muted); }
- .stat-val { font-weight:700; }
- /* EQUITY TICKER */
- .equity-change { font-size:12px; padding:2px 8px; border-radius:20px; margin-left:8px; }
- .equity-up { background:rgba(0,211,149,.15); color:var(--green); }
- .equity-down { background:rgba(255,77,109,.15); color:var(--red); }
- /* HEATMAP */
- .heatmap { display:grid; grid-template-columns:repeat(7,1fr); gap:4px; }
- .heatmap-cell { aspect-ratio:1; border-radius:4px; display:flex; align-items:center; justify-content:center; font-size:9px; font-weight:700; cursor:default; transition:.2s; }
- .heatmap-cell:hover { transform:scale(1.1); }
- /* NO DATA */
- .no-data-chart { display:flex; flex-direction:column; align-items:center; justify-content:center; height:160px; color:var(--muted); font-size:13px; gap:8px; }
- .no-data-chart span { font-size:32px; }
- /* EQUITY FILTER BUTTONS */
- .eq-btn { padding:5px 12px; border-radius:20px; border:1px solid var(--border); background:transparent; color:var(--muted); font-size:12px; font-weight:600; cursor:pointer; transition:.2s; }
- .eq-btn:hover { border-color:var(--accent); color:var(--accent); }
- .eq-btn.active { background:var(--accent); border-color:var(--accent); color:#fff; }
- /* EXPIRY CALENDAR */
- .cal-filter-bar { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-bottom:14px; }
- .cal-yr-btn { padding:5px 14px; border-radius:20px; border:1px solid var(--border); background:transparent; color:var(--muted); font-size:12px; font-weight:600; cursor:pointer; transition:all .2s; }
- .cal-yr-btn.active, .cal-yr-btn:hover { background:var(--accent); color:#fff; border-color:var(--accent); }
- .cal-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:16px; }
- .cal-month { background:var(--surface2); border:1px solid var(--border); border-radius:12px; overflow:hidden; transition:border-color .2s; }
- .cal-month:hover { border-color:var(--accent); }
- .cal-month-header { padding:12px 16px 10px; font-size:14px; font-weight:700; color:var(--white); display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid var(--border); }
- .cal-month-header .cal-count { font-size:11px; padding:2px 8px; border-radius:20px; background:rgba(108,99,255,.2); color:var(--accent); }
- .cal-month-stats { display:grid; grid-template-columns:1fr 1fr 1fr; gap:0; border-bottom:1px solid var(--border); }
- .cal-ms { padding:8px 10px; text-align:center; border-right:1px solid var(--border); }
- .cal-ms:last-child { border-right:none; }
- .cal-ms-val { font-size:16px; font-weight:800; color:var(--white); }
- .cal-ms-lbl { font-size:9px; text-transform:uppercase; letter-spacing:.5px; color:var(--muted); margin-top:2px; }
- .cal-type-bar { display:flex; height:6px; border-radius:3px; overflow:hidden; margin:0 14px 8px; }
- .cal-type-seg { transition:width .3s; }
- .cal-outcome-row { display:flex; gap:8px; padding:4px 14px 8px; flex-wrap:wrap; }
- .cal-outcome-pill { font-size:10px; padding:2px 8px; border-radius:12px; font-weight:600; }
- .cal-trade-row { padding:7px 14px; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid rgba(255,255,255,.04); font-size:12px; }
- .cal-trade-row:last-child { border-bottom:none; }
- .cal-trade-left { display:flex; flex-direction:column; gap:2px; }
- .cal-trade-ticker { font-weight:700; color:var(--white); }
- .cal-trade-info { color:var(--muted); font-size:11px; }
- .cal-trade-right { display:flex; flex-direction:column; align-items:flex-end; gap:2px; }
- .cal-past { opacity:.5; }
- .cal-soon { border-left:3px solid var(--yellow); }
- .cal-pnl-bar { display:flex; align-items:center; gap:6px; padding:6px 14px 10px; }
- .cal-pnl-track { flex:1; height:8px; background:rgba(255,255,255,.06); border-radius:4px; overflow:hidden; position:relative; }
- .cal-pnl-fill { height:100%; border-radius:4px; transition:width .3s; }
- .cal-pnl-val { font-size:11px; font-weight:700; min-width:60px; text-align:right; }
- /* Stats enhanced */
- .stat-section-title { font-size:11px; text-transform:uppercase; letter-spacing:1px; color:var(--accent); font-weight:700; padding:10px 0 6px; border-bottom:1px solid var(--border); margin-bottom:4px; }
- .cal-today { border-left:3px solid var(--red); }
- /* TRADE CARDS */
- .trade-card { background:var(--surface2); border:1px solid var(--border); border-radius:12px; padding:16px 20px; margin-bottom:10px; display:grid; grid-template-columns:auto 1fr auto; gap:16px; align-items:center; transition:.2s; }
- .trade-card:hover { border-color:var(--accent); box-shadow:0 0 0 1px rgba(108,99,255,.2); }
- .trade-card.status-open { border-left:3px solid var(--green); }
- .trade-card.status-closed { border-left:3px solid #555; }
- .trade-card.status-stop { border-left:3px solid var(--red); }
- .trade-card.status-early { border-left:3px solid var(--yellow); }
- .tc-left { display:flex; flex-direction:column; align-items:center; gap:4px; min-width:64px; }
- .tc-ticker { font-size:20px; font-weight:800; color:var(--white); letter-spacing:.5px; }
- .tc-asset { font-size:10px; color:var(--muted); text-transform:uppercase; }
- .tc-mid { display:grid; grid-template-columns:repeat(5,1fr); gap:8px 16px; }
- .tc-field { display:flex; flex-direction:column; gap:2px; }
- .tc-field span { font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:.3px; }
- .tc-field strong { font-size:13px; color:var(--text); font-weight:600; }
- .tc-right { display:flex; flex-direction:column; align-items:flex-end; gap:8px; min-width:120px; }
- .tc-pnl { font-size:22px; font-weight:800; }
- .tc-roi { font-size:12px; font-weight:600; }
- .tc-actions { display:flex; gap:6px; flex-wrap:wrap; justify-content:flex-end; }
- .tc-divider { height:1px; background:var(--border); margin:0 0 4px 0; }
- .tc-dates { display:flex; gap:16px; }
- .tc-dates .tc-field strong { font-size:12px; }
- @media(max-width:1100px){ .grid-2-1{grid-template-columns:1fr;} .grid-3{grid-template-columns:repeat(2,1fr);} }
- @media(max-width:900px){ .grid-4{grid-template-columns:repeat(2,1fr);} .calc-preview{grid-template-columns:repeat(2,1fr);} .grid-1-1{grid-template-columns:1fr;} }
- @media(max-width:600px){ .grid-4{grid-template-columns:1fr;} .grid-3{grid-template-columns:1fr;} }
- </style>
- </head>
- <body>
- <header>
- <h1>π Vertical Spread Tracker <span>Options</span><span class="autosave-dot" title="Auto-salvataggio attivo nel browser"></span></h1>
- <div class="header-actions">
- <button class="btn btn-outline" onclick="exportCSV()" title="Esporta tutti i trade in formato CSV per Excel">β¬ CSV</button>
- <button class="btn btn-outline" style="border-color:#4cc9f0;color:#4cc9f0;" onclick="importJSON()" title="Ripristina un backup JSON salvato in precedenza">β¬ Ripristina JSON</button>
- <button class="btn btn-outline" style="border-color:#f9c74f;color:#f9c74f;" onclick="exportJSON()" title="Salva tutti i tuoi trade in un file JSON sul PC">πΎ Salva JSON</button>
- <button class="btn btn-primary" onclick="openModal()">οΌ Nuovo Trade</button>
- </div>
- </header>
- <!-- INPUT NASCOSTO PER IMPORT FILE -->
- <input type="file" id="jsonFileInput" accept=".json" style="display:none" onchange="loadJSONFile(event)" />
- <!-- TOAST -->
- <div class="toast" id="toast"></div>
- <nav>
- <button class="nav-btn active" onclick="showPage('dashboard',this)">π Dashboard</button>
- <button class="nav-btn" onclick="showPage('analytics',this)">π Analytics</button>
- </nav>
- <!-- βββββββββββββββββββββββββββ PAGE: DASHBOARD βββββββββββββββββββββββββββ -->
- <div class="page active" id="page-dashboard">
- <div class="container">
- <!-- KPI ROW 1: 4 box principali -->
- <div class="grid-4">
- <div class="kpi"><label>Trades Aperti</label><div class="val yellow" id="k-open">0</div><div class="sub">posizioni attive</div></div>
- <div class="kpi"><label>Margine Impiegato</label><div class="val yellow" id="k-margin">$0,00</div><div class="sub">somma margine broker posizioni aperte</div></div>
- <div class="kpi highlight"><label>P&L Realizzato</label><div class="val green" id="k-pnl">$0,00</div><div class="sub">al netto di tutte le fee</div></div>
- <div class="kpi"><label>Win Rate</label><div class="val green" id="k-wr">0%</div><div class="sub" id="k-wr-sub">0 win / 0 chiusi</div></div>
- </div>
- <!-- KPI ROW 2: Dettaglio WR + ROI medio + AVERAGE box -->
- <div style="display:grid;grid-template-columns:1fr 1fr 2fr;gap:16px;margin-bottom:24px;">
- <!-- Dettaglio Win Rate -->
- <div class="kpi">
- <label>Dettaglio Win Rate</label>
- <div style="display:flex;flex-direction:column;gap:6px;margin-top:4px;" id="k-wr-detail">
- <div style="display:flex;justify-content:space-between;font-size:13px;"><span style="color:var(--muted)">β Scadenza</span><span id="k-wr-exp" class="green">0 (0%)</span></div>
- <div style="display:flex;justify-content:space-between;font-size:13px;"><span style="color:var(--muted)">β© Uscita Ant.</span><span id="k-wr-early" class="yellow">0 (0%)</span></div>
- <div style="display:flex;justify-content:space-between;font-size:13px;"><span style="color:var(--muted)">π Stop Loss</span><span id="k-wr-stop" class="red">0 (0%)</span></div>
- </div>
- </div>
- <!-- ROI Medio per Trade -->
- <div class="kpi">
- <label>ROI Medio Atteso per Trade</label>
- <div class="val green" id="k-roi-avg">0,00%</div>
- <div class="sub">media ROI atteso (aperti + chiusi)</div>
- </div>
- <!-- AVERAGE box: 4 metriche in griglia -->
- <div class="kpi">
- <label>Average β medie su tutti i trade</label>
- <div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:10px;margin-top:8px;">
- <div>
- <div style="font-size:10px;color:var(--muted);text-transform:uppercase;margin-bottom:4px;">Max Profitto Netto</div>
- <div style="font-size:15px;font-weight:700;color:var(--green);" id="k-avg-profit">β</div>
- </div>
- <div>
- <div style="font-size:10px;color:var(--muted);text-transform:uppercase;margin-bottom:4px;">Max Perdita (SL)</div>
- <div style="font-size:15px;font-weight:700;color:var(--red);" id="k-avg-loss">β</div>
- </div>
- <div>
- <div style="font-size:10px;color:var(--muted);text-transform:uppercase;margin-bottom:4px;">Margine Broker</div>
- <div style="font-size:15px;font-weight:700;color:var(--yellow);" id="k-avg-margin">β</div>
- </div>
- <div>
- <div style="font-size:10px;color:var(--muted);text-transform:uppercase;margin-bottom:4px;">R/R Ratio medio</div>
- <div style="font-size:15px;font-weight:700;" id="k-avg-rr">β</div>
- </div>
- </div>
- </div>
- </div>
- <!-- TRADE ENTRY SECTION -->
- <div class="section">
- <div class="section-title">Aggiungi / Modifica Trade</div>
- <div id="formAlert" class="alert"></div>
- <!-- TABS -->
- <div class="tabs">
- <button class="tab active" onclick="setSpreadType('bull_put',this)">Bull Put Spread</button>
- <button class="tab" onclick="setSpreadType('bear_call',this)">Bear Call Spread</button>
- </div>
- <input type="hidden" id="editId" value="">
- <input type="hidden" id="spreadType" value="bull_put">
- <div class="form-grid">
- <div class="form-group">
- <label>Tipo Strumento</label>
- <select id="f-asset-type" onchange="onAssetTypeChange()">
- <option value="stock">Stock</option>
- <option value="etf">ETF</option>
- <option value="future_index">Future Index</option>
- <option value="future_mp">Future M.P.</option>
- <option value="future_fx">Future Valute</option>
- </select>
- </div>
- <div class="form-group"><label>Ticker</label><input type="text" id="f-ticker" placeholder="es. AAPL" oninput="this.value=this.value.toUpperCase()" /></div>
- <div class="form-group"><label>Data Apertura</label><input type="date" id="f-open-date" /></div>
- <div class="form-group"><label>Scadenza</label><input type="date" id="f-expiry" /></div>
- <div class="form-group"><label>Contratti</label><input type="number" id="f-contracts" value="1" min="1" onchange="calcPreview()" /></div>
- <div class="form-group">
- <label>Moltiplicatore</label>
- <input type="text" inputmode="numeric" id="f-multiplier" value="100" oninput="calcPreview()" />
- </div>
- <div class="form-group">
- <label>Margine Broker ($)</label>
- <input type="text" inputmode="decimal" id="f-margin-broker" placeholder="es. 760" oninput="calcPreview()" />
- </div>
- <div class="form-group"><label id="sell-strike-lbl">Put VENDUTA (OTM)</label><input type="text" inputmode="decimal" id="f-sell-strike" placeholder="es. 30" oninput="validateStrikesLive(); calcPreview();" /></div>
- <div class="form-group"><label id="buy-strike-lbl">Put COMPRATA (piΓΉ bassa)</label><input type="text" inputmode="decimal" id="f-buy-strike" placeholder="es. 25" oninput="validateStrikesLive(); calcPreview();" /></div>
- <div class="form-group"><label>Premio Netto Incassato ($)</label><input type="text" inputmode="decimal" id="f-premium" placeholder="es. 0,26" oninput="calcPreview()" /></div>
- <div class="form-group"><label>Commissioni ($)</label><input type="text" inputmode="decimal" id="f-fees" placeholder="es. 6,50" value="0" oninput="calcPreview()" /></div>
- </div>
- <!-- STRIKE ERROR -->
- <div id="strikeError" style="display:none;background:rgba(255,77,109,.1);border:1px solid var(--red);border-radius:8px;padding:10px 14px;font-size:13px;color:var(--red);margin-top:10px;">
- β <strong>Strike non valido:</strong> per un Bull Put Spread la Put COMPRATA deve essere <em>inferiore</em> alla Put VENDUTA. Per un Bear Call Spread la Call COMPRATA deve essere <em>superiore</em> alla Call VENDUTA.
- </div>
- <!-- CALC PREVIEW -->
- <div class="calc-preview" id="calcPreview" style="grid-template-columns:repeat(4,1fr);">
- <div class="calc-item">
- <label>Max Profitto Netto</label>
- <span class="green" id="cp-maxprofit">β</span>
- <small style="color:var(--muted);font-size:10px;display:block;margin-top:3px;">Premio Γ Molt. Γ Cont. β Comm.</small>
- </div>
- <div class="calc-item">
- <label>Max Perdita (con SL)</label>
- <span class="red" id="cp-maxloss">β</span>
- <small style="color:var(--muted);font-size:10px;display:block;margin-top:3px;">SL debit + Comm. β Max Profitto</small>
- </div>
- <div class="calc-item">
- <label>R/R Ratio</label>
- <span id="cp-rr">β</span>
- <small style="color:var(--muted);font-size:10px;display:block;margin-top:3px;">Max Perdita SL Γ· Max Profitto</small>
- </div>
- <div class="calc-item">
- <label>ROI % (su margine broker)</label>
- <span class="green" id="cp-roi">β</span>
- <small style="color:var(--muted);font-size:10px;display:block;margin-top:3px;">Max Profitto Netto / Margine</small>
- </div>
- </div>
- <div class="form-row">
- <button class="btn btn-success" onclick="saveTrade()">β Salva Trade</button>
- <button class="btn btn-outline" onclick="resetForm()">β Reset</button>
- </div>
- </div>
- <!-- TRADES TABLE -->
- <div class="section">
- <div class="section-title">
- <span>Trades Recenti</span>
- <div style="display:flex;gap:8px;">
- <select id="filterStatus" onchange="renderTable()" style="background:var(--surface2);border:1px solid var(--border);color:var(--text);border-radius:8px;padding:6px 10px;font-size:12px;">
- <option value="all">Tutti i stati</option>
- <option value="open">Aperti</option>
- <option value="closed">Chiusi (Win)</option>
- <option value="stop">Stop Loss</option>
- </select>
- <select id="filterType" onchange="renderTable()" style="background:var(--surface2);border:1px solid var(--border);color:var(--text);border-radius:8px;padding:6px 10px;font-size:12px;">
- <option value="all">Tutti i tipi</option>
- <option value="bull_put">Bull Put</option>
- <option value="bear_call">Bear Call</option>
- </select>
- </div>
- </div>
- <div id="tradeList"></div>
- <div id="noTrades" style="text-align:center;padding:40px;color:var(--muted);">Nessun trade ancora. Aggiungi il tuo primo trade!</div>
- </div>
- </div>
- </div><!-- end page-dashboard -->
- <!-- βββββββββββββββββββββββββββ PAGE: ANALYTICS βββββββββββββββββββββββββββ -->
- <div class="page" id="page-analytics">
- <div class="container">
- <!-- KPI BAR -->
- <div class="grid-4" style="margin-bottom:20px;">
- <div class="kpi highlight">
- <label>P&L Cumulativo</label>
- <div class="val green" id="an-equity">$0,00</div>
- <div class="sub" id="an-equity-sub">su 0 trade chiusi</div>
- </div>
- <div class="kpi">
- <label>Profit Factor</label>
- <div class="val" id="an-pf">β</div>
- <div class="sub">Gross Win Γ· Gross Loss (>1 = profittevole)</div>
- </div>
- <div class="kpi">
- <label>Max Drawdown</label>
- <div class="val red" id="an-dd">$0,00</div>
- <div class="sub">peggior perdita cumulata consecutiva</div>
- </div>
- <div class="kpi">
- <label>Avg Win / Avg Loss</label>
- <div class="val" id="an-avgwl">β</div>
- <div class="sub">qualitΓ media per trade</div>
- </div>
- </div>
- <!-- EQUITY CURVE (full width) con filtro temporale -->
- <div class="chart-box" style="margin-bottom:20px;">
- <div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;margin-bottom:4px;">
- <div>
- <div class="chart-title">Equity Curve β P&L Cumulativo nel Tempo</div>
- <div class="chart-sub">Ogni punto = chiusura di un trade. Filtra per periodo per vedere l'andamento.</div>
- </div>
- <div style="display:flex;gap:6px;flex-wrap:wrap;">
- <button class="eq-btn active" onclick="setEqFilter('all',this)">Tutto</button>
- <button class="eq-btn" onclick="setEqFilter('1y',this)">1 Anno</button>
- <button class="eq-btn" onclick="setEqFilter('6m',this)">6 Mesi</button>
- <button class="eq-btn" onclick="setEqFilter('3m',this)">3 Mesi</button>
- <button class="eq-btn" onclick="setEqFilter('1m',this)">1 Mese</button>
- </div>
- </div>
- <canvas id="chartEquity" style="max-height:300px;"></canvas>
- <div class="no-data-chart" id="nd-equity"><span>π</span>Nessun trade chiuso nel periodo selezionato</div>
- </div>
- <!-- ROW 2: Mensile (waterfall) + Drawdown -->
- <div class="grid-1-1" style="margin-bottom:20px;">
- <div class="chart-box">
- <div class="chart-title">P&L per Mese</div>
- <div class="chart-sub">Profitto/perdita realizzata aggregata per mese di chiusura</div>
- <canvas id="chartMonthly" style="max-height:240px;"></canvas>
- <div class="no-data-chart" id="nd-monthly"><span>π </span>Nessun trade chiuso</div>
- </div>
- <div class="chart-box">
- <div class="chart-title">Drawdown nel Tempo</div>
- <div class="chart-sub">Distanza dal picco massimo di equity β misura la resilienza del metodo</div>
- <canvas id="chartDrawdown" style="max-height:240px;"></canvas>
- <div class="no-data-chart" id="nd-drawdown"><span>π</span>Nessun trade chiuso</div>
- </div>
- </div>
- <!-- ROW 3: Outcome donut + Holding Period + Asset Type -->
- <div class="grid-3" style="margin-bottom:20px;">
- <div class="chart-box">
- <div class="chart-title">Distribuzione Esiti</div>
- <div class="chart-sub">Scadenza Β· Stop Loss Β· Uscita Anticipata</div>
- <canvas id="chartDonut" style="max-height:200px;"></canvas>
- <div class="no-data-chart" id="nd-donut"><span>π©</span>Nessun trade chiuso</div>
- </div>
- <div class="chart-box">
- <div class="chart-title">Holding Period</div>
- <div class="chart-sub">Distribuzione dei giorni di permanenza in posizione</div>
- <canvas id="chartHolding" style="max-height:200px;"></canvas>
- <div class="no-data-chart" id="nd-holding"><span>β±</span>Nessun trade chiuso</div>
- </div>
- <div class="chart-box">
- <div class="chart-title">P&L per Tipo Strumento</div>
- <div class="chart-sub">Stock Β· ETF Β· Future β profitto netto realizzato</div>
- <canvas id="chartAsset" style="max-height:200px;"></canvas>
- <div class="no-data-chart" id="nd-asset"><span>π</span>Nessun trade chiuso</div>
- </div>
- </div>
- <!-- ROW 4: Bull Put vs Bear Call + P&L Reale per trade -->
- <div class="grid-1-1" style="margin-bottom:20px;">
- <div class="chart-box">
- <div class="chart-title">Bull Put vs Bear Call</div>
- <div class="chart-sub">Avg P&L realizzato e Win Rate per tipologia di spread</div>
- <canvas id="chartTypeComp" style="max-height:240px;"></canvas>
- <div class="no-data-chart" id="nd-type"><span>βοΈ</span>Nessun trade chiuso</div>
- </div>
- <div class="chart-box">
- <div class="chart-title">P&L Realizzato per Trade</div>
- <div class="chart-sub">Ogni barra = un trade chiuso, in ordine cronologico</div>
- <canvas id="chartPnlBar" style="max-height:240px;"></canvas>
- <div class="no-data-chart" id="nd-pnlbar"><span>πΉ</span>Nessun trade chiuso</div>
- </div>
- </div>
- <!-- CALENDARIO SCADENZE (full width) -->
- <div class="chart-box" style="margin-bottom:20px;">
- <div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;margin-bottom:4px;">
- <div>
- <div class="chart-title">π Calendario Scadenze Avanzato</div>
- <div class="chart-sub">Scadenze organizzate per mese con statistiche dettagliate β quantitΓ , tipologia, esiti, P&L</div>
- </div>
- <div class="cal-filter-bar" id="calYearFilter"></div>
- </div>
- <!-- Calendar summary KPIs -->
- <div class="grid-4" id="calSummaryKpis" style="margin:10px 0 14px;"></div>
- <div id="expiryCalendar" style="margin-top:0;"></div>
- </div>
- <!-- STATS TABLE full width -->
- <div class="chart-box" style="margin-bottom:20px;">
- <div class="chart-title">π Statistiche Avanzate</div>
- <div class="chart-sub">Riepilogo completo della performance</div>
- <div id="statsTable" style="margin-top:12px;display:grid;grid-template-columns:1fr 1fr;gap:0 40px;"></div>
- </div>
- </div>
- </div><!-- end page-analytics -->
- <!-- CLOSE MODAL -->
- <div class="modal-overlay" id="closeModal">
- <div class="modal">
- <h2>π Chiudi Trade</h2>
- <input type="hidden" id="close-trade-id">
- <div class="form-grid" style="grid-template-columns:1fr 1fr;">
- <div class="form-group"><label>Prezzo di Chiusura (debit pagato, 0 se scaduto)</label><input type="text" inputmode="decimal" id="close-price" placeholder="es. 0,20" value="0" oninput="onClosePriceChange()" /></div>
- <div class="form-group"><label>Data Chiusura</label><input type="date" id="close-date" /></div>
- <div class="form-group"><label id="close-fees-label">Commissioni Chiusura ($) <span id="close-fees-note" style="color:var(--green);font-size:10px;">β Scadenza β nessuna comm.</span></label><input type="text" inputmode="decimal" id="close-fees" placeholder="es. 6,50" value="0" disabled style="opacity:.4;cursor:not-allowed;" /></div>
- <div class="form-group"><label>Motivo Chiusura</label>
- <select id="close-reason">
- <option value="closed">β Profitto / Scadenza</option>
- <option value="stop">π Stop Loss</option>
- <option value="early">β© Chiusura Anticipata</option>
- </select>
- </div>
- </div>
- <div id="closePreview" style="background:var(--surface2);border-radius:8px;padding:12px;margin:14px 0;font-size:13px;"></div>
- <div style="display:flex;gap:10px;margin-top:16px;">
- <button class="btn btn-success" onclick="confirmClose()">Conferma Chiusura</button>
- <button class="btn btn-outline" onclick="closeModal()">Annulla</button>
- </div>
- </div>
- </div>
- <script>
- // βββ STATE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- let trades = JSON.parse(localStorage.getItem('vst_trades') || '[]');
- let nextId = parseInt(localStorage.getItem('vst_nextid') || '1');
- function save() {
- localStorage.setItem('vst_trades', JSON.stringify(trades));
- localStorage.setItem('vst_nextid', nextId);
- flashAutosave();
- }
- // βββ TOAST NOTIFICATION βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- let toastTimer = null;
- function showToast(msg, type = 'success', duration = 3000) {
- const el = document.getElementById('toast');
- el.textContent = msg;
- el.className = `toast ${type} show`;
- clearTimeout(toastTimer);
- toastTimer = setTimeout(() => el.classList.remove('show'), duration);
- }
- // βββ AUTO-SAVE FLASH ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function flashAutosave() {
- const dot = document.querySelector('.autosave-dot');
- if (!dot) return;
- dot.style.background = '#f9c74f';
- setTimeout(() => dot.style.background = '#00d395', 800);
- }
- // βββ EXPORT JSON (BACKUP COMPLETO) ββββββββββββββββββββββββββββββββββββββββββββ
- function exportJSON() {
- if (trades.length === 0) {
- showToast('β Nessun trade da salvare!', 'error'); return;
- }
- const backup = {
- version: 1,
- exportDate: new Date().toISOString(),
- nextId,
- trades
- };
- const blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
- const a = document.createElement('a');
- a.href = URL.createObjectURL(blob);
- a.download = `VST_backup_${today()}.json`;
- a.click();
- URL.revokeObjectURL(a.href);
- showToast(`πΎ Backup salvato β ${trades.length} trade esportati!`, 'success');
- }
- // βββ IMPORT JSON (RIPRISTINO) βββββββββββββββββββββββββββββββββββββββββββββββββ
- function importJSON() {
- // Chiede conferma solo se ci sono giΓ dati
- if (trades.length > 0) {
- if (!confirm(`β Attenzione!\n\nHai giΓ ${trades.length} trade caricati.\n\nScegli:\nβ’ OK = Sostituisci tutto con il backup\nβ’ Annulla = Aggiungi il backup ai trade esistenti\n\n(Premi OK per sostituire, Annulla per unire)`)) {
- document.getElementById('jsonFileInput').setAttribute('data-mode', 'merge');
- } else {
- document.getElementById('jsonFileInput').setAttribute('data-mode', 'replace');
- }
- } else {
- document.getElementById('jsonFileInput').setAttribute('data-mode', 'replace');
- }
- document.getElementById('jsonFileInput').value = ''; // reset per permettere di ricaricare lo stesso file
- document.getElementById('jsonFileInput').click();
- }
- function loadJSONFile(event) {
- const file = event.target.files[0];
- if (!file) return;
- const mode = document.getElementById('jsonFileInput').getAttribute('data-mode') || 'replace';
- const reader = new FileReader();
- reader.onload = function(e) {
- try {
- const backup = JSON.parse(e.target.result);
- // Supporta sia il formato con wrapper { version, trades } che un array diretto
- const incoming = Array.isArray(backup) ? backup : (backup.trades || []);
- const incomingNextId = backup.nextId || null;
- if (!incoming.length) {
- showToast('β File JSON vuoto o non valido.', 'error'); return;
- }
- if (mode === 'replace') {
- trades = incoming;
- nextId = incomingNextId || (Math.max(...incoming.map(t => t.id || 0)) + 1);
- } else {
- // Merge: evita duplicati per ID
- const existingIds = new Set(trades.map(t => t.id));
- const toAdd = incoming.filter(t => !existingIds.has(t.id));
- trades = [...trades, ...toAdd];
- nextId = Math.max(nextId, ...[...trades.map(t => t.id || 0)]) + 1;
- }
- save();
- renderAll();
- const modeLabel = mode === 'replace' ? 'sostituiti' : 'uniti';
- showToast(`β ${incoming.length} trade ${modeLabel} con successo!`, 'info', 4000);
- } catch(err) {
- showToast('β Errore: file JSON non valido o corrotto.', 'error', 5000);
- }
- };
- reader.readAsText(file);
- }
- // βββ INIT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- document.addEventListener('DOMContentLoaded', () => {
- document.getElementById('f-open-date').value = today();
- document.getElementById('close-date').value = today();
- renderAll();
- });
- function today() {
- return new Date().toISOString().split('T')[0];
- }
- // βββ EU NUMBER FORMATTING βββββββββββββββββββββββββββββββββββββββββββββββββββββ
- // Decimali con virgola, migliaia con punto (es: 1.234,56)
- function euFmt(n, decimals = 2) {
- return n.toLocaleString('it-IT', { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
- }
- function euCurr(n) {
- return '$' + euFmt(n, 2);
- }
- // Accetta sia "0.26" che "0,26" β fondamentale per utenti con tastiera europea
- function parseEU(str) {
- if (str === null || str === undefined) return NaN;
- const s = String(str).trim();
- // Se contiene sia punto che virgola: il punto Γ¨ migliaia, la virgola Γ¨ decimale
- // Es: "1.234,56" β "1234.56"
- if (s.includes(',') && s.includes('.')) {
- return parseFloat(s.replace(/\./g, '').replace(',', '.'));
- }
- // Se contiene solo virgola: Γ¨ il decimale
- // Es: "0,26" β "0.26"
- if (s.includes(',')) {
- return parseFloat(s.replace(',', '.'));
- }
- // Altrimenti Γ¨ giΓ formato JS standard con punto
- // Es: "1.4", "300", "6.5"
- return parseFloat(s);
- }
- // Converte un numero JS in stringa EU per i campi del form
- // Es: 1.4 β "1,4" | 300 β "300" | 6.5 β "6,5"
- function toField(n, decimals) {
- if (n === null || n === undefined || isNaN(n)) return '';
- if (decimals !== undefined) {
- return euFmt(n, decimals);
- }
- // Decimali automatici: mostra solo i decimali necessari
- const s = n.toString();
- const parts = s.split('.');
- if (parts.length === 1) return s; // numero intero, nessuna virgola
- return parts[0] + ',' + parts[1]; // sostituisce il punto con virgola
- }
- // βββ ASSET TYPE β AUTO MOLTIPLICATORE ββββββββββββββββββββββββββββββββββββββββ
- const MULT_MAP = { stock:100, etf:100, future_index:1, future_mp:1000, future_fx:100000 };
- function onAssetTypeChange() {
- const at = document.getElementById('f-asset-type').value;
- document.getElementById('f-multiplier').value = MULT_MAP[at] || 100;
- calcPreview();
- }
- // βββ SPREAD TYPE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function setSpreadType(type, btn) {
- document.getElementById('spreadType').value = type;
- document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
- btn.classList.add('active');
- const isBullPut = type === 'bull_put';
- document.getElementById('sell-strike-lbl').textContent = isBullPut ? 'Put VENDUTA (OTM)' : 'Call VENDUTA (OTM)';
- document.getElementById('buy-strike-lbl').textContent = isBullPut ? 'Put COMPRATA (piΓΉ bassa)' : 'Call COMPRATA (piΓΉ alta)';
- validateStrikesLive();
- calcPreview();
- }
- // βββ LIVE STRIKE VALIDATION βββββββββββββββββββββββββββββββββββββββββββββββββββ
- // PuΓ² essere chiamata senza parametri (aggiorna il DOM) o con valori giΓ parsati
- // (usata da saveTrade per validare senza toccare il DOM state)
- function validateStrikesLive(sellVal, buyVal, typeVal) {
- const sell = sellVal !== undefined ? sellVal : parseFloat(document.getElementById('f-sell-strike').value);
- const buy = buyVal !== undefined ? buyVal : parseFloat(document.getElementById('f-buy-strike').value);
- const type = typeVal !== undefined ? typeVal : document.getElementById('spreadType').value;
- const buyEl = document.getElementById('f-buy-strike');
- const errEl = document.getElementById('strikeError');
- // Se uno dei due campi Γ¨ vuoto/NaN β nessun errore, lascia passare
- if (isNaN(sell) || isNaN(buy) || sell === 0 || buy === 0) {
- buyEl.classList.remove('input-error');
- errEl.style.display = 'none';
- return true;
- }
- // Bull Put Spread: Put comprata DEVE essere INFERIORE alla Put venduta
- // Bear Call Spread: Call comprata DEVE essere SUPERIORE alla Call venduta
- const invalid = (type === 'bull_put' && buy >= sell) || (type === 'bear_call' && buy <= sell);
- // Aggiorna il DOM solo se chiamata senza parametri (dalla UI)
- if (sellVal === undefined) {
- buyEl.classList.toggle('input-error', invalid);
- errEl.style.display = invalid ? 'block' : 'none';
- }
- return !invalid;
- }
- // βββ CALC PREVIEW βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function calcPreview() {
- const prem = parseEU(document.getElementById('f-premium').value) || 0;
- const sell = parseEU(document.getElementById('f-sell-strike').value) || 0;
- const buy = parseEU(document.getElementById('f-buy-strike').value) || 0;
- const cont = parseInt(document.getElementById('f-contracts').value) || 1;
- const mult = parseEU(document.getElementById('f-multiplier').value) || 100;
- const broker = parseEU(document.getElementById('f-margin-broker').value) || 0;
- const fees = parseEU(document.getElementById('f-fees').value) || 0;
- if (!prem || !sell || !buy) return;
- // 1) Max Profitto Netto = (premio Γ molt Γ cont) β commissioni
- // Es: 0,26 Γ 100 Γ 5 = 130 β 6,50 comm = 123,50
- const grossPremium = prem * mult * cont;
- const maxProfit = grossPremium - fees;
- // 2) Stop Loss debit totale = premio Γ 3 Γ molt Γ cont
- // Es: 0,26 Γ 3 Γ 100 Γ 5 = 390,00
- const stopDebit = prem * 3 * mult * cont;
- // 3) Max Perdita con SL = stopDebit + commissioni β maxProfittoNetto
- // Es: 390 + 6,50 β 123,50 = 273,00
- const maxLossWithSL = stopDebit + fees - maxProfit;
- // 4) R/R = MaxPerditaSL / MaxProfittoNetto (quante volte rischio vs guadagno)
- // poi si toglie 1 per avere il rapporto puro
- // Es: 273 / 123,50 = 2,21 β mostro "1 : 2,21"
- const rrRaw = maxLossWithSL > 0 && maxProfit > 0 ? (maxLossWithSL / maxProfit) : null;
- const rrStr = rrRaw !== null ? `1 : ${euFmt(rrRaw, 2)}` : 'β';
- // 5) ROI = MaxProfittoNetto / margine broker Γ 100
- const roi = broker > 0 ? euFmt((maxProfit / broker) * 100, 2) + '%' : 'β';
- document.getElementById('cp-maxprofit').textContent = euCurr(maxProfit);
- document.getElementById('cp-maxloss').textContent = '-' + euCurr(maxLossWithSL);
- document.getElementById('cp-rr').textContent = rrStr;
- document.getElementById('cp-roi').textContent = roi;
- }
- // βββ SAVE TRADE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function saveTrade() {
- const ticker = document.getElementById('f-ticker').value.trim().toUpperCase();
- const openDate = document.getElementById('f-open-date').value;
- const expiry = document.getElementById('f-expiry').value;
- const contracts = parseInt(document.getElementById('f-contracts').value) || 1;
- const mult = parseEU(document.getElementById('f-multiplier').value) || 100;
- const brokerMargin= parseEU(document.getElementById('f-margin-broker').value) || 0;
- const sellStr = parseEU(document.getElementById('f-sell-strike').value);
- const buyStr = parseEU(document.getElementById('f-buy-strike').value);
- const prem = parseEU(document.getElementById('f-premium').value);
- const fees = parseEU(document.getElementById('f-fees').value) || 0;
- const type = document.getElementById('spreadType').value;
- const assetType = document.getElementById('f-asset-type').value;
- const editId = document.getElementById('editId').value;
- const alertEl = document.getElementById('formAlert');
- alertEl.className = 'alert';
- if (!ticker || !openDate || !expiry || isNaN(sellStr) || isNaN(buyStr) || isNaN(prem)) {
- alertEl.textContent = 'β Compila tutti i campi obbligatori.';
- alertEl.className = 'alert error'; return;
- }
- if (prem <= 0) { alertEl.textContent = 'β Il premio deve essere > 0.'; alertEl.className = 'alert error'; return; }
- // Valida gli strike passando i valori giΓ letti β nessuna dipendenza dal DOM state
- if (!validateStrikesLive(sellStr, buyStr, type)) {
- alertEl.textContent = 'β Strike non valido: per Bull Put la Put comprata deve essere inferiore. Per Bear Call la Call comprata deve essere superiore.';
- alertEl.className = 'alert error'; return;
- }
- const spread = Math.abs(sellStr - buyStr);
- const spreadVal = spread * mult * contracts;
- const grossPremium = prem * mult * contracts;
- const maxProfit = grossPremium - fees; // netto commissioni
- const stopDebit = prem * 3 * mult * contracts; // SL debit
- const maxLoss = stopDebit + fees - maxProfit; // perdita effettiva con SL
- const stopLoss = stopDebit; // soglia debit da monitorare
- const margin = brokerMargin > 0 ? brokerMargin : spreadVal;
- const trade = { id: editId ? parseInt(editId) : nextId++,
- ticker, type, assetType, openDate, expiry, contracts, multiplier: mult,
- sellStrike: sellStr, buyStrike: buyStr, spread, premium: prem, fees,
- brokerMargin, margin, maxProfit, maxLoss, stopLoss, status: 'open',
- closeDate: null, closePrice: null, closeFees: 0, pnl: null, closeReason: null };
- if (editId) {
- const idx = trades.findIndex(t => t.id === parseInt(editId));
- if (idx > -1) { trade.status = trades[idx].status; trade.closeDate = trades[idx].closeDate;
- trade.closePrice = trades[idx].closePrice; trade.pnl = trades[idx].pnl;
- trade.closeFees = trades[idx].closeFees; trade.closeReason = trades[idx].closeReason;
- trades[idx] = trade; }
- } else { trades.unshift(trade); }
- save();
- alertEl.textContent = editId ? 'β Trade aggiornato!' : 'β Trade salvato!';
- alertEl.className = 'alert success';
- setTimeout(() => alertEl.className = 'alert', 2500);
- resetForm();
- renderAll();
- }
- // βββ RESET FORM βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function resetForm() {
- ['f-ticker','f-sell-strike','f-buy-strike','f-premium','f-margin-broker'].forEach(id => document.getElementById(id).value = '');
- document.getElementById('f-contracts').value = 1;
- document.getElementById('f-fees').value = 0;
- document.getElementById('f-multiplier').value = 100;
- document.getElementById('f-asset-type').value = 'stock';
- document.getElementById('f-open-date').value = today();
- document.getElementById('f-expiry').value = '';
- document.getElementById('editId').value = '';
- document.getElementById('strikeError').style.display = 'none';
- document.getElementById('f-buy-strike').classList.remove('input-error');
- ['cp-maxprofit','cp-maxloss','cp-rr','cp-roi'].forEach(id => document.getElementById(id).textContent = 'β');
- }
- // βββ EDIT TRADE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function editTrade(id) {
- const t = trades.find(x => x.id === id); if (!t) return;
- document.getElementById('editId').value = t.id;
- document.getElementById('f-ticker').value = t.ticker;
- document.getElementById('f-open-date').value = t.openDate;
- document.getElementById('f-expiry').value = t.expiry;
- document.getElementById('f-contracts').value = t.contracts;
- document.getElementById('f-multiplier').value = toField(t.multiplier || 100);
- document.getElementById('f-margin-broker').value = t.brokerMargin ? toField(t.brokerMargin) : '';
- document.getElementById('f-sell-strike').value = toField(t.sellStrike);
- document.getElementById('f-buy-strike').value = toField(t.buyStrike);
- document.getElementById('f-premium').value = toField(t.premium);
- document.getElementById('f-fees').value = toField(t.fees || 0);
- document.getElementById('spreadType').value = t.type;
- document.getElementById('f-asset-type').value = t.assetType || 'stock';
- document.querySelectorAll('.tab').forEach(tab => {
- tab.classList.toggle('active', (t.type === 'bull_put' && tab.textContent.includes('Bull')) || (t.type === 'bear_call' && tab.textContent.includes('Bear')));
- });
- validateStrikesLive();
- calcPreview();
- window.scrollTo({ top: 200, behavior: 'smooth' });
- }
- // βββ DELETE TRADE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function deleteTrade(id) {
- if (!confirm('Eliminare questo trade?')) return;
- trades = trades.filter(t => t.id !== id);
- save(); renderAll();
- }
- // βββ RIAPRI TRADE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function reopenTrade(id) {
- const t = trades.find(x => x.id === id);
- if (!t) return;
- const label = { closed:'Chiuso', stop:'Stop Loss', early:'Uscita Anticipata' }[t.status] || t.status;
- if (!confirm(`Riaprire questo trade?\n\nStato attuale: ${label}\nP&L registrato: ${t.pnl !== null ? '$'+t.pnl.toFixed(2) : 'β'}\n\nConfermando, il trade tornerΓ "Aperto" e tutti i dati di chiusura saranno azzerati.`)) return;
- t.status = 'open';
- t.closeDate = null;
- t.closePrice = null;
- t.closeFees = 0;
- t.pnl = null;
- t.closeReason = null;
- save();
- renderAll();
- showToast('β© Trade riaperto β ora puoi chiuderlo correttamente.', 'info');
- }
- // βββ OPEN CLOSE MODAL βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function openModal() { window.scrollTo({ top: 200, behavior:'smooth'}); }
- function openCloseModal(id) {
- const t = trades.find(x => x.id === id); if (!t) return;
- const mult = t.multiplier || 100;
- const grossPremium = t.premium * mult * t.contracts;
- document.getElementById('close-trade-id').value = id;
- document.getElementById('close-date').value = today();
- document.getElementById('close-price').value = '0';
- document.getElementById('close-fees').value = '0';
- const prev = document.getElementById('closePreview');
- prev.innerHTML = `
- <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;font-size:13px;">
- <div><span style="color:var(--muted);font-size:10px;display:block;">TICKER / TIPO</span>
- <b>${t.ticker}</b> β ${t.type === 'bull_put' ? 'Bull Put' : 'Bear Call'}</div>
- <div><span style="color:var(--muted);font-size:10px;display:block;">PREMIO LORDO INCASSATO</span>
- <b style="color:var(--green)">${euCurr(grossPremium)}</b></div>
- <div><span style="color:var(--muted);font-size:10px;display:block;">STOP LOSS DEBIT SOGLIA</span>
- <b style="color:var(--red)">${euCurr(t.stopLoss)}</b> (riacquisto a ${euFmt(t.premium * 3, 4)})</div>
- <div><span style="color:var(--muted);font-size:10px;display:block;">COMM. APERTURA GIΓ PAGATE</span>
- <b>${euCurr(t.fees || 0)}</b></div>
- <div><span style="color:var(--muted);font-size:10px;display:block;">MOLTIPLICATORE</span>
- <b>${mult}</b></div>
- <div><span style="color:var(--muted);font-size:10px;display:block;">CONTRATTI</span>
- <b>${t.contracts}</b></div>
- </div>
- <div style="margin-top:10px;padding-top:8px;border-top:1px solid var(--border);font-size:11px;color:var(--muted);">
- P&L = Premio lordo β (Prezzo riacquisto Γ ${mult} Γ ${t.contracts}) β Comm. apertura β Comm. chiusura
- </div>`;
- document.getElementById('closeModal').classList.add('active');
- onClosePriceChange(); // imposta subito lo stato corretto del campo commissioni
- }
- function closeModal() { document.getElementById('closeModal').classList.remove('active'); }
- function onClosePriceChange() {
- const price = parseEU(document.getElementById('close-price').value) || 0;
- const feesInput = document.getElementById('close-fees');
- const noteEl = document.getElementById('close-fees-note');
- if (price === 0) {
- // Scaduto a zero: nessun riacquisto β nessuna commissione di chiusura
- feesInput.value = '0';
- feesInput.disabled = true;
- feesInput.style.opacity = '.4';
- feesInput.style.cursor = 'not-allowed';
- noteEl.textContent = 'β Scadenza β nessuna comm.';
- noteEl.style.color = 'var(--green)';
- } else {
- // Riacquisto attivo: commissioni di chiusura applicabili
- feesInput.disabled = false;
- feesInput.style.opacity = '1';
- feesInput.style.cursor = 'text';
- noteEl.textContent = '(riacquisto attivo)';
- noteEl.style.color = 'var(--muted)';
- }
- }
- function confirmClose() {
- const id = parseInt(document.getElementById('close-trade-id').value);
- const closePrice = parseEU(document.getElementById('close-price').value) || 0;
- const closeDate = document.getElementById('close-date').value;
- const closeFees = parseEU(document.getElementById('close-fees').value) || 0;
- const reason = document.getElementById('close-reason').value;
- const t = trades.find(x => x.id === id); if (!t) return;
- const mult = t.multiplier || 100;
- // P&L = Premio lordo incassato β debit pagato per riacquisto β commissioni apertura β commissioni chiusura
- const grossPremium = t.premium * mult * t.contracts;
- const buybackCost = closePrice * mult * t.contracts;
- const pnl = grossPremium - buybackCost - (t.fees || 0) - closeFees;
- t.closeDate = closeDate;
- t.closePrice = closePrice;
- t.closeFees = closeFees;
- t.pnl = pnl;
- t.status = reason;
- t.closeReason = reason;
- save(); renderAll(); closeModal();
- showToast(`β Trade chiuso β P&L: ${pnl >= 0 ? '+' : ''}${euCurr(pnl)}`, pnl >= 0 ? 'success' : 'error', 4000);
- }
- // βββ RENDER CARDS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderTable() {
- const filterSt = document.getElementById('filterStatus').value;
- const filterTy = document.getElementById('filterType').value;
- const list = document.getElementById('tradeList');
- list.innerHTML = '';
- const filtered = trades.filter(t =>
- (filterSt === 'all' || t.status === filterSt) &&
- (filterTy === 'all' || t.type === filterTy)
- );
- document.getElementById('noTrades').style.display = filtered.length ? 'none' : 'block';
- const assetLabels = { stock:'Stock', etf:'ETF', future_index:'Future Index', future_mp:'Future M.P.', future_fx:'Future Valute' };
- const statusLabel = { open:'β Aperto', closed:'β Chiuso', stop:'β Stop Loss', early:'β© Uscita Anticipata' };
- const statusColors = { open:'var(--green)', closed:'#777', stop:'var(--red)', early:'var(--yellow)' };
- filtered.forEach((t, i) => {
- const typeLabel = t.type === 'bull_put' ? 'Bull Put Spread' : 'Bear Call Spread';
- const typeBadge = t.type === 'bull_put' ? 'badge-put' : 'badge-call';
- const mult = t.multiplier || 100;
- const statusCol = statusColors[t.status] || statusColors.open;
- const stLabel = statusLabel[t.status] || t.status;
- // P&L display
- const hasPnl = t.pnl !== null;
- const pnlColor = hasPnl ? (t.pnl >= 0 ? 'var(--green)' : 'var(--red)') : 'var(--muted)';
- const pnlText = hasPnl ? `${t.pnl >= 0 ? '+' : ''}${euCurr(t.pnl)}` : 'Aperto';
- // ROI atteso = Max Profitto Netto / Margine Broker (fisso, usato per decidere il trade)
- const roiAtteso = (t.margin > 0 && t.maxProfit != null) ? euFmt((t.maxProfit / t.margin) * 100, 2) + '%' : 'β';
- const roiColor = 'var(--green)'; // sempre verde: Γ¨ un dato atteso, non realizzato
- list.innerHTML += `
- <div class="trade-card status-${t.status}">
- <!-- LEFT: ticker + badge + stato -->
- <div class="tc-left">
- <div class="tc-ticker">${t.ticker}</div>
- <span class="badge ${typeBadge}" style="text-align:center">${t.type === 'bull_put' ? 'Bull Put' : 'Bear Call'}</span>
- <div class="tc-asset">${assetLabels[t.assetType] || 'β'}</div>
- <div style="font-size:11px;font-weight:700;color:${statusCol};margin-top:2px;">${stLabel}</div>
- </div>
- <!-- MID: tutti i dati in griglia -->
- <div style="display:flex;flex-direction:column;gap:10px;">
- <div class="tc-mid">
- <div class="tc-field"><span>Strike Venduto</span><strong>${euCurr(t.sellStrike)}</strong></div>
- <div class="tc-field"><span>Strike Comprato</span><strong>${euCurr(t.buyStrike)}</strong></div>
- <div class="tc-field"><span>Contratti Γ Molt.</span><strong>${t.contracts} Γ ${mult}</strong></div>
- <div class="tc-field"><span>Premio Lordo</span><strong class="green">${euCurr(t.premium * mult * t.contracts)}</strong></div>
- <div class="tc-field"><span>Commissioni</span><strong>${euCurr(t.fees||0)}</strong></div>
- <div class="tc-field"><span>Max Profitto Netto</span><strong class="green">${euCurr(t.maxProfit)}</strong></div>
- <div class="tc-field"><span>Stop Loss Debit</span><strong class="red">${euCurr(t.stopLoss)}</strong></div>
- <div class="tc-field"><span>Max Perdita (con SL)</span><strong class="red">-${euCurr(t.maxLoss)}</strong></div>
- <div class="tc-field"><span>Margine Broker</span><strong class="yellow">${euCurr(t.margin)}</strong></div>
- <div class="tc-field"><span>R/R Ratio</span><strong>${t.maxProfit > 0 && t.maxLoss > 0 ? '1 : ' + euFmt(t.maxLoss/t.maxProfit, 2) : 'β'}</strong></div>
- </div>
- <div class="tc-divider"></div>
- <div class="tc-dates">
- <div class="tc-field"><span>Apertura</span><strong>${t.openDate}</strong></div>
- <div class="tc-field"><span>Scadenza</span><strong>${t.expiry}</strong></div>
- ${t.closeDate ? `<div class="tc-field"><span>Chiusura</span><strong>${t.closeDate}</strong></div>` : ''}
- ${t.closePrice !== null && t.closePrice !== undefined ? `<div class="tc-field"><span>Prezzo Chiusura</span><strong>$${parseFloat(t.closePrice).toFixed(2)}</strong></div>` : ''}
- </div>
- </div>
- <!-- RIGHT: P&L + ROI + azioni -->
- <div class="tc-right">
- <div class="tc-pnl" style="color:${pnlColor}">${pnlText}</div>
- <div class="tc-roi" style="color:${roiColor}">ROI atteso ${roiAtteso}</div>
- <div class="tc-actions">
- ${t.status === 'open'
- ? `<button class="btn btn-success btn-sm" onclick="openCloseModal(${t.id})">π Chiudi</button>`
- : `<button class="btn btn-sm" style="background:rgba(249,199,79,.15);border:1px solid var(--yellow);color:var(--yellow);" onclick="reopenTrade(${t.id})">β© Riapri</button>`
- }
- <button class="btn btn-outline btn-sm" onclick="editTrade(${t.id})">β Modifica</button>
- <button class="btn btn-danger btn-sm" onclick="deleteTrade(${t.id})">π</button>
- </div>
- </div>
- </div>`;
- });
- }
- // βββ RENDER KPIs ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderKPIs() {
- const open = trades.filter(t => t.status === 'open');
- const closed = trades.filter(t => t.status !== 'open');
- const all = trades; // tutti: aperti + chiusi
- // ββ Margine impiegato = somma margine broker posizioni APERTE
- const margineImpiegato = open.reduce((s, t) => s + (t.margin || 0), 0);
- // ββ P&L Realizzato
- const totalPnl = closed.reduce((s, t) => s + (t.pnl || 0), 0);
- // ββ Win Rate: chiusi in profitto (scadenza + uscita anticipata in profitto)
- const winsAll = closed.filter(t => (t.pnl || 0) > 0);
- const wr = closed.length > 0 ? ((winsAll.length / closed.length) * 100).toFixed(1) : 0;
- // ββ Dettaglio Win Rate per categoria
- const nExp = closed.filter(t => t.status === 'closed').length;
- const nEarly = closed.filter(t => t.status === 'early').length;
- const nStop = closed.filter(t => t.status === 'stop').length;
- const tot = closed.length || 1;
- const pct = n => ((n / tot) * 100).toFixed(1);
- // ββ ROI medio atteso = media semplice del ROI atteso di TUTTI i trade (aperti + chiusi)
- // ROI atteso = maxProfit / margin Γ 100 (giΓ calcolato al momento del salvataggio)
- const roiArr = all.filter(t => t.margin > 0 && t.maxProfit != null)
- .map(t => (t.maxProfit / t.margin) * 100);
- const roiAvg = roiArr.length > 0 ? (roiArr.reduce((a,b)=>a+b,0) / roiArr.length) : 0;
- // ββ AVERAGE box: medie su tutti i trade (aperti + chiusi)
- const avg = (arr, fn) => arr.length > 0 ? arr.reduce((s,t)=>s+(fn(t)||0),0)/arr.length : null;
- const avgProfit = avg(all, t => t.maxProfit);
- const avgLoss = avg(all, t => t.maxLoss);
- const avgMargin = avg(all, t => t.margin);
- // R/R per ogni trade = maxLoss / maxProfit
- const rrArr = all.filter(t => t.maxProfit > 0 && t.maxLoss > 0).map(t => t.maxLoss / t.maxProfit);
- const avgRR = rrArr.length > 0 ? rrArr.reduce((a,b)=>a+b,0)/rrArr.length : null;
- // ββ Aggiorno il DOM
- document.getElementById('k-open').textContent = open.length;
- document.getElementById('k-margin').textContent = euCurr(margineImpiegato);
- document.getElementById('k-margin').className = 'val yellow';
- document.getElementById('k-pnl').textContent = `${totalPnl >= 0 ? '+' : ''}${euCurr(totalPnl)}`;
- document.getElementById('k-pnl').className = `val ${totalPnl >= 0 ? 'green' : 'red'}`;
- document.getElementById('k-wr').textContent = euFmt(parseFloat(wr), 1) + '%';
- document.getElementById('k-wr').className = `val ${parseFloat(wr) >= 50 ? 'green' : 'red'}`;
- document.getElementById('k-wr-sub').textContent = `${winsAll.length} win / ${closed.length} chiusi`;
- document.getElementById('k-wr-exp').textContent = `${nExp} (${pct(nExp)}%)`;
- document.getElementById('k-wr-early').textContent = `${nEarly} (${pct(nEarly)}%)`;
- document.getElementById('k-wr-stop').textContent = `${nStop} (${pct(nStop)}%)`;
- document.getElementById('k-roi-avg').textContent = euFmt(roiAvg, 2) + '%';
- document.getElementById('k-roi-avg').className = `val ${roiAvg >= 0 ? 'green' : 'red'}`;
- document.getElementById('k-avg-profit').textContent = avgProfit !== null ? euCurr(avgProfit) : 'β';
- document.getElementById('k-avg-loss').textContent = avgLoss !== null ? euCurr(avgLoss) : 'β';
- document.getElementById('k-avg-margin').textContent = avgMargin !== null ? euCurr(avgMargin) : 'β';
- document.getElementById('k-avg-rr').textContent = avgRR !== null ? '1 : ' + euFmt(avgRR, 2) : 'β';
- }
- function renderAll() { renderKPIs(); renderTable(); if(document.getElementById('page-analytics').classList.contains('active')) renderAnalytics(); }
- // βββ PAGE NAVIGATION ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function showPage(id, btn) {
- document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
- document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
- document.getElementById('page-' + id).classList.add('active');
- btn.classList.add('active');
- if (id === 'analytics') renderAnalytics();
- }
- // βββ CHART DEFAULTS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- const C = { green:'#00d395', red:'#ff4d6d', accent:'#6c63ff', yellow:'#f9c74f',
- blue:'#4cc9f0', orange:'#f77f00', purple:'#b5179e', muted:'#8892b0',
- surface2:'#22263a', border:'#2e3250', text:'#e2e8f0' };
- Chart.defaults.color = C.muted;
- Chart.defaults.borderColor = C.border;
- Chart.defaults.font.family = "'Segoe UI', sans-serif";
- Chart.defaults.font.size = 12;
- let charts = {};
- function destroyChart(id) { if(charts[id]) { charts[id].destroy(); delete charts[id]; } }
- function noData(canvasId, ndId, show) {
- document.getElementById(canvasId).style.display = show ? 'none' : 'block';
- document.getElementById(ndId).style.display = show ? 'flex' : 'none';
- }
- // βββ ANALYTICS STATE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- let eqFilter = 'all';
- function setEqFilter(f, btn) {
- eqFilter = f;
- document.querySelectorAll('.eq-btn').forEach(b => b.classList.remove('active'));
- btn.classList.add('active');
- const closed = getFilteredClosed();
- renderEquityCurve(closed);
- }
- function getFilteredClosed() {
- const all = trades.filter(t => t.status !== 'open' && t.pnl !== null)
- .sort((a,b) => new Date(a.closeDate) - new Date(b.closeDate));
- if (eqFilter === 'all') return all;
- const now = new Date();
- const ms = { '1m':30, '3m':90, '6m':180, '1y':365 }[eqFilter] * 86400000;
- return all.filter(t => t.closeDate && (now - new Date(t.closeDate)) <= ms);
- }
- // βββ ANALYTICS MASTER RENDER ββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderAnalytics() {
- const allClosed = trades.filter(t => t.status !== 'open' && t.pnl !== null)
- .sort((a,b) => new Date(a.closeDate) - new Date(b.closeDate));
- const filtered = getFilteredClosed();
- renderAnalyticsKPIs(allClosed);
- renderEquityCurve(filtered);
- renderMonthlyPnl(allClosed);
- renderDrawdown(allClosed);
- renderDonut(allClosed);
- renderHolding(allClosed);
- renderAssetType(allClosed);
- renderTypeComp(allClosed);
- renderPnlBar(allClosed);
- renderExpiryCalendar();
- renderStatsTable(allClosed);
- }
- // βββ ANALYTICS KPIs βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderAnalyticsKPIs(closed) {
- const totalPnl = closed.reduce((s,t)=>s+(t.pnl||0),0);
- const wins = closed.filter(t=>t.pnl>0);
- const losses = closed.filter(t=>t.pnl<=0);
- const grossWin = wins.reduce((s,t)=>s+t.pnl,0);
- const grossLoss = Math.abs(losses.reduce((s,t)=>s+t.pnl,0));
- const pf = grossLoss>0 ? euFmt(grossWin/grossLoss,2) : wins.length>0 ? 'β' : 'β';
- const avgWin = wins.length>0 ? wins.reduce((s,t)=>s+t.pnl,0)/wins.length : 0;
- const avgLoss = losses.length>0 ? Math.abs(losses.reduce((s,t)=>s+t.pnl,0)/losses.length) : 0;
- const avgWL = avgLoss>0 ? euFmt(avgWin/avgLoss,2) : avgWin>0 ? 'β' : 'β';
- let peak=0, dd=0, cumul=0;
- closed.forEach(t=>{ cumul+=t.pnl; if(cumul>peak) peak=cumul; const d=peak-cumul; if(d>dd) dd=d; });
- document.getElementById('an-equity').textContent = (totalPnl>=0?'+':'')+euCurr(totalPnl);
- document.getElementById('an-equity').className = `val ${totalPnl>=0?'green':'red'}`;
- document.getElementById('an-equity-sub').textContent = `su ${closed.length} trade chiusi`;
- document.getElementById('an-pf').textContent = pf;
- document.getElementById('an-pf').className = `val ${parseFloat(String(pf).replace(',','.'))>=1?'green':'red'}`;
- document.getElementById('an-dd').textContent = '-'+euCurr(dd);
- document.getElementById('an-avgwl').textContent = avgWL;
- document.getElementById('an-avgwl').className = `val ${parseFloat(String(avgWL).replace(',','.'))>=1?'green':'red'}`;
- }
- // βββ EQUITY CURVE (con filtro temporale) ββββββββββββββββββββββββββββββββββββββ
- function renderEquityCurve(closed) {
- destroyChart('equity');
- if (!closed.length) { noData('chartEquity','nd-equity',true); return; }
- noData('chartEquity','nd-equity',false);
- let cum=0;
- const labels = closed.map(t=>t.closeDate||t.openDate);
- const data = closed.map(t=>{ cum+=t.pnl; return parseFloat(cum.toFixed(2)); });
- const ptColors = data.map(v=>v>=0?C.green:C.red);
- charts.equity = new Chart(document.getElementById('chartEquity'),{
- type:'line',
- data:{ labels, datasets:[{
- label:'Equity ($)', data,
- borderColor:C.accent, borderWidth:2.5,
- pointBackgroundColor:ptColors, pointBorderColor:ptColors,
- pointRadius:4, pointHoverRadius:7,
- fill:true,
- backgroundColor:(ctx)=>{
- const g=ctx.chart.ctx.createLinearGradient(0,0,0,260);
- g.addColorStop(0,'rgba(108,99,255,0.3)');
- g.addColorStop(1,'rgba(108,99,255,0.01)');
- return g;
- }, tension:0.3
- }]},
- options:{ responsive:true, plugins:{ legend:{display:false},
- tooltip:{ callbacks:{
- title: ctx => `Data: ${ctx[0].label}`,
- label: c => `Equity cumulativa: ${euCurr(c.raw)}`
- }}},
- scales:{
- x:{ ticks:{maxRotation:45,font:{size:10}}, grid:{color:C.border} },
- y:{ ticks:{callback:v=>euCurr(v)}, grid:{color:C.border} }
- }}
- });
- }
- // βββ MONTHLY P&L ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderMonthlyPnl(closed) {
- destroyChart('monthly');
- const wd=closed.filter(t=>t.closeDate);
- if (!wd.length) { noData('chartMonthly','nd-monthly',true); return; }
- noData('chartMonthly','nd-monthly',false);
- const map={};
- wd.forEach(t=>{ const k=t.closeDate.substring(0,7); map[k]=(map[k]||0)+t.pnl; });
- const sorted=Object.entries(map).sort((a,b)=>a[0].localeCompare(b[0]));
- // Etichette leggibili: "Gen 25" etc.
- const monthNames=['Gen','Feb','Mar','Apr','Mag','Giu','Lug','Ago','Set','Ott','Nov','Dic'];
- const labels=sorted.map(([k])=>{ const [y,m]=k.split('-'); return `${monthNames[parseInt(m)-1]} '${y.slice(2)}`; });
- const data=sorted.map(e=>parseFloat(e[1].toFixed(2)));
- charts.monthly = new Chart(document.getElementById('chartMonthly'),{
- type:'bar',
- data:{ labels, datasets:[{ label:'P&L Mensile', data,
- backgroundColor:data.map(v=>v>=0?'rgba(0,211,149,0.75)':'rgba(255,77,109,0.75)'),
- borderColor:data.map(v=>v>=0?C.green:C.red),
- borderWidth:1.5, borderRadius:5 }]},
- options:{ responsive:true, plugins:{ legend:{display:false},
- tooltip:{callbacks:{label:c=>`P&L: ${euCurr(c.raw)}`}}},
- scales:{ x:{grid:{color:C.border}}, y:{ticks:{callback:v=>euCurr(v)},grid:{color:C.border}} }}
- });
- }
- // βββ DRAWDOWN βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderDrawdown(closed) {
- destroyChart('drawdown');
- if (!closed.length) { noData('chartDrawdown','nd-drawdown',true); return; }
- noData('chartDrawdown','nd-drawdown',false);
- let peak=0, cum=0;
- const labels=closed.map(t=>t.closeDate||t.openDate);
- const data=closed.map(t=>{ cum+=t.pnl; if(cum>peak) peak=cum; return parseFloat((-(peak-cum)).toFixed(2)); });
- charts.drawdown = new Chart(document.getElementById('chartDrawdown'),{
- type:'line',
- data:{ labels, datasets:[{ label:'Drawdown ($)', data,
- borderColor:C.red, borderWidth:2,
- pointRadius:3, pointBackgroundColor:C.red,
- fill:true,
- backgroundColor:'rgba(255,77,109,0.12)',
- tension:0.3 }]},
- options:{ responsive:true, plugins:{ legend:{display:false},
- tooltip:{callbacks:{label:c=>`DD: ${euCurr(c.raw)}`}}},
- scales:{
- x:{ticks:{maxRotation:45,font:{size:10}},grid:{color:C.border}},
- y:{ticks:{callback:v=>euCurr(v)},grid:{color:C.border}}
- }}
- });
- }
- // βββ DONUT ESITI ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderDonut(closed) {
- destroyChart('donut');
- if (!closed.length) { noData('chartDonut','nd-donut',true); return; }
- noData('chartDonut','nd-donut',false);
- const exp =closed.filter(t=>t.status==='closed').length;
- const stops=closed.filter(t=>t.status==='stop').length;
- const early=closed.filter(t=>t.status==='early').length;
- charts.donut = new Chart(document.getElementById('chartDonut'),{
- type:'doughnut',
- data:{ labels:['β Scadenza','π Stop Loss','β© Uscita Ant.'],
- datasets:[{ data:[exp,stops,early],
- backgroundColor:[C.green,C.red,C.yellow],
- borderColor:C.surface2, borderWidth:3, hoverOffset:10 }]},
- options:{ responsive:true, cutout:'65%',
- plugins:{ legend:{position:'bottom',labels:{padding:14,usePointStyle:true,font:{size:11}}},
- tooltip:{callbacks:{label:c=>`${c.label}: ${c.raw} (${closed.length>0?(c.raw/closed.length*100).toFixed(1):0}%)`}}
- }}
- });
- }
- // βββ HOLDING PERIOD βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderHolding(closed) {
- destroyChart('holding');
- const wd=closed.filter(t=>t.closeDate&&t.openDate);
- if (!wd.length) { noData('chartHolding','nd-holding',true); return; }
- noData('chartHolding','nd-holding',false);
- const b={'β€5gg':0,'6-14gg':0,'15-30gg':0,'31-60gg':0,'61-90gg':0,'90+gg':0};
- wd.forEach(t=>{
- const d=Math.round((new Date(t.closeDate)-new Date(t.openDate))/86400000);
- if(d<=5) b['β€5gg']++; else if(d<=14) b['6-14gg']++; else if(d<=30) b['15-30gg']++;
- else if(d<=60) b['31-60gg']++; else if(d<=90) b['61-90gg']++; else b['90+gg']++;
- });
- const pal=[C.accent,'#7c75ff',C.blue,'#74b9ff',C.yellow,C.orange];
- charts.holding = new Chart(document.getElementById('chartHolding'),{
- type:'bar',
- data:{ labels:Object.keys(b),
- datasets:[{ label:'NΒ° Trade', data:Object.values(b),
- backgroundColor:pal, borderColor:pal, borderWidth:1.5, borderRadius:5 }]},
- options:{ responsive:true, plugins:{legend:{display:false},
- tooltip:{callbacks:{label:c=>`${c.raw} trade`}}},
- scales:{ x:{grid:{color:C.border}},
- y:{ticks:{stepSize:1,callback:v=>Number.isInteger(v)?v:''},grid:{color:C.border}} }}
- });
- }
- // βββ P&L PER TIPO STRUMENTO βββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderAssetType(closed) {
- destroyChart('asset');
- if (!closed.length) { noData('chartAsset','nd-asset',true); return; }
- noData('chartAsset','nd-asset',false);
- const labels={'stock':'Stock','etf':'ETF','future_index':'Future Index','future_mp':'Future M.P.','future_fx':'Future Valute'};
- const map={};
- closed.forEach(t=>{ const k=labels[t.assetType]||t.assetType||'Altro'; map[k]=(map[k]||0)+t.pnl; });
- const sorted=Object.entries(map).sort((a,b)=>b[1]-a[1]);
- const pal=['rgba(108,99,255,0.8)','rgba(76,201,240,0.8)','rgba(249,199,79,0.8)','rgba(0,211,149,0.8)','rgba(255,77,109,0.8)'];
- charts.asset = new Chart(document.getElementById('chartAsset'),{
- type:'bar',
- data:{ labels:sorted.map(e=>e[0]),
- datasets:[{ label:'P&L ($)', data:sorted.map(e=>parseFloat(e[1].toFixed(2))),
- backgroundColor:sorted.map((e,i)=>e[1]>=0?pal[i%pal.length]:'rgba(255,77,109,0.75)'),
- borderRadius:6, borderWidth:1.5 }]},
- options:{ responsive:true, plugins:{legend:{display:false},
- tooltip:{callbacks:{label:c=>`P&L: ${euCurr(c.raw)}`}}},
- scales:{ x:{grid:{color:C.border}}, y:{ticks:{callback:v=>euCurr(v)},grid:{color:C.border}} }}
- });
- }
- // βββ BULL PUT vs BEAR CALL ββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderTypeComp(closed) {
- destroyChart('typeComp');
- if (!closed.length) { noData('chartTypeComp','nd-type',true); return; }
- noData('chartTypeComp','nd-type',false);
- const bp=closed.filter(t=>t.type==='bull_put');
- const bc=closed.filter(t=>t.type==='bear_call');
- const avgPnl=arr=>arr.length>0?arr.reduce((s,t)=>s+t.pnl,0)/arr.length:0;
- const wr=arr=>arr.length>0?arr.filter(t=>t.pnl>0).length/arr.length*100:0;
- charts.typeComp = new Chart(document.getElementById('chartTypeComp'),{
- type:'bar',
- data:{ labels:['Bull Put Spread','Bear Call Spread'],
- datasets:[
- { label:'Avg P&L ($)', data:[parseFloat(avgPnl(bp).toFixed(2)),parseFloat(avgPnl(bc).toFixed(2))],
- backgroundColor:['rgba(249,199,79,0.75)','rgba(108,99,255,0.75)'],
- borderColor:[C.yellow,C.accent], borderWidth:1.5, borderRadius:6, yAxisID:'y' },
- { label:'Win Rate (%)', data:[parseFloat(wr(bp).toFixed(1)),parseFloat(wr(bc).toFixed(1))],
- type:'line', yAxisID:'y2', tension:0.3,
- borderColor:C.green, borderWidth:2.5,
- pointBackgroundColor:C.green, pointRadius:7, pointHoverRadius:10,
- backgroundColor:'transparent' }
- ]},
- options:{ responsive:true,
- plugins:{legend:{position:'bottom',labels:{usePointStyle:true,padding:14}}},
- scales:{
- y:{position:'left',ticks:{callback:v=>euCurr(v)},grid:{color:C.border}},
- y2:{position:'right',min:0,max:100,ticks:{callback:v=>v+'%'},grid:{drawOnChartArea:false}}
- }}
- });
- }
- // βββ P&L REALIZZATO PER TRADE (barre) βββββββββββββββββββββββββββββββββββββββββ
- function renderPnlBar(closed) {
- destroyChart('pnlbar');
- if (!closed.length) { noData('chartPnlBar','nd-pnlbar',true); return; }
- noData('chartPnlBar','nd-pnlbar',false);
- const labels=closed.map((t,i)=>`#${i+1} ${t.closeDate||''}`);
- const data=closed.map(t=>parseFloat(t.pnl.toFixed(2)));
- charts.pnlbar = new Chart(document.getElementById('chartPnlBar'),{
- type:'bar',
- data:{ labels, datasets:[{ label:'P&L ($)', data,
- backgroundColor:data.map(v=>v>=0?'rgba(0,211,149,0.75)':'rgba(255,77,109,0.75)'),
- borderColor:data.map(v=>v>=0?C.green:C.red),
- borderWidth:1.5, borderRadius:5 }]},
- options:{ responsive:true, plugins:{legend:{display:false},
- tooltip:{callbacks:{label:c=>`P&L: ${euCurr(c.raw)}`}}},
- scales:{ x:{ticks:{maxRotation:45,font:{size:10}},grid:{color:C.border}},
- y:{ticks:{callback:v=>euCurr(v)},grid:{color:C.border}} }}
- });
- }
- // βββ CALENDARIO SCADENZE AVANZATO βββββββββββββββββββββββββββββββββββββββββββββ
- let calYearFilter = 'all';
- function setCalYear(yr, btn) {
- calYearFilter = yr;
- document.querySelectorAll('.cal-yr-btn').forEach(b=>b.classList.remove('active'));
- btn.classList.add('active');
- renderExpiryCalendar();
- }
- function renderExpiryCalendar() {
- const el = document.getElementById('expiryCalendar');
- const kpiEl = document.getElementById('calSummaryKpis');
- const filterEl = document.getElementById('calYearFilter');
- if (!trades.length) { el.innerHTML='<div class="no-data-chart" style="height:80px"><span>π </span>Nessun trade inserito</div>'; kpiEl.innerHTML=''; filterEl.innerHTML=''; return; }
- const now = new Date();
- const today = now.toISOString().split('T')[0];
- // Collect years for filter
- const years = [...new Set(trades.filter(t=>t.expiry).map(t=>t.expiry.substring(0,4)))].sort();
- filterEl.innerHTML = `<button class="cal-yr-btn ${calYearFilter==='all'?'active':''}" onclick="setCalYear('all',this)">Tutti</button>` +
- years.map(y=>`<button class="cal-yr-btn ${calYearFilter===y?'active':''}" onclick="setCalYear('${y}',this)">${y}</button>`).join('');
- // Filter trades
- const filtered = trades.filter(t => t.expiry && (calYearFilter==='all' || t.expiry.startsWith(calYearFilter)));
- // Group by YYYY-MM
- const map = {};
- filtered.forEach(t => { const k=t.expiry.substring(0,7); if(!map[k]) map[k]=[]; map[k].push(t); });
- const sorted = Object.entries(map).sort((a,b)=>a[0].localeCompare(b[0]));
- // Summary KPIs
- const totalExp = filtered.length;
- const openExp = filtered.filter(t=>t.status==='open').length;
- const closedExp = totalExp - openExp;
- const totalPremium = filtered.reduce((s,t)=>s+t.premium*(t.multiplier||100)*t.contracts,0);
- const closedPnl = filtered.filter(t=>t.status!=='open'&&t.pnl!=null).reduce((s,t)=>s+t.pnl,0);
- const winCount = filtered.filter(t=>t.status!=='open'&&t.pnl>0).length;
- const wr = closedExp>0 ? (winCount/closedExp*100).toFixed(1) : 'β';
- kpiEl.innerHTML = `
- <div class="kpi"><label>Scadenze Totali</label><div class="val">${totalExp}</div><div class="sub">${openExp} aperte Β· ${closedExp} chiuse</div></div>
- <div class="kpi"><label>Win Rate</label><div class="val ${closedExp>0&&winCount/closedExp>=.5?'green':'red'}">${wr}%</div><div class="sub">${winCount} vinti su ${closedExp} chiusi</div></div>
- <div class="kpi"><label>Premio Lordo</label><div class="val yellow">${euCurr(totalPremium)}</div><div class="sub">premio totale incassato</div></div>
- <div class="kpi"><label>P&L Realizzato</label><div class="val ${closedPnl>=0?'green':'red'}">${(closedPnl>=0?'+':'')+euCurr(closedPnl)}</div><div class="sub">sui trade chiusi</div></div>`;
- const monthNames=['Gen','Feb','Mar','Apr','Mag','Giu','Lug','Ago','Set','Ott','Nov','Dic'];
- const assetShort={'stock':'STK','etf':'ETF','future_index':'IDX','future_mp':'MP','future_fx':'FX'};
- const typeShort={'bull_put':'BP','bear_call':'BC'};
- const typeColors={'bull_put':'var(--green)','bear_call':'var(--red)'};
- const outcomeLabels={closed:'Scadenza',stop:'Stop Loss',early:'Uscita Ant.'};
- const outcomeColors={closed:'rgba(142,142,142,.2)',stop:'rgba(255,71,87,.2)',early:'rgba(255,214,10,.2)'};
- const outcomeFg={closed:'#999',stop:'var(--red)',early:'var(--yellow)'};
- // Find max month pnl for bar scaling
- const monthPnls = sorted.map(([,ts])=> {
- const c=ts.filter(t=>t.status!=='open'&&t.pnl!=null);
- return c.reduce((s,t)=>s+t.pnl,0);
- });
- const maxAbsPnl = Math.max(...monthPnls.map(Math.abs), 1);
- el.innerHTML = `<div class="cal-grid">${sorted.map(([ym, ts], idx) => {
- const [y,m] = ym.split('-');
- const mi = parseInt(m)-1;
- const monthLabel = `${monthNames[mi]} ${y}`;
- const isPast = ym < today.substring(0,7);
- const isCur = ym === today.substring(0,7);
- const openC = ts.filter(t=>t.status==='open').length;
- const closedC = ts.filter(t=>t.status!=='open').length;
- // Type breakdown
- const bpCount = ts.filter(t=>t.type==='bull_put').length;
- const bcCount = ts.filter(t=>t.type==='bear_call').length;
- const total = ts.length;
- const bpPct = total>0 ? (bpCount/total*100).toFixed(0) : 0;
- const bcPct = total>0 ? (bcCount/total*100).toFixed(0) : 0;
- // Outcome breakdown (closed only)
- const closedTrades = ts.filter(t=>t.status!=='open');
- const outcomes = {};
- closedTrades.forEach(t=>{ outcomes[t.status]=(outcomes[t.status]||0)+1; });
- // P&L for month
- const mPnl = closedTrades.filter(t=>t.pnl!=null).reduce((s,t)=>s+t.pnl,0);
- const pnlPct = Math.min(Math.abs(mPnl)/maxAbsPnl*100, 100);
- const pnlColor = mPnl>=0 ? 'var(--green)' : 'var(--red)';
- // Contracts total
- const totalContracts = ts.reduce((s,t)=>s+t.contracts,0);
- // Trade rows
- const rows = ts.sort((a,b)=>a.expiry.localeCompare(b.expiry)).map(t => {
- const dte = Math.ceil((new Date(t.expiry)-now)/86400000);
- const isSoon = dte>=0 && dte<=7 && t.status==='open';
- const isExp = t.expiry < today;
- const rc = isExp&&t.status!=='open' ? 'cal-past' : isSoon ? 'cal-soon' : '';
- const dot = {open:'<span style="color:var(--green);font-weight:700;">β</span>',closed:'<span style="color:#666;">β</span>',stop:'<span style="color:var(--red);">β</span>',early:'<span style="color:var(--yellow);">β©</span>'}[t.status]||'';
- const roi = t.margin>0&&t.maxProfit!=null ? `ROI ${euFmt(t.maxProfit/t.margin*100,1)}%` : '';
- const ed = t.expiry.split('-')[2];
- const ds = t.status==='open' ? (dte<0?'scaduta':`${dte}gg`) : (t.pnl!=null?(t.pnl>=0?'+':'')+euCurr(t.pnl):'');
- const dsColor = t.status==='open' ? (dte<0?'var(--muted)':dte<=7?'var(--yellow)':'var(--accent)') : (t.pnl>=0?'var(--green)':'var(--red)');
- return `<div class="cal-trade-row ${rc}">
- <div class="cal-trade-left">
- <div class="cal-trade-ticker">${dot} ${t.ticker} <span style="color:var(--muted);font-size:10px;font-weight:400;">${assetShort[t.assetType]||''} Β· ${typeShort[t.type]||''}</span></div>
- <div class="cal-trade-info">Scad. ${ed}/${m} Β· ${t.contracts}ct Β· ${euFmt(t.premium,2)} prem.</div>
- </div>
- <div class="cal-trade-right">
- <span style="color:var(--green);font-size:11px;font-weight:700;">${roi}</span>
- <span style="color:${dsColor};font-size:11px;font-weight:600;">${ds}</span>
- </div>
- </div>`;
- }).join('');
- const hBg = isPast ? 'var(--surface2)' : isCur ? 'rgba(108,99,255,0.15)' : 'rgba(76,201,240,0.08)';
- const outcomePills = Object.entries(outcomes).map(([k,v])=>`<span class="cal-outcome-pill" style="background:${outcomeColors[k]||'rgba(142,142,142,.1)'};color:${outcomeFg[k]||'#888'};">${outcomeLabels[k]||k} ${v}</span>`).join('');
- return `<div class="cal-month">
- <div class="cal-month-header" style="background:${hBg};">
- <span>${monthLabel} ${isCur?'<span style="font-size:10px;color:var(--accent);">β ora</span>':isPast?'<span style="font-size:10px;color:var(--muted);">(passato)</span>':''}</span>
- <span class="cal-count">${total} trade</span>
- </div>
- <div class="cal-month-stats">
- <div class="cal-ms"><div class="cal-ms-val">${openC}</div><div class="cal-ms-lbl">Aperti</div></div>
- <div class="cal-ms"><div class="cal-ms-val">${closedC}</div><div class="cal-ms-lbl">Chiusi</div></div>
- <div class="cal-ms"><div class="cal-ms-val">${totalContracts}</div><div class="cal-ms-lbl">Contratti</div></div>
- </div>
- <div class="cal-type-bar">
- <div class="cal-type-seg" style="width:${bpPct}%;background:var(--green);" title="Bull Put ${bpPct}%"></div>
- <div class="cal-type-seg" style="width:${bcPct}%;background:var(--red);" title="Bear Call ${bcPct}%"></div>
- </div>
- ${outcomePills?`<div class="cal-outcome-row">${outcomePills}</div>`:''}
- <div class="cal-pnl-bar">
- <div class="cal-pnl-track"><div class="cal-pnl-fill" style="width:${pnlPct}%;background:${pnlColor};"></div></div>
- <div class="cal-pnl-val" style="color:${pnlColor};">${closedC>0?(mPnl>=0?'+':'')+euCurr(mPnl):'β'}</div>
- </div>
- ${rows}
- </div>`;
- }).join('')}</div>`;
- }
- // βββ STATISTICHE AVANZATE βββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function renderStatsTable(closed) {
- const el = document.getElementById('statsTable');
- if (!closed.length) { el.innerHTML='<div class="no-data-chart" style="height:80px;"><span>π</span>Nessun dato ancora</div>'; return; }
- const wins = closed.filter(t=>t.pnl>0);
- const losses = closed.filter(t=>t.pnl<=0);
- const pnlArr = closed.map(t=>t.pnl);
- const maxWin = Math.max(...pnlArr);
- const maxLoss = Math.min(...pnlArr);
- const totalPnl = closed.reduce((s,t)=>s+t.pnl,0);
- const grossWin = wins.reduce((s,t)=>s+t.pnl,0);
- const grossLoss= Math.abs(losses.reduce((s,t)=>s+t.pnl,0));
- const avgWin = wins.length>0 ? grossWin/wins.length : 0;
- const avgLoss = losses.length>0 ? grossLoss/losses.length : 0;
- const daysArr = closed.filter(t=>t.closeDate).map(t=>Math.round((new Date(t.closeDate)-new Date(t.openDate))/86400000));
- const avgDays = daysArr.length>0 ? Math.round(daysArr.reduce((a,b)=>a+b,0)/daysArr.length) : 0;
- const maxDays = daysArr.length>0 ? Math.max(...daysArr) : 0;
- const roiArr = closed.filter(t=>t.margin>0&&t.maxProfit!=null).map(t=>t.maxProfit/t.margin*100);
- const avgRoi = roiArr.length>0 ? roiArr.reduce((a,b)=>a+b,0)/roiArr.length : 0;
- const totalPrem= closed.reduce((s,t)=>s+t.premium*(t.multiplier||100)*t.contracts,0);
- const col1=[
- ['Trade chiusi totali', closed.length, ''],
- ['Trade in profitto', `${wins.length} (${closed.length>0?(wins.length/closed.length*100).toFixed(1):0}%)`, 'green'],
- ['Trade in perdita', `${losses.length} (${closed.length>0?(losses.length/closed.length*100).toFixed(1):0}%)`, 'red'],
- ['Miglior trade', euCurr(maxWin), 'green'],
- ['Peggior trade', euCurr(maxLoss), 'red'],
- ['P&L cumulativo', (totalPnl>=0?'+':'')+euCurr(totalPnl), totalPnl>=0?'green':'red'],
- ];
- const col2=[
- ['Premio lordo totale incassato', euCurr(totalPrem), 'yellow'],
- ['Avg Win per trade', euCurr(avgWin), 'green'],
- ['Avg Loss per trade', euCurr(avgLoss), 'red'],
- ['ROI medio atteso', euFmt(avgRoi,2)+'%', 'green'],
- ['Giorni medi in posizione', `${avgDays} gg`, ''],
- ['Holding max', `${maxDays} gg`, ''],
- ];
- const renderCol = rows => rows.map(([l,v,c])=>`<div class="stat-row"><span class="stat-label">${l}</span><span class="stat-val ${c}">${v}</span></div>`).join('');
- el.innerHTML = `<div>${renderCol(col1)}</div><div>${renderCol(col2)}</div>`;
- }
- // βββ EXPORT CSV βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function exportCSV() {
- const headers = ['ID','Ticker','Tipo','Apertura','Scadenza','Short Strike','Long Strike','Spread','Contratti','Premio','Max Profit','Max Loss','Margine','Stop Loss','Prezzo Chiusura','Data Chiusura','P&L','ROM%','Stato'];
- const rows = trades.map(t => [
- t.id, t.ticker, t.type === 'bull_put' ? 'Bull Put' : 'Bear Call',
- t.openDate, t.expiry, t.sellStrike, t.buyStrike, t.spread, t.contracts,
- t.premium, t.maxProfit.toFixed(2), t.maxLoss.toFixed(2),
- t.margin.toFixed(2), t.stopLoss.toFixed(2),
- t.closePrice ?? '', t.closeDate ?? '',
- t.pnl !== null ? t.pnl.toFixed(2) : '',
- (t.pnl !== null && t.margin > 0) ? (t.pnl/t.margin*100).toFixed(2) : '',
- t.status
- ]);
- const csv = [headers, ...rows].map(r => r.join(',')).join('\n');
- const a = document.createElement('a');
- a.href = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv);
- a.download = `vertical_spreads_${today()}.csv`;
- a.click();
- }
- </script>
- </body>
- </html>
RAW Paste Data
Copied
