{ "cells": [ { "cell_type": "markdown", "id": "bb54dbbf", "metadata": {}, "source": [ "# Calcolatore 2 — I costi, sui tuoi numeri\n", "\n", "*Quaderno del capitolo «I costi che ti mangiano vivo» di **La matematica di chi perde**.*\n", "\n", "Tre risposte, con i tuoi parametri: quanto ti costano i costi in un anno, quale\n", "rendimento lordo ti serve **solo per pareggiarli**, e quanto ti sarebbe rimasto\n", "operando a quella frequenza su una serie reale.\n", "\n", "Il numero che quasi nessuno conosce è il secondo.\n", "\n", "---\n", "\n", "> **EN** — *Calculator 2 — Costs, on your own numbers.* Notebook for the\n", "> chapter \"The costs that eat you alive\". Three answers, with your own\n", "> parameters: what costs take out of a year, what gross return you need\n", "> **just to break even on them**, and what would have been left trading at\n", "> that frequency on a real series. The number almost nobody knows is the\n", "> second one." ] }, { "cell_type": "markdown", "id": "c4070946", "metadata": {}, "source": [ "Le righe marcate **PROVA** sono quelle da cambiare: cambiale e riesegui per\n", "vedere l'effetto. Il resto — comprese le righe marcate **NON TOCCARE** —\n", "serve a mantenere il risultato confrontabile con quello stampato nel libro.\n", "\n", "The lines marked **TRY** are the ones to change: edit them and rerun to see\n", "the effect. Everything else — including lines marked **DO NOT CHANGE** —\n", "exists to keep the result comparable with the one printed in the book." ] }, { "cell_type": "code", "execution_count": null, "id": "66fc8400", "metadata": {}, "outputs": [], "source": [ "# Setup — esegui questa cella per prima.\n", "%pip install -q \"polars>=1.0\"\n", "try:\n", " import avvio\n", "except ModuleNotFoundError:\n", " import urllib.request\n", "\n", " urllib.request.urlretrieve(\n", " \"https://raw.githubusercontent.com/cryptoverso-lab/quaderni/main/matematica/avvio.py\",\n", " \"avvio.py\",\n", " )\n", " import avvio\n", "\n", "avvio.prepara([\"btcusdt\", \"ethusdt\", \"solusdt\"])" ] }, { "cell_type": "code", "execution_count": null, "id": "0309f7cf", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "from cvbook.dati import carica\n", "from cvbook.lingua import t\n", "from cvbook.metriche import equity, rendimenti" ] }, { "cell_type": "markdown", "id": "eaa582c9", "metadata": {}, "source": [ "## 1. I tuoi tre numeri\n", "\n", "Se non conosci il tuo costo per giro, il capitolo spiega come misurarlo: prendi\n", "dieci operazioni chiuse, confronta il prezzo che avevi visto con quello\n", "ottenuto, somma le commissioni. Fai il conto prima di stimarlo, e confrontalo\n", "con la forbice del capitolo: fra lo 0,06% e lo 0,25%.\n", "\n", "---\n", "\n", "> **EN** — *1. Your three numbers.* If you don't know your own round-trip\n", "> cost, the chapter explains how to measure it: take ten closed trades,\n", "> compare the price you saw with the price you got, add the commissions.\n", "> Do the sum before guessing, and compare it with the chapter's range:\n", "> between 0.06% and 0.25%." ] }, { "cell_type": "code", "execution_count": null, "id": "8fd9af19", "metadata": {}, "outputs": [], "source": [ "CAPITALE = 10_000.0 # ← il capitale impegnato, in euro\n", " # PROVA / TRY: il tuo capitale reale — cambia solo la\n", " # scala, non le percentuali qui sotto\n", "COSTO_GIRO = 0.0012 # ← costo tutto compreso di un giro completo (0,12%)\n", " # PROVA / TRY: 0,0006 (scontato) · 0,0012 · 0,0025 (al dettaglio)\n", "OPERAZIONI_ANNO = 52 # ← quanti giri completi fai in un anno\n", " # PROVA / TRY: la tua frequenza reale, contata sull'estratto conto\n", "\n", "speso = CAPITALE * COSTO_GIRO * OPERAZIONI_ANNO\n", "pareggio = (1 + COSTO_GIRO) ** OPERAZIONI_ANNO - 1\n", "\n", "print(t(\"in un anno paghi: \", \"in a year you pay: \") + f\"{speso:10,.2f}\"\n", " + t(\" euro\", \" euros\"))\n", "print(t(\"cioe' il: \", \"that is: \") + f\"{speso / CAPITALE:10.2%}\"\n", " + t(\" del capitale\", \" of capital\"))\n", "print(t(\"rendimento lordo per \", \"gross return to \"))\n", "print(t(\"NON perdere nulla: \", \"lose NOTHING: \") + f\"{pareggio:10.2%}\")" ] }, { "cell_type": "markdown", "id": "39e03371", "metadata": {}, "source": [ "## 2. La soglia, al variare della frequenza\n", "\n", "La riga tratteggiata è, come nel libro, il rendimento medio storico di lungo\n", "periodo di un indice azionario ampio. Tutto ciò che sta sopra è territorio in\n", "cui il costo si mangia più di quanto un mercato intero abbia mai reso.\n", "\n", "---\n", "\n", "> **EN** — *2. The threshold, as frequency varies.* The dashed line is, as\n", "> in the book, the long-run historical average return of a broad equity\n", "> index. Everything above it is territory where cost eats more than an\n", "> entire market has ever returned." ] }, { "cell_type": "code", "execution_count": null, "id": "2d55a45d", "metadata": {}, "outputs": [], "source": [ "FREQUENZE = np.array([12, 26, 52, 125, 250, 500])\n", "COSTI = [0.0006, 0.0012, 0.0025]\n", "\n", "with avvio.figura(\"schermo\"):\n", " fig, ax = plt.subplots(figsize=(9, 5))\n", " for c in COSTI:\n", " soglia = ((1 + c) ** FREQUENZE - 1) * 100\n", " ax.plot(FREQUENZE, soglia, marker=\"o\", label=t(f\"costo {c:.2%} a giro\", f\"cost {c:.2%} per round trip\"))\n", "\n", " # Riferimento in grigio, con l'etichetta in legenda invece che sul grafico:\n", " # una scritta appoggiata sulle curve e' il modo piu' rapido di rendere\n", " # illeggibile una figura che dice una cosa semplice.\n", " # Grey reference, labelled in the legend rather than on the chart: text\n", " # sitting on top of the curves is the fastest way to make an otherwise\n", " # simple figure unreadable.\n", " ax.axhline(\n", " 10,\n", " linestyle=\"--\",\n", " linewidth=1.2,\n", " color=\"#8C8C8C\",\n", " zorder=0,\n", " label=t(\"~10% annuo: media storica di un indice azionario\",\n", " \"~10% a year: historical average of an equity index\"),\n", " )\n", "\n", " ax.set_xscale(\"log\")\n", " ax.set_xticks(FREQUENZE)\n", " ax.set_xticklabels([str(f) for f in FREQUENZE])\n", " ax.set_xlabel(t(\"Operazioni complete all'anno\", \"Round trips per year\"))\n", " ax.set_ylabel(t(\"Rendimento lordo necessario per pareggiare (%)\", \"Gross return needed to break even (%)\"))\n", " ax.set_ylim(0, None)\n", " ax.legend(loc=\"upper left\")\n", " plt.show()\n", "\n", "for c in COSTI:\n", " print(t(f\"costo {c:.2%}: \", f\"cost {c:.2%}: \") + \" \".join(\n", " f\"{f}op→{((1 + c) ** f - 1):.1%}\" for f in FREQUENZE))" ] }, { "cell_type": "markdown", "id": "6b1b9635", "metadata": {}, "source": [ "## 3. Cosa sarebbe rimasto, su dati reali\n", "\n", "Stessa esposizione, stesso asset, stesso periodo. L'unica cosa che cambia è\n", "**quante volte** si chiude e si riapre la stessa posizione. Nessuna previsione\n", "diversa, nessuna decisione diversa.\n", "\n", "---\n", "\n", "> **EN** — *3. What would have been left, on real data.* Same exposure,\n", "> same asset, same period. The only thing that changes is **how many times**\n", "> the same position is closed and reopened. No different forecast, no\n", "> different decision." ] }, { "cell_type": "code", "execution_count": null, "id": "4923aaea", "metadata": {}, "outputs": [], "source": [ "SERIE = \"btcusdt\" # ← PROVA / TRY: \"ethusdt\" · \"solusdt\" (le tre preparate nel setup)\n", " # per un'altra delle 11 serie in codice/dati/registro.json\n", " # aggiungila anche a avvio.prepara([...]) qui sopra\n", "\n", "r = rendimenti(carica(SERIE).sort(\"data\")[\"chiusura\"].to_numpy())\n", "n = len(r)\n", "\n", "\n", "def con_frequenza(rend: np.ndarray, ogni_n_giorni: int | None, costo: float) -> float:\n", " \"\"\"Capitale finale restando sempre investiti ma rientrando ogni N giorni.\"\"\"\n", " operazioni = np.zeros(len(rend))\n", " # L'ingresso del primo giorno si paga sempre, anche a chi non rientrera'\n", " # mai piu': e' un'operazione. Senza questa riga il compra-e-tieni era\n", " # l'unica riga della tabella a viaggiare gratis, e il confronto con le\n", " # altre frequenze partiva gia' truccato di dodici centesimi per mille euro.\n", " # The first day's entry is always paid, even by whoever never re-enters.\n", " operazioni[0] = 1.0\n", " if ogni_n_giorni is not None:\n", " operazioni[::ogni_n_giorni] = 1.0\n", " return float(equity(rend - operazioni * costo)[-1])\n", "\n", "\n", "print(f\"{SERIE} · {n}\" + t(\" giorni\\n\", \" days\\n\"))\n", "print(f\"{t('frequenza', 'frequency'):>22s} \" + \" \".join(f\"{c:>10.2%}\" for c in COSTI))\n", "for ogni, etichetta in [\n", " (None, t(\"mai (compra e tieni)\", \"never (buy and hold)\")),\n", " (365, t(\"una volta l'anno\", \"once a year\")),\n", " (30, t(\"una volta al mese\", \"once a month\")),\n", " (7, t(\"una volta a settimana\", \"once a week\")),\n", " (1, t(\"ogni giorno\", \"every day\")),\n", "]:\n", " valori = \" \".join(f\"{con_frequenza(r, ogni, c):10.3f}x\" for c in COSTI)\n", " print(f\"{etichetta:>22s} {valori}\")" ] }, { "cell_type": "markdown", "id": "df13cff6", "metadata": {}, "source": [ "### Esercizi\n", "\n", "1. **Metti la tua frequenza reale** in `OPERAZIONI_ANNO`: contale sull'estratto\n", " conto dell'ultimo anno, non a memoria. Poi confronta la soglia di pareggio\n", " con quello che pensavi di poter ottenere.\n", "2. Raddoppia `COSTO_GIRO` e riesegui tutto. Se una strategia sopravvive solo al\n", " costo ottimistico, quel vantaggio appartiene a chi ha costi bassi, non a te.\n", "3. Nella terza cella cambia serie. La colonna del compra-e-tieni cambia molto;\n", " il **rapporto** fra le righe quasi per niente: il costo della frequenza non\n", " dipende da quale asset hai scelto.\n", "\n", "---\n", "\n", "> **EN** — *Exercises.*\n", "> 1. **Enter your real frequency** in `OPERAZIONI_ANNO`: count it from last\n", "> year's statement, not from memory. Then compare the break-even\n", "> threshold with what you thought you could get.\n", "> 2. Double `COSTO_GIRO` and rerun everything. If a strategy only survives\n", "> at the optimistic cost, that edge belongs to whoever has low costs, not\n", "> to you.\n", "> 3. In the third cell, change the series. The buy-and-hold column changes a\n", "> lot; the **ratio** between the rows barely at all: the cost of\n", "> frequency doesn't depend on which asset you picked." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 }