research(wave-0822): SL-ANCHOR — il nodo sciolto dal LOG (la SPEC lo diceva non misurabile); rotolante ma il timore e' refutato, e il docstring della cadenza e' una trappola
This commit is contained in:
@@ -705,6 +705,7 @@ stessi hanno nominato senza poterli eseguire**. Stesso contratto di consegna, st
|
|||||||
| 37 | GATE-C | l'ultima gamba del gate funded + il biglietto come opzione | **PASS 2/2** — gate senza date; **P(≥50/g) 4,4% → 2,6%** |
|
| 37 | GATE-C | l'ultima gamba del gate funded + il biglietto come opzione | **PASS 2/2** — gate senza date; **P(≥50/g) 4,4% → 2,6%** |
|
||||||
| 38 | SCALE-SPEC | specificare il knob di leva che non esiste | **LEAD** — specificata, **1 nodo irrisolto** sul disaster-SL |
|
| 38 | SCALE-SPEC | specificare il knob di leva che non esiste | **LEAD** — specificata, **1 nodo irrisolto** sul disaster-SL |
|
||||||
| 39 | FUNDING-AVOID | evitare il funding esprimendo il libro sui datati | **SCARTATO** sui datati · **LEAD** sullo **spot** |
|
| 39 | FUNDING-AVOID | evitare il funding esprimendo il libro sui datati | **SCARTATO** sui datati · **LEAD** sullo **spot** |
|
||||||
|
| 40 | SL-ANCHOR | il nodo che bloccava il gradino di leva | **SCIOLTO** — rotolante, ma il timore e' refutato |
|
||||||
| 22 | MAKER | l'esecuzione passiva e' una fonte di ritorno, al netto del costo di non essere eseguiti? | **SCARTATO** — il segno dipende da `<` contro `<=` |
|
| 22 | MAKER | l'esecuzione passiva e' una fonte di ritorno, al netto del costo di non essere eseguiti? | **SCARTATO** — il segno dipende da `<` contro `<=` |
|
||||||
| 23 | BOCPD | un rilevatore di cambio di regime vero batte il miglior lookback COSTANTE? | **SCARTATO** — filone chiuso definitivamente |
|
| 23 | BOCPD | un rilevatore di cambio di regime vero batte il miglior lookback COSTANTE? | **SCARTATO** — filone chiuso definitivamente |
|
||||||
| 24 | CRITICO | cosa NON e' stato misurato, e quale singola misura mancante vale di piu' | **3 correzioni all'ondata**, 1 regola mia ritirata |
|
| 24 | CRITICO | cosa NON e' stato misurato, e quale singola misura mancante vale di piu' | **3 correzioni all'ondata**, 1 regola mia ritirata |
|
||||||
@@ -1982,3 +1983,70 @@ risparmio** — se i derivati sono `c-quater` (26%) e lo spot `c-sexies` (33%),
|
|||||||
(i 0,47 punti risparmiati si pagano con −0,034 di ShHOLD, negativo a 0/23 offset, e il gate iso-vol
|
(i 0,47 punti risparmiati si pagano con −0,034 di ShHOLD, negativo a 0/23 offset, e il gate iso-vol
|
||||||
fallisce). **SCARTATO** sui datati, **LEAD** sullo spot per la sola gamba TP01 — subordinato alla
|
fallisce). **SCARTATO** sui datati, **LEAD** sullo spot per la sola gamba TP01 — subordinato alla
|
||||||
domanda fiscale, che va posta al commercialista **prima** di qualunque prova.
|
domanda fiscale, che va posta al commercialista **prima** di qualunque prova.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 40 — SL-ANCHOR (il nodo del disaster-SL, sciolto)
|
||||||
|
|
||||||
|
`scripts/research/r0823_sl_anchor.py` (**2,0 s**, sola lettura). **42 celle**, nessuna riduzione per
|
||||||
|
budget. ✅ **Sola lettura provata**: `git status` mostra solo questo file; il ramo venue fa **2 sole**
|
||||||
|
`get_open_orders` e si astiene nei minuti :05-:10 e :24-:30.
|
||||||
|
|
||||||
|
🚨 **La dichiarazione di non-misurabilita' della SPEC era FALSA: il log bastava.** Ieri notte
|
||||||
|
`SPEC-scale-key.md` scriveva *"letto nel codice, NON misurato — il log non lo permette (servirebbe
|
||||||
|
`pos_prima`): non ricostruibile a posteriori"*. **Terza conferma della regola del 26/07: un blocco
|
||||||
|
dichiarato e' un'IPOTESI, non un fatto.**
|
||||||
|
|
||||||
|
📌 **L'agente ha scritto l'attesa PRIMA di guardare, derivandola dal codice — e ha predetto le soglie
|
||||||
|
esatte:** rotolante e **simmetrico**, con innesco a **+5,263%** (salita) / **−4,762%** (discesa),
|
||||||
|
perche' il fattore 0,70 si semplifica e la condizione `|stp−want|/want ≥ 0,05` diventa una condizione
|
||||||
|
**sul mark**, non sullo stop.
|
||||||
|
|
||||||
|
**Misura sul log di produzione** (1443 giri, 60,1 giorni, 2840 righe-asset): i 15 `placed` si
|
||||||
|
scompongono in **5 prime aperture + 7 ribilanci + 3 A POSIZIONE FERMA**. Le tre — 21/08 00:07 BTC,
|
||||||
|
06:07 ETH, 22:07 BTC — hanno azione `HOLD (a target)`, **0 ordini**, e **trigger spostato**: salti
|
||||||
|
**+5,74% / +5,81% / +5,38%**, tutti **appena sopra il +5,263% predetto**.
|
||||||
|
**Conferma dal venue:** la posizione BTC e' aperta dal **19/08 15:07**, l'ordine di stop sul book e'
|
||||||
|
stato creato il **22/08 01:07** → **l'ordine e' 2,5 giorni piu' giovane della posizione che protegge.**
|
||||||
|
|
||||||
|
🚨 **MA LA CONSEGUENZA CHE BLOCCAVA IL GRADINO E' REFUTATA, e va nel verso OPPOSTO al timore:** uno
|
||||||
|
stop che segue il prezzo **si allontana dal mercato**, quindi scatta **meno**. Episodi su storia reale
|
||||||
|
(caso peggiore: sempre long, rientro immediato): a **1h** — la cadenza che gira — BTC `roll` **0**
|
||||||
|
contro `floor` 1; ETH `roll` **1** contro 1; **mai piu' di 1 in 30 giorni**. Caduta massima storica
|
||||||
|
dentro un giro: BTC 1h **−23,17%**, ETH **−30,35%**, contro i ~26,5-30% che servirebbero.
|
||||||
|
📌 **E nell'outage — l'unico scenario per cui il bracket esiste — il ri-ancoraggio e' SPENTO per
|
||||||
|
costruzione**: nessun giro di cron, nessun ri-piazzamento ⇒ **un episodio solo**.
|
||||||
|
**⇒ Il 37,5% resta il costo di un episodio singolo, il margine 1,33× di WORST-DAY e' quello vero, e la
|
||||||
|
sua gamba REGGE.**
|
||||||
|
|
||||||
|
**Due cose da registrare, entrambe INDIPENDENTI da k:**
|
||||||
|
1. ⚠️ **Il nome inganna: "disaster-SL −30%" suggerisce un massimo di perdita per trade che NON
|
||||||
|
ESISTE.** Su cicli a ingresso proprio, il rotolante lascia passare perdite oltre −30% **senza
|
||||||
|
scattare** nel **0,03% (24h) / 0,96-2,84% (7g) / 7,22-13,33% (30g)** dei cicli, e il peggior DD
|
||||||
|
dall'ingresso e' **−60,6% BTC / −61,5% ETH** contro −38,3%/−45,4% del pavimento.
|
||||||
|
2. 🚨 **L'accumulo e' funzione della CADENZA DEL CRON, non della leva — e c'e' una trappola viva in
|
||||||
|
produzione:** il docstring di `book_execute.py` prescrive *"ogni ~230 minuti"* mentre **il cron gira
|
||||||
|
ogni ora**. Chi "correggesse" la cadenza verso il docstring sposterebbe il libro sulla riga **4h**,
|
||||||
|
**dove BTC raddoppia gli scatti** (`roll` 2 contro 1). A 24h ETH arriva a 5, con 2 in 30 giorni.
|
||||||
|
|
||||||
|
⚠️ **Trappola n.10 del brief catturata in sessione:** la colonna "perdita dall'ingresso" della prima
|
||||||
|
stesura era un **artefatto** (posizione aperta dal 2018 ⇒ era il DD di un buy&hold pluriennale) →
|
||||||
|
dichiarata e **rifatta con cicli a ingresso proprio**. E una prima versione della simulazione e' stata
|
||||||
|
**scartata** perche' il rientro avveniva fuori dal giro di cron (irrealistico).
|
||||||
|
✅ **Controllo positivo superato** (obbligatorio): lo stesso esercizio applicato a un'implementazione
|
||||||
|
**a pavimento** risponde `ok` → l'esercizio **distingue** le due implementazioni, non passa e basta.
|
||||||
|
✅ **Guardia anti-rete superata**: la sottoclasse che esercita il vero `ensure_disaster_sl` ha `_post`
|
||||||
|
che **solleva**, e l'esercizio e' riuscito ⇒ **nessuna chiamata di rete e' partita da quel percorso.**
|
||||||
|
|
||||||
|
⚠️ **Limite dichiarato:** **0 ri-ancoraggi verso il BASSO osservati su 3** — i 2 mesi di vita del libro
|
||||||
|
sono **un solo regime** (BTC da ~$66,5k a ~$78,6k) e non c'e' mai stato un calo del mark >4,76% con
|
||||||
|
posizione aperta. **La simmetria e' un fatto di CODICE** (un solo `abs()`, esercitato in entrambi i
|
||||||
|
versi), **non un fatto OSSERVATO**: la prima osservazione utile arriva al primo ribasso >5% con
|
||||||
|
posizione aperta.
|
||||||
|
📌 **Nota di metodo:** la storia degli ordini **non e' raggiungibile dal percorso del conto** — il
|
||||||
|
gateway `cerbero-mcp` espone 32 tool Deribit e **nessuno** e' `get_order_history_by_currency`. Le
|
||||||
|
chiavi dirette appartengono a un altro progetto e **non sono state usate**: sarebbe stato inventare un
|
||||||
|
percorso, *e la domanda non ne aveva bisogno.*
|
||||||
|
|
||||||
|
**VERDETTO: `IL DISASTER-SL E' ROTOLANTE (costo vero 37,5% contro 37,5% — un episodio solo alla cadenza
|
||||||
|
di produzione; sale a ~75% solo con cron a 24h, che non e' la configurazione che gira)`.**
|
||||||
|
|||||||
@@ -0,0 +1,534 @@
|
|||||||
|
"""SL-ANCHOR — il disaster-SL del libro live e' un PAVIMENTO o un −30% ROTOLANTE?
|
||||||
|
|
||||||
|
Il nodo lasciato aperto da `docs/research/SPEC-scale-key.md` §4.5: la guardia che morde per prima
|
||||||
|
sul gradino di leva e' il costo del disaster-SL (B = 37,5% dell'equity a k=1,25). La SPEC osserva
|
||||||
|
*dalla lettura del codice* che `src/live/execution.py::ensure_disaster_sl` ri-ancora lo stop al mark
|
||||||
|
a ogni giro, e conclude: «B e' il costo di UN episodio, un ribasso lento puo' produrne piu' d'uno»,
|
||||||
|
dichiarando la cosa **non ricostruibile a posteriori** perche' «servirebbe `pos_prima` nel log».
|
||||||
|
|
||||||
|
Quella dichiarazione di non-misurabilita' e' un'IPOTESI, e qui viene sfidata (regola del 26/07).
|
||||||
|
Il log di produzione stampa, a ogni giro e per ogni asset, **l'azione** (`HOLD` = nessun ordine =
|
||||||
|
posizione invariata) e **lo stato + il prezzo di trigger del bracket**; e nello stato `ok` quel
|
||||||
|
prezzo e' letto dal venue (`get_open_orders -> trigger_price`), non calcolato. Un `placed` su una
|
||||||
|
riga `HOLD (a target)` e' quindi esattamente l'evento cercato: bracket ricostruito a posizione
|
||||||
|
ferma. La domanda ERA gia' rispondibile col log che c'e'.
|
||||||
|
|
||||||
|
SOLA LETTURA. Questo script:
|
||||||
|
- non importa nulla che invii ordini se non per ISPEZIONARLO (la sottoclasse di §1 ha `_post` che
|
||||||
|
SOLLEVA: se il codice di produzione toccasse la rete, il test fallirebbe rumorosamente);
|
||||||
|
- tocca il venue solo con `--venue`, solo `get_open_orders` (lettura), 2 richieste, e si rifiuta
|
||||||
|
di partire nei minuti :05-:10 (cron_book) e :24-:30 (cron_chain).
|
||||||
|
|
||||||
|
uv run python scripts/research/r0823_sl_anchor.py # attesa + log + simulazione
|
||||||
|
uv run python scripts/research/r0823_sl_anchor.py --venue # + riscontro sul conto (2 letture)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from collections import Counter
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
sys.path.insert(0, str(PROJECT_ROOT))
|
||||||
|
|
||||||
|
from src.data.downloader import load_data # noqa: E402
|
||||||
|
from src.live.deribit import DISASTER_LABEL, disaster_stop_price # noqa: E402
|
||||||
|
from src.live.execution import DeribitTrader # noqa: E402
|
||||||
|
|
||||||
|
LOG = PROJECT_ROOT / "logs" / "cron_book.log"
|
||||||
|
SL_PCT = 0.30 # config/live.json -> disaster_sl_pct
|
||||||
|
TOL = 0.05 # tolleranza relativa cablata in ensure_disaster_sl
|
||||||
|
AMT_TOL = 0.10 # tolleranza sull'amount, idem
|
||||||
|
H = "=" * 96
|
||||||
|
|
||||||
|
|
||||||
|
def sec(t: str):
|
||||||
|
print("\n" + H + f"\n{t}\n" + H)
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 1. L'ATTESA — derivata dal CODICE, scritta prima di guardare i dati
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
class _TraderStub(DeribitTrader):
|
||||||
|
"""Esercita il VERO `ensure_disaster_sl` senza rete e senza ordini.
|
||||||
|
|
||||||
|
Non e' una re-implementazione: il metodo sotto esame e' quello di produzione, importato. Sono
|
||||||
|
stubbate solo le sue dipendenze di I/O. `_post` SOLLEVA -> se un percorso non stubbato provasse
|
||||||
|
a parlare col venue, l'esercizio si romperebbe invece di riuscire in silenzio.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, pos_usd: float, mark: float):
|
||||||
|
self._pos = pos_usd
|
||||||
|
self._mark = mark
|
||||||
|
self.book: list[dict] = [] # "ordini aperti" finti
|
||||||
|
self.eventi: list[tuple] = [] # (azione, prezzo)
|
||||||
|
|
||||||
|
def _post(self, path, payload): # noqa: D102
|
||||||
|
raise AssertionError(f"RETE VIETATA in questo esercizio (path={path})")
|
||||||
|
|
||||||
|
def position_usd(self, instrument): return self._pos
|
||||||
|
def mark_price(self, instrument): return self._mark
|
||||||
|
def open_orders(self, instrument): return list(self.book)
|
||||||
|
|
||||||
|
def cancel_order(self, order_id):
|
||||||
|
self.book = [o for o in self.book if o["order_id"] != order_id]
|
||||||
|
self.eventi.append(("cancel", order_id))
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def place_disaster_sl(self, instrument, side_held, amount, stop_price, label=DISASTER_LABEL):
|
||||||
|
from src.live.execution import Fill
|
||||||
|
oid = f"stub-{len(self.eventi)}"
|
||||||
|
self.book.append({"order_id": oid, "label": DISASTER_LABEL, "amount": amount,
|
||||||
|
"trigger_price": stop_price})
|
||||||
|
self.eventi.append(("place", stop_price))
|
||||||
|
return Fill(instrument, "sell", amount, 0.0, None, 0.0, oid, "untriggered", True)
|
||||||
|
|
||||||
|
|
||||||
|
def attesa():
|
||||||
|
sec("1. ATTESA — cosa dice il CODICE, prima di guardare i dati")
|
||||||
|
print("""`ensure_disaster_sl` (src/live/execution.py:196-222), a ogni giro orario:
|
||||||
|
mark = self.mark_price(instrument) <- MARK CORRENTE, non il prezzo d'ingresso
|
||||||
|
want_stop = disaster_stop_price(inst, mark, sl_pct) <- 0,70 * mark_ora (long)
|
||||||
|
tiene il bracket esistente SOLO se |stp - want_stop| / want_stop < 0,05 E |amt - want| < 10%*want
|
||||||
|
altrimenti -> cancel_order(...) + place_disaster_sl(..., want_stop)
|
||||||
|
|
||||||
|
Il trigger NON e' funzione del prezzo d'ingresso della posizione: e' funzione del mark dell'ora.
|
||||||
|
Sostituendo stp = 0,70*M_vecchio e want = 0,70*M_ora, il fattore 0,70 si semplifica:
|
||||||
|
|
||||||
|
|0,70*M_vec - 0,70*M_ora| / (0,70*M_ora) = |M_vec/M_ora - 1| >= 0,05
|
||||||
|
|
||||||
|
che e' una condizione sul MARK, non sullo stop. Soglie (esatte, non arrotondate):
|
||||||
|
salita M_ora >= M_vec/0,95 -> +5,263% -> lo stop sale (trailing "buono")
|
||||||
|
discesa M_ora <= M_vec/1,05 -> -4,762% -> lo stop SCENDE (il -30% insegue il prezzo giu')
|
||||||
|
|
||||||
|
ATTESA DICHIARATA: **ROTOLANTE e SIMMETRICO** — nel codice c'e' un solo `abs()`, non due rami.
|
||||||
|
ATTESA 2: esiste un SECONDO innesco, che la SPEC non cita — il controllo sull'AMOUNT. Ogni
|
||||||
|
ribilancio cambia la size -> `|amt - want_amount| >= 10%` -> bracket ricostruito a 0,70*mark_ora
|
||||||
|
ANCHE se il prezzo non si e' mosso del 5%. Su un libro vol-targeted questo innesco e' il piu'
|
||||||
|
frequente dei due.
|
||||||
|
ATTESA 3 (conseguenza, e va nel verso OPPOSTO al timore della SPEC): uno stop che scende insieme
|
||||||
|
al prezzo si allontana dal mercato in un ribasso -> viene toccato MENO, non piu'. Cio' che si
|
||||||
|
perde non e' "piu' episodi", e' il PAVIMENTO: la perdita dall'INGRESSO non e' piu' limitata al 30%.""")
|
||||||
|
|
||||||
|
# --- esercizio del codice di produzione su un percorso sintetico (nessuna rete, nessun ordine)
|
||||||
|
print("\n Esercizio del VERO `ensure_disaster_sl` (stub di I/O, `_post` solleva):")
|
||||||
|
inst = "BTC_USDC-PERPETUAL"
|
||||||
|
righe = []
|
||||||
|
for nome, mult in (("mark FERMO", 1.000), ("mark +4,0% (sotto soglia)", 1.040),
|
||||||
|
("mark +5,5% (sopra soglia)", 1.055), ("mark -4,0% (sotto soglia)", 0.960),
|
||||||
|
("mark -5,5% (sopra soglia)", 0.945)):
|
||||||
|
t = _TraderStub(pos_usd=200.0, mark=70_000.0)
|
||||||
|
r0 = t.ensure_disaster_sl(inst, SL_PCT) # primo piazzamento
|
||||||
|
t._mark = 70_000.0 * mult # il mark si muove, la posizione NO
|
||||||
|
r1 = t.ensure_disaster_sl(inst, SL_PCT)
|
||||||
|
righe.append((nome, r0["stop"], r1["state"], r1.get("stop")))
|
||||||
|
print(f" {'scenario':<28} {'stop iniziale':>14} {'esito':>10} {'stop dopo':>14} posizione")
|
||||||
|
for n, s0, st, s1 in righe:
|
||||||
|
print(f" {n:<28} {s0:>14,.1f} {st:>10} {s1:>14,.1f} INVARIATA")
|
||||||
|
su = [r for r in righe if "+5,5" in r[0]][0]
|
||||||
|
giu = [r for r in righe if "-5,5" in r[0]][0]
|
||||||
|
assert su[2] == "placed" and giu[2] == "placed", "atteso ri-piazzamento in ENTRAMBI i versi"
|
||||||
|
assert righe[0][2] == "ok" and righe[1][2] == "ok" and righe[3][2] == "ok"
|
||||||
|
print(f"\n -> il codice di produzione ri-piazza in ENTRAMBI i versi: lo stop SCENDE da "
|
||||||
|
f"{giu[1]:,.1f} a {giu[3]:,.1f} con la posizione ferma.")
|
||||||
|
print(" (asserzione superata: nessuna chiamata di rete e' partita, `_post` avrebbe sollevato)")
|
||||||
|
|
||||||
|
# controllo positivo: la stessa asserzione applicata a un pavimento DEVE fallire
|
||||||
|
class _Pavimento(_TraderStub):
|
||||||
|
def ensure_disaster_sl(self, instrument, sl_pct):
|
||||||
|
if not self.book:
|
||||||
|
return _TraderStub.ensure_disaster_sl(self, instrument, sl_pct)
|
||||||
|
return {"state": "ok", "stop": self.book[0]["trigger_price"]}
|
||||||
|
p = _Pavimento(pos_usd=200.0, mark=70_000.0)
|
||||||
|
p.ensure_disaster_sl(inst, SL_PCT)
|
||||||
|
p._mark = 70_000.0 * 0.945
|
||||||
|
assert p.ensure_disaster_sl(inst, SL_PCT)["state"] == "ok", "controllo positivo rotto"
|
||||||
|
print(" CONTROLLO POSITIVO: su un'implementazione a PAVIMENTO lo stesso esercizio da' `ok`")
|
||||||
|
print(" -> l'esercizio ha potenza: distingue le due implementazioni, non passa e basta.")
|
||||||
|
return dict(soglia_su=1 / 0.95 - 1, soglia_giu=1 / 1.05 - 1)
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 2. LA MISURA — dal log di produzione (che porta letture del venue)
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
_RUN = re.compile(r"^===== (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})Z cron_book =====")
|
||||||
|
_ASSET = re.compile(r"^ (BTC|ETH) TP ([+-][\d.]+) . SKH ([+-]\d)\(([^)]*)\) -> net \$([+-][\d,]+)"
|
||||||
|
r" \| pos \$([+-][\d,]+) -> (.*)$")
|
||||||
|
_DSL = re.compile(r"^ disaster-SL: (\w[\w-]*)(?: @ \$([\d,.]+))?")
|
||||||
|
_REC = re.compile(r"^ reconcile: pos \$([-\d,]+)")
|
||||||
|
_FILL = re.compile(r"^ -> (BUY|SELL) ([\d.]+) @ \$([\d,.]+)")
|
||||||
|
|
||||||
|
|
||||||
|
def _f(s: str) -> float:
|
||||||
|
return float(s.replace(",", ""))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_log() -> list[dict]:
|
||||||
|
runs: list[dict] = []
|
||||||
|
cur = asset = None
|
||||||
|
for ln in LOG.read_text().splitlines():
|
||||||
|
m = _RUN.match(ln)
|
||||||
|
if m:
|
||||||
|
cur = {"ts": pd.Timestamp(m.group(1), tz="UTC"), "assets": []}
|
||||||
|
runs.append(cur); asset = None; continue
|
||||||
|
if cur is None:
|
||||||
|
continue
|
||||||
|
m = _ASSET.match(ln)
|
||||||
|
if m:
|
||||||
|
asset = dict(asset=m.group(1), tp=float(m.group(2)), skh=int(m.group(3)),
|
||||||
|
net=_f(m.group(5)), pos=_f(m.group(6)), action=m.group(7),
|
||||||
|
dsl_state=None, dsl_stop=None, n_fill=0, pos_after=None)
|
||||||
|
cur["assets"].append(asset); continue
|
||||||
|
if asset is None:
|
||||||
|
continue
|
||||||
|
if _FILL.match(ln):
|
||||||
|
asset["n_fill"] += 1; continue
|
||||||
|
m = _REC.match(ln)
|
||||||
|
if m:
|
||||||
|
asset["pos_after"] = _f(m.group(1)); continue
|
||||||
|
m = _DSL.match(ln)
|
||||||
|
if m:
|
||||||
|
asset["dsl_state"] = m.group(1)
|
||||||
|
asset["dsl_stop"] = _f(m.group(2)) if m.group(2) else None
|
||||||
|
return runs
|
||||||
|
|
||||||
|
|
||||||
|
def misura_log(soglie: dict) -> dict:
|
||||||
|
sec("2. LA MISURA — log di produzione `logs/cron_book.log` (1 giro/ora dal 2026-06-23)")
|
||||||
|
runs = parse_log()
|
||||||
|
righe = [(r["ts"], a) for r in runs for a in r["assets"]]
|
||||||
|
st = Counter(a["dsl_state"] for _, a in righe)
|
||||||
|
span = (runs[-1]["ts"] - runs[0]["ts"]).total_seconds() / 3600
|
||||||
|
print(f" giri: {len(runs)} ({runs[0]['ts']:%Y-%m-%d %H:%M} -> {runs[-1]['ts']:%Y-%m-%d %H:%M} UTC, "
|
||||||
|
f"{span/24:.1f} giorni)")
|
||||||
|
print(f" righe-asset: {len(righe)} | stati disaster-SL: {dict(st)}")
|
||||||
|
print(f" (i {st[None]} `None` sono giri usciti PRIMA del passo disaster-SL — conto offline /"
|
||||||
|
" feed stantio: non sono uno stato del bracket, sono l'assenza della misura)")
|
||||||
|
|
||||||
|
gap = pd.Series([r["ts"] for r in runs]).diff().dt.total_seconds() / 3600
|
||||||
|
print(f" cadenza del cron: mediana {gap.median():.2f}h · max {gap.max():.2f}h · "
|
||||||
|
f"buchi >2h: {int((gap > 2).sum())}"
|
||||||
|
" <- durante un buco lo stop e' CONGELATO (nessun giro = nessun ri-ancoraggio)")
|
||||||
|
|
||||||
|
# --- L'EVENTO CERCATO: bracket ricostruito a POSIZIONE FERMA -------------------------------
|
||||||
|
print("\n a) `placed` = bracket cancellato e ricostruito. La SPEC li dichiara «non separabili»")
|
||||||
|
print(" dalle prime aperture: FALSO — la riga porta gia' l'azione dello stesso giro.")
|
||||||
|
placed = [(t, a) for t, a in righe if a["dsl_state"] == "placed"]
|
||||||
|
hold = [(t, a) for t, a in placed if a["action"].startswith("HOLD") and a["n_fill"] == 0]
|
||||||
|
apert = [(t, a) for t, a in placed if abs(a["pos"]) < 1.0]
|
||||||
|
ribil = [(t, a) for t, a in placed if abs(a["pos"]) >= 1.0 and a["n_fill"] > 0]
|
||||||
|
print(f" {len(placed)} `placed` totali = {len(apert)} prime aperture (pos=0) + "
|
||||||
|
f"{len(ribil)} ribilanci (innesco AMOUNT) + {len(hold)} A POSIZIONE FERMA (innesco PREZZO)")
|
||||||
|
print(f"\n {'timestamp':<21} {'as':<4} {'azione':<16} {'pos $':>7} {'ordini':>7} {'nuovo stop':>12}")
|
||||||
|
for t, a in hold:
|
||||||
|
print(f" {t:%Y-%m-%d %H:%M:%S} {a['asset']:<4} {a['action'][:16]:<16} "
|
||||||
|
f"{a['pos']:>7,.0f} {a['n_fill']:>7} {a['dsl_stop']:>12,.1f}")
|
||||||
|
print(f"\n -> {len(hold)} osservazioni in cui NESSUN ordine e' partito, la posizione non e'")
|
||||||
|
print(" cambiata, e il trigger sul book e' stato comunque spostato. **UNA sarebbe bastata.**")
|
||||||
|
|
||||||
|
# --- serie del trigger, e taglia dei salti vs la soglia predetta ---------------------------
|
||||||
|
print("\n b) serie del trigger on-book (nello stato `ok` il numero e' LETTO dal venue,")
|
||||||
|
print(" `get_open_orders -> trigger_price`: non e' un valore che il libro ricalcola)")
|
||||||
|
salti = []
|
||||||
|
for A in ("BTC", "ETH"):
|
||||||
|
prev = None
|
||||||
|
print(f"\n --- {A} ---")
|
||||||
|
for t, a in righe:
|
||||||
|
if a["asset"] != A:
|
||||||
|
continue
|
||||||
|
if a["dsl_state"] in (None, "flat"):
|
||||||
|
if prev is not None:
|
||||||
|
print(f" {t:%Y-%m-%d %H:%M} FLAT (posizione chiusa -> bracket cancellato)")
|
||||||
|
prev = None; continue
|
||||||
|
s = a["dsl_stop"]
|
||||||
|
if prev is None:
|
||||||
|
print(f" {t:%Y-%m-%d %H:%M} primo piazzamento stop ${s:>10,.1f}")
|
||||||
|
elif abs(s - prev) > 1e-9:
|
||||||
|
d = s / prev - 1
|
||||||
|
innesco = "PREZZO (pos. ferma)" if a["action"].startswith("HOLD") else "AMOUNT (ribilancio)"
|
||||||
|
salti.append((A, t, d, innesco))
|
||||||
|
print(f" {t:%Y-%m-%d %H:%M} stop ${prev:>10,.1f} -> ${s:>10,.1f} ({d:+6.2%}) "
|
||||||
|
f"innesco: {innesco}")
|
||||||
|
prev = s
|
||||||
|
sp = [d for _, _, d, i in salti if i.startswith("PREZZO")]
|
||||||
|
print(f"\n salti a posizione ferma: {['%+.2f%%' % (x*100) for x in sp]}")
|
||||||
|
print(f" soglia predetta al §1 per il ri-piazzamento in salita: {soglie['soglia_su']:+.3%}")
|
||||||
|
print(f" -> tutti e {len(sp)} stanno appena SOPRA la soglia: la misura non dice solo «si muove»,")
|
||||||
|
print(" dice che si muove **quando e quanto il codice prevede**.")
|
||||||
|
|
||||||
|
print("\n c) ONESTA' SUL CAMPIONE — quello che NON e' stato osservato:")
|
||||||
|
print(f" ri-ancoraggi verso il BASSO a posizione ferma: 0 su {len(sp)}.")
|
||||||
|
print(" Il campione e' di 2 mesi e UN SOLO REGIME: nella finestra BTC va da ~$66,5k a ~$78,6k")
|
||||||
|
print(" e non c'e' mai stato un calo del mark >4,76% mentre una posizione era aperta.")
|
||||||
|
print(" La simmetria del ri-ancoraggio NON e' quindi un fatto osservato: e' un fatto di")
|
||||||
|
print(" CODICE, esercitato al §1 (un solo `abs()`, ri-piazzamento provato in entrambi i versi).")
|
||||||
|
return dict(n_runs=len(runs), n_hold=len(hold), n_placed=len(placed), gap_max=float(gap.max()))
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 3. RISCONTRO SUL VENUE — solo lettura, solo il percorso che il libro usa gia'
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
def riscontro_venue(runs_info: dict):
|
||||||
|
sec("3. RISCONTRO SUL VENUE (percorso del conto, SOLA LETTURA)")
|
||||||
|
print(""" La storia degli ordini (`get_order_history_by_currency` / `get_order_state`) NON e'
|
||||||
|
raggiungibile: il gateway che il libro usa (cerbero-mcp) espone 32 tool Deribit e nessuno di essi
|
||||||
|
e' la storia degli ordini (verificato in `cerbero_mcp/exchanges/deribit/tools.py`: ticker,
|
||||||
|
orderbook, positions, account_summary, open_orders, trade_history, place/cancel...). Le chiavi API
|
||||||
|
diritte di Deribit appartengono a un ALTRO progetto: usarle sarebbe inventare un percorso, e la
|
||||||
|
domanda non ne ha bisogno. Si usa quindi cio' che il percorso sanzionato offre: `get_open_orders`.""")
|
||||||
|
m = datetime.now(timezone.utc).minute
|
||||||
|
if 5 <= m <= 10 or 24 <= m <= 30:
|
||||||
|
print(f"\n ⏸ minuto :{m:02d} — finestra di cron_book (:07) / cron_chain (:25). Non interrogo.")
|
||||||
|
return
|
||||||
|
from src.live.deribit import DeribitRead
|
||||||
|
r = DeribitRead()
|
||||||
|
print(f"\n {'strumento':<22} {'trigger on-book':>16} {'amount':>10} creato (UTC)")
|
||||||
|
for i, inst in enumerate(("BTC_USDC-PERPETUAL", "ETH_USDC-PERPETUAL")):
|
||||||
|
if i:
|
||||||
|
time.sleep(0.6) # <= 2 req/s
|
||||||
|
try:
|
||||||
|
br = [o for o in r.open_orders(inst) if (o.get("label") or "") == DISASTER_LABEL]
|
||||||
|
except Exception as e: # noqa: BLE001
|
||||||
|
print(f" {inst:<22} lettura fallita: {type(e).__name__}: {e}")
|
||||||
|
continue
|
||||||
|
for o in br:
|
||||||
|
trg = float(o.get("trigger_price") or o.get("stop_price") or o.get("price") or 0)
|
||||||
|
ct = o.get("creation_timestamp")
|
||||||
|
when = pd.Timestamp(int(ct), unit="ms", tz="UTC").strftime("%Y-%m-%d %H:%M:%S") if ct else "?"
|
||||||
|
print(f" {inst:<22} {trg:>16,.2f} {float(o.get('amount') or 0):>10.4f} {when}")
|
||||||
|
if not br:
|
||||||
|
print(f" {inst:<22} nessun bracket (posizione flat)")
|
||||||
|
print("""
|
||||||
|
Da leggere insieme al §2: il `creation_timestamp` del bracket vivo e' l'ora dell'ULTIMO
|
||||||
|
`placed` del log, non quella di apertura della posizione. L'oggetto-ordine sul venue e' piu'
|
||||||
|
GIOVANE della posizione che protegge: e' stato distrutto e ricreato mentre la posizione viveva.""")
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 4. LA CONSEGUENZA — quanto costa davvero, sulla storia vera di BTC/ETH
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
def simula(df: pd.DataFrame, modo: str, ore_cron: int, sl: float = SL_PCT, tol: float = TOL) -> dict:
|
||||||
|
"""Replica la regola di produzione su barre orarie reali.
|
||||||
|
|
||||||
|
`modo="roll"` = `ensure_disaster_sl` com'e' (stop = 0,70*mark, ri-ancorato oltre `tol`);
|
||||||
|
`modo="floor"` = la controfattuale che la SPEC assumeva (stop fissato all'ingresso, mai mosso).
|
||||||
|
Il runner e' VIVO e la posizione e' SEMPRE LONG (caso peggiore per l'accumulo di episodi:
|
||||||
|
dopo ogni scatto si rientra al giro successivo). `ore_cron` = cadenza dei giri.
|
||||||
|
"""
|
||||||
|
c = df["close"].to_numpy(float); lo = df["low"].to_numpy(float); op = df["open"].to_numpy(float)
|
||||||
|
_dt = pd.to_datetime(df["datetime"])
|
||||||
|
if getattr(_dt.dt, "tz", None) is not None: # confronti sempre naive-UTC, mai misti
|
||||||
|
_dt = _dt.dt.tz_convert("UTC").dt.tz_localize(None)
|
||||||
|
ts = _dt.to_numpy()
|
||||||
|
n = len(c)
|
||||||
|
stop = anchor = entry = None
|
||||||
|
epis = []
|
||||||
|
peggio_da_ingresso = 0.0
|
||||||
|
for i in range(1, n):
|
||||||
|
if (i % ore_cron) == 0: # il giro del cron: e' l'UNICO momento in cui
|
||||||
|
mark = c[i - 1] # si apre, si rientra e si ri-ancora
|
||||||
|
if stop is None: # (ri)apertura + primo bracket
|
||||||
|
entry = anchor = mark
|
||||||
|
stop = mark * (1 - sl)
|
||||||
|
elif modo == "roll":
|
||||||
|
want = mark * (1 - sl)
|
||||||
|
if abs(stop - want) / want >= tol:
|
||||||
|
stop, anchor = want, mark
|
||||||
|
if stop is None:
|
||||||
|
continue
|
||||||
|
peggio_da_ingresso = min(peggio_da_ingresso, lo[i] / entry - 1)
|
||||||
|
if lo[i] <= stop: # scatto (gap-through al peggiore fra open e stop)
|
||||||
|
fill = min(op[i], stop)
|
||||||
|
epis.append(dict(t=ts[i], entry=entry, anchor=anchor, stop=stop, fill=fill,
|
||||||
|
perdita_da_ancora=fill / anchor - 1, perdita_da_ingresso=fill / entry - 1))
|
||||||
|
stop = entry = anchor = None
|
||||||
|
return dict(episodi=epis, n=len(epis), peggior_perdita_da_ingresso=peggio_da_ingresso)
|
||||||
|
|
||||||
|
|
||||||
|
def conseguenza():
|
||||||
|
sec("4. LA CONSEGUENZA — quanti disaster-SL si accumulano davvero (storia reale BTC/ETH 1h)")
|
||||||
|
print(" Regola di produzione replicata bar-per-bar. Runner VIVO, posizione SEMPRE LONG e")
|
||||||
|
print(" rientro al giro successivo dopo ogni scatto = caso peggiore per l'accumulo.")
|
||||||
|
print(" GRIGLIA: 2 asset x 2 modi (roll / floor) x 3 cadenze (1h / 4h / 24h) = 12 celle.\n")
|
||||||
|
out = {}
|
||||||
|
print(f" {'asset':<5} {'cadenza':>8} {'modo':>6} {'episodi':>8} {'peggior scatto':>15} "
|
||||||
|
f"{'max in 30g':>11} peggior DD dall'ingresso (*)")
|
||||||
|
for asset in ("BTC", "ETH"):
|
||||||
|
d = load_data(asset, "1h")
|
||||||
|
for ore in (1, 4, 24):
|
||||||
|
for modo in ("roll", "floor"):
|
||||||
|
r = simula(d, modo, ore)
|
||||||
|
e = r["episodi"]
|
||||||
|
pe = min((x["perdita_da_ancora"] for x in e), default=float("nan"))
|
||||||
|
if e:
|
||||||
|
tt = pd.Series([pd.Timestamp(x["t"]) for x in e])
|
||||||
|
m30 = int(max(((tt >= t) & (tt < t + pd.Timedelta("30D"))).sum() for t in tt))
|
||||||
|
else:
|
||||||
|
m30 = 0
|
||||||
|
out[(asset, ore, modo)] = r
|
||||||
|
print(f" {asset:<5} {str(ore)+'h':>8} {modo:>6} {r['n']:>8} "
|
||||||
|
f"{('%.1f%%' % (pe*100)) if e else ' —':>15} {m30:>11} "
|
||||||
|
f"{r['peggior_perdita_da_ingresso']:>+.1%}")
|
||||||
|
# elenco degli scatti alla cadenza DI PRODUZIONE (1h) e alla peggiore (24h)
|
||||||
|
for ore in (1, 24):
|
||||||
|
for modo in ("roll", "floor"):
|
||||||
|
e = out[(asset, ore, modo)]["episodi"]
|
||||||
|
if e:
|
||||||
|
print(f" {asset} {str(ore)+'h':>3} {modo:<5}: " +
|
||||||
|
", ".join(f"{pd.Timestamp(x['t']):%Y-%m-%d %H:%M} "
|
||||||
|
f"(da ingresso {x['perdita_da_ingresso']:+.1%})" for x in e[:8]))
|
||||||
|
print("""
|
||||||
|
COME SI LEGGE:
|
||||||
|
· MECCANISMO: uno stop che SEGUE il prezzo in discesa si ALLONTANA dal mercato -> per scattare
|
||||||
|
serve una caduta di ~26,5-30% DENTRO un solo giro di cron (l'ancora sta entro +5,26%/-4,76%
|
||||||
|
dal mark). Piu' fitto e' il cron, piu' lo stop e' irraggiungibile.
|
||||||
|
· A CADENZA DI PRODUZIONE (1h), su ~7-8 anni: BTC 0 scatti, ETH 1. Mai piu' di 1 in 30 giorni.
|
||||||
|
-> **l'accumulo che la SPEC temeva NON si verifica alla configurazione che gira.**
|
||||||
|
· ⚠️ MA LA RELAZIONE NON E' MONOTONA E IL TIMORE NON E' INFONDATO: a 4h BTC fa 2 scatti contro
|
||||||
|
l'1 del pavimento, a 24h ETH ne fa 5 con 2 dentro 30 giorni. Con un cron lento l'ancora resta
|
||||||
|
alta dopo una salita, lo stop siede vicino sotto il mercato, il crollo lo prende, si rientra e
|
||||||
|
la gamba successiva lo riprende. **Il rischio di accumulo e' funzione della CADENZA DEL CRON,
|
||||||
|
non di k**: il gradino di leva non lo crea e non lo amplifica.
|
||||||
|
· ⚠️ E qui c'e' un aggancio operativo: il docstring di `scripts/live/book_execute.py` prescrive
|
||||||
|
«ogni ~230 minuti» (la griglia di SKH01) mentre il cron gira **ogni ora** (misurato sopra: 1443
|
||||||
|
giri in 1442 ore). Chi "correggesse" la cadenza verso il docstring sposterebbe il libro dalla
|
||||||
|
riga 1h alla riga 4h — quella in cui BTC raddoppia gli scatti.
|
||||||
|
|
||||||
|
(*) ⚠️ CAVEAT sulla colonna, e sul suo artefatto: in questa lente la posizione e' SEMPRE aperta
|
||||||
|
dal 2018, quindi in modo `roll` l'"ingresso" e' un prezzo di anni prima e il -48%/-61% e' il
|
||||||
|
drawdown di un buy&hold pluriennale, NON di un trade del libro (che dura ore-giorni). E'
|
||||||
|
esattamente la trappola n.10 del brief: il confronto va appaiato per INGRESSO. Il §4b rifa'
|
||||||
|
la stessa domanda su durate realistiche.""")
|
||||||
|
|
||||||
|
# cadute massime realizzabili dentro un giro: e' la distanza che separa il rotolante dallo scatto
|
||||||
|
print("\n Quanto deve cadere il prezzo DENTRO un giro perche' il rotolante scatti:")
|
||||||
|
print(" (lo stop e' 0,70*ancora e l'ancora sta entro +5,26%/-4,76% dal mark -> serve ~26,5-30%)")
|
||||||
|
print(f"\n {'asset':<5} {'finestra':>9} {'caduta max storica (low vs close t-w)':>42}")
|
||||||
|
for asset in ("BTC", "ETH"):
|
||||||
|
d = load_data(asset, "1h")
|
||||||
|
for w in (1, 4, 24):
|
||||||
|
m = float((d["low"].rolling(w).min() / d["close"].shift(w) - 1).min())
|
||||||
|
print(f" {asset:<5} {str(w)+'h':>9} {m:>41.2%}")
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def cicli_bounded(df: pd.DataFrame, modo: str, hold_h: int, sl: float = SL_PCT, tol: float = TOL) -> dict:
|
||||||
|
"""Stessa regola, ma su TRADE di durata realistica: cicli NON sovrapposti di `hold_h` ore, ogni
|
||||||
|
ciclo entra al suo inizio col proprio prezzo. Risponde alla domanda «quanto puo' perdere un
|
||||||
|
trade dall'INGRESSO prima che lo stop lo fermi», senza l'artefatto della posizione perpetua."""
|
||||||
|
c = df["close"].to_numpy(float); lo = df["low"].to_numpy(float); op = df["open"].to_numpy(float)
|
||||||
|
n = len(c)
|
||||||
|
peggiori, scattati, sfondati = [], 0, 0
|
||||||
|
for a in range(1, n - hold_h, hold_h):
|
||||||
|
entry = c[a - 1]
|
||||||
|
stop = entry * (1 - sl)
|
||||||
|
worst = 0.0
|
||||||
|
fired = False
|
||||||
|
for i in range(a, a + hold_h):
|
||||||
|
mark = c[i - 1]
|
||||||
|
if modo == "roll":
|
||||||
|
want = mark * (1 - sl)
|
||||||
|
if abs(stop - want) / want >= tol:
|
||||||
|
stop = want
|
||||||
|
worst = min(worst, lo[i] / entry - 1)
|
||||||
|
if lo[i] <= stop:
|
||||||
|
fired = True
|
||||||
|
worst = min(worst, min(op[i], stop) / entry - 1)
|
||||||
|
break
|
||||||
|
peggiori.append(worst)
|
||||||
|
scattati += fired
|
||||||
|
sfondati += (worst < -sl) and not fired # perdita >30% SENZA che lo stop fermasse
|
||||||
|
p = np.array(peggiori)
|
||||||
|
return dict(n=len(p), peggiore=float(p.min()), scattati=scattati, sfondati=sfondati,
|
||||||
|
q_sfondati=sfondati / len(p))
|
||||||
|
|
||||||
|
|
||||||
|
def bounded():
|
||||||
|
sec("4b. LA PERDITA DALL'INGRESSO, su trade di durata realistica (cicli non sovrapposti)")
|
||||||
|
print(" Il libro tiene una posizione ore-giorni, non anni. Qui ogni ciclo ha il PROPRIO ingresso.")
|
||||||
|
print(" GRIGLIA: 2 asset x 2 modi x 3 durate (24h / 7g / 30g) = 12 celle (totale script: 24).\n")
|
||||||
|
print(f" {'asset':<5} {'durata':>7} {'modo':>6} {'cicli':>7} {'scatti':>7} "
|
||||||
|
f"{'peggior DD da ingresso':>23} {'cicli oltre -30% SENZA scatto':>31}")
|
||||||
|
for asset in ("BTC", "ETH"):
|
||||||
|
d = load_data(asset, "1h")
|
||||||
|
for h, lab in ((24, "24h"), (168, "7g"), (720, "30g")):
|
||||||
|
for modo in ("roll", "floor"):
|
||||||
|
r = cicli_bounded(d, modo, h)
|
||||||
|
print(f" {asset:<5} {lab:>7} {modo:>6} {r['n']:>7} {r['scattati']:>7} "
|
||||||
|
f"{r['peggiore']:>23.1%} {r['sfondati']:>8} ({r['q_sfondati']:>6.2%})")
|
||||||
|
print("""
|
||||||
|
Qui il rotolante mostra il suo vero difetto, senza artefatti: su trade veri lascia passare
|
||||||
|
perdite dall'ingresso ben oltre il 30% SENZA scattare, mentre il pavimento no. Non e' un costo
|
||||||
|
che il gradino di leva crea ne' amplifica: e' una proprieta' di oggi, ed e' il motivo per cui
|
||||||
|
il disaster-SL NON e' un "massimo -30% per trade". Cio' che limita la perdita di un trade nel
|
||||||
|
libro sono le uscite software di TP01/SKH01, non questo bracket.""")
|
||||||
|
|
||||||
|
|
||||||
|
def costo(out: dict):
|
||||||
|
sec("5. IL NUMERO CHE SBLOCCA (o chiude) IL GRADINO DI LEVA")
|
||||||
|
for k in (1.00, 1.25, 1.50):
|
||||||
|
b = 2 * 0.50 * k * SL_PCT
|
||||||
|
print(f" k={k:.2f} -> costo di UN episodio = 2*frac*k*sl_pct = {b:.1%} dell'equity"
|
||||||
|
f" (margine su 50%: {0.50/b:.2f}x)")
|
||||||
|
print("""
|
||||||
|
La SPEC temeva che il 37,5% a k=1,25 fosse il costo di UN episodio fra molti, e che quindi il
|
||||||
|
margine 1,33x non fosse quello vero. Le misure:
|
||||||
|
|
||||||
|
1) B copre GIA' il caso peggiore per asset. Il fattore `len(ASSETS)*frac` e' il lordo su ENTRAMBE
|
||||||
|
le gambe: «un episodio» in G2 significa gia' BTC **e** ETH stoppati insieme. Alla cadenza di
|
||||||
|
produzione questo non e' mai successo in ~7-8 anni (BTC 0 scatti, ETH 1, in anni diversi).
|
||||||
|
2) Con RUNNER VIVO a 1h gli scatti sono 0-1 in tutta la storia e mai piu' di 1 in 30 giorni ->
|
||||||
|
nessun accumulo da sommare al 37,5%.
|
||||||
|
3) Nella finestra in cui il disaster-SL serve davvero — l'OUTAGE — il ri-ancoraggio e' SPENTO PER
|
||||||
|
COSTRUZIONE: nessun giro di cron, nessuna cancellazione, nessun ri-piazzamento, e nemmeno un
|
||||||
|
rientro. Lo stop resta dov'era: **un episodio solo, per costruzione.** Il log misura la taglia
|
||||||
|
di quella finestra: nessun buco del cron oltre 2 ore in 60 giorni.
|
||||||
|
|
||||||
|
=> Il 37,5% resta il costo di UN episodio: il margine 1,33x di G6 e' quello vero, e la gamba
|
||||||
|
WORST-DAY del gradino regge. **Il nodo e' sciolto, e non blocca.**
|
||||||
|
|
||||||
|
⚠️ Cio' che il rotolante rompe e' un'ALTRA affermazione — che il progetto non scrive da nessuna
|
||||||
|
parte ma che il nome «disaster-SL -30%» suggerisce: che la perdita su un trade sia limitata al
|
||||||
|
30%. NON lo e' (§4b: fino a -60,6% BTC / -61,5% ETH dall'ingresso senza che lo stop scatti, nel
|
||||||
|
7-13% dei cicli a 30 giorni). E' una proprieta' di OGGI, indipendente da k, e la protezione vera
|
||||||
|
del singolo trade sono le uscite software di TP01/SKH01.
|
||||||
|
|
||||||
|
⚠️ E c'e' un accoppiamento nuovo da registrare: l'accumulo di episodi e' funzione della CADENZA
|
||||||
|
DEL CRON (1h: no · 4h: BTC 2 · 24h: ETH 5 con 2 in 30 giorni). Oggi la cadenza e' misurata sana,
|
||||||
|
ma nessun sorvegliante la controlla contro questa conseguenza, e il docstring di `book_execute`
|
||||||
|
ne prescrive una (~230 min) che cade nella riga peggiore. Non e' un'azione richiesta qui: e' il
|
||||||
|
parametro da rileggere prima del gradino, insieme a G2.""")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print(H)
|
||||||
|
print(" SL-ANCHOR — il disaster-SL e' un PAVIMENTO o un -30% ROTOLANTE?")
|
||||||
|
print(" Nodo aperto da docs/research/SPEC-scale-key.md §4.5 · SOLA LETTURA · nessun ordine")
|
||||||
|
print(H)
|
||||||
|
soglie = attesa()
|
||||||
|
info = misura_log(soglie)
|
||||||
|
if "--venue" in sys.argv[1:]:
|
||||||
|
riscontro_venue(info)
|
||||||
|
else:
|
||||||
|
print("\n (riscontro sul venue non richiesto: aggiungere --venue per 2 letture di open_orders)")
|
||||||
|
out = conseguenza()
|
||||||
|
bounded()
|
||||||
|
costo(out)
|
||||||
|
print("\n" + H)
|
||||||
|
print(" VERDETTO — il disaster-SL e' ROTOLANTE: MISURATO, non dedotto.")
|
||||||
|
print(" · 3 osservazioni di produzione con bracket ricostruito a POSIZIONE FERMA e 0 ordini;")
|
||||||
|
print(" · i salti (+5,74 / +5,81 / +5,38%) cadono appena sopra la soglia +5,263% predetta;")
|
||||||
|
print(" · il venue conferma: il bracket vivo e' piu' GIOVANE della posizione che protegge.")
|
||||||
|
print(" La conseguenza temuta — episodi che si accumulano — e' REFUTATA alla cadenza che gira:")
|
||||||
|
print(" il 37,5% resta il costo di un episodio singolo e il margine 1,33x di G6 e' quello vero.")
|
||||||
|
print(H)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user