Seloriva?

In seguito ad una malaugurata intervista/scontro rilasciata da Merz , ho sul mio whatsapp una tonnellata di amici che mi chiedono cosa sia sta roba. Sono andato a dare un'occhiata al loro sito (seloriva.net) e ho visto di cosa si tratti. E cosi', scrivo direttamente sul blog cosa ne penso.
In pratica, se andate sul sito e vi registrate, e versate circa 250 euro, scoprirete che il vostro conto dopo un giorno e' cresciuto, in una settimana potrebbe anche (potrebbe) essere decuplicato, e che scaricando la piccola cifra, vi arriva sul conto in banca. Vedo recensioni di gente che lo ha usato con una piccola cifra, ed e' soddisfatta.
La fatina dei denti, insomma.
Si tratta di qualcosa che , onestamente, difficilmente e' quel che dice di essere. Nel senso che loro dicono "dammi dei soldi, e io te li faccio crescere ad un ritmo pazzesco usando l' Intelligenza Artificiale".
Appena sento una buzzword, personalmente mi vedo un attimo in dubbio. Il primo dubbio che mi viene e' che in realta' non serve scomodare nessuna AI, se tutto quello che volete fare e' fast trading. In generale, basta un software capace di capire una strategia di Parrondo, e potreste (notare il condizionale) guadagnare molti soldi.
In generale una strategia Parrondo-like non e' una strategia astuta, anzi e' considerata "ingenua" dagli operatori. Ma per farvi capire quanto NON sia difficile scrivere un software del genere, lo allego sotto.
E non usa nessuna AI. Il mio codice, intendo.
Perche' una cosa simile e' impossibile? Mai detto che sia impossibile. Strategie simili, nel lungo e lunghissimo termine possono funzionare. Su tempi brevi, sono scommesse.
Allora facciamo le domande del caso.
- D: Perche' non lo fanno tutti?
- R: perche' pochissime piattaforme di trading, se non quasi nessuna, danno accesso a delle API che possano consentire di farlo. Il trading e' un wallet garden, essenzialmente.
Andiamo avanti.
- D: perche' un simile strumento non e' famoso presso il pubblico?
- R: perche' protegge dalle truffe i piccoli risparmiatori. Se leggete le T&C , infatti, in pratica non potete MAI lamentarvi di quel che succede.
Ma anche:
- D: perche' un simile strumento non e' famoso presso il pubblico?
- R: perche' dietro alla buzzword AI sono contenuti dei rischi nascosti, che per legge non e' possibile spalmare sul pubblico. E' vietato vendere cose simili, almeno in Italia.
Inoltre:
- D:Ma i "grandi" usano strumenti simili, vero? LORO non vogliono che noi sappiamo.
- R: a dire il vero, "LORO" sarebbero piu' che felici di avere milioni di fessi che si giocano i soldi in questo modo. E li perdono.
Ma anche:
- D:Ma i "grandi" usano strumenti simili, vero? LORO non vogliono che noi sappiamo.
- R: i "grandi", se perdono un milione di euro, non perdono il sonno. Se tu perdi l'unica casa di cui hai pagato il mutuo in una vita di lavoro, il sonno lo perdi eccome. I ponti sono rumorosi.
Perche' escludi che ci sia una vera AI dietro?
Non lo escludo affatto. Esistono dei modelli di AI, come le reti RNN (Recursive Neural Netrowk) che mi sentirei di provare nel mondo del finance. Le usiamo al lavoro per fare failire prediction, cioe' segnalare se un'auto stia per rompersi, avendo i diagnostici inviati, periodicamente, dall'auto stessa.
Funzionano, diciamo, spesso.
Ma perche' escluderei che venga usata da quel sito? Perche' non puoi prevedere un picco del 1000% in una settimana, se in realta' non avviene un picco del 1000% ogni settimana. Cosa che non avviene.
E' semplice.
Hanno paura che tutti la usino e allora tutti diventino ricchissimi?
Capiamoci. Supponiamo che OGNI tedesco butti li' i suoi risparmi, e che funzioni. Poiche' il guadagno rimane investito sino a quando non viene liquidato, e in una settimana guadagnate un sacco, questo produrrebbe borse che cominciano, inspiegabilmente, a crescere.
Se poi usasse anche il paradosso di Parrondo, come ho fatto, aziende che stanno crollando vedrebbero un inspiegabile finanziatore che arriva e li salva. Il guaio, pero', e' che questa cosa e' gia' accaduta. La borsa si gonfia perche' tutti giocano in borsa, e quindi e' una profezia autoavverante.
Voi la chiamerete "ma e' una figata".
Gli storici dell'economia invece la chiamano "crisi del 1929".
Suggerisco quindi di provarci?
Essendo una cosa appena esplosa, siamo nella fase in cui devono trovare polli e convincerli a giocare somme piu' grosse. Di conseguenza, probabilmente vi faranno vincere.
Ma per piu' di un mese o due, partendo da oggi, ricordate che poi comincera' la mietitura.
E pagherete anche per quelli che ci hanno guadagnato il loro biglietto da 500€.
E comunque, non sono riuscito a trovare il testo di questa fantomatica intervista di Merz dove avrebbe parlato di questo investimento. Quindi , sinora, per quanto mi riguarda, e' una truffa.
// Simulatore di "volatility harvesting" in stile Parrondo per due asset con barre a 5 minuti
// Autore: Uriel Fanelli
//
// Idea: anche se ciascun asset ha una tendenza media al ribasso, una
// mean-reversion del *rapporto* tra i due può permettere una logica
// contrarian/switching o un ribilanciamento a bande che estragga rendimento
// dalla volatilità. È un effetto "alla Parrondo": due giochi perdenti che,
// combinati con regole dipendenti dallo stato, possono produrre crescita in
// certi regimi di mercato.
//
// DISCLAIMER: è un giocattolo di ricerca. Non dimostra nulla sui mercati reali
// e non garantisce risultati. Costi di transazione, slippage e selezione avversa
// di solito distruggono strategie ingenue. Usalo a tuo rischio.
//
// Build:
// go build -o parrondo
//
// Esempi d'uso:
// # Su CSV proprio (timestamp,assetA,assetB):
// ./parrondo -csv dati.csv -fee 0.01 -strategy contrarian -thresh 0.8
//
// # Confronto tra strategie:
// ./parrondo -csv dati.csv -compare
//
// # Test sintetico (mean-reverting, entrambi gli asset in trend ribassista):
// ./parrondo -simulate 20000 -seed 42 -fee 0.01 -strategy contrarian -thresh 0.9
// ./parrondo -simulate 20000 -seed 42 -compare
//
// Formato CSV atteso:
// timestamp,assetA,assetB
// 2025-09-17T09:00:00Z,100.0,100.0
// 2025-09-17T09:05:00Z,99.8,99.6
// ... (spaziatura di 5 minuti consigliata ma non obbligatoria)
//
// Strategie implementate:
// holdA : buy-and-hold dell'asset A
// holdB : buy-and-hold dell'asset B
// alternate : switch ogni K barre (opzione -period, default 1)
// rebalance : mantieni 50/50 con ribilanciamento a banda quando |wA-0.5| > thresh (0<thresh<0.5)
// contrarian : switching mono-asset: tieni il *perdente recente* quando la z-score di Δ ln(A/B) supera la soglia
// Usa una finestra mobile (opzione -window) per la z-score delle variazioni del log-rapporto.
//
// Note:
// - La fee è addebitata quando *si scambia*, non sul semplice drift di prezzo; la fee è % del valore spostato.
// - Per il ribilanciamento, la fee si applica alla frazione scambiata per tornare a 50/50.
// - Per il contrarian si tiene esattamente un asset per volta, switchando quando il segnale scatta.
// - Tutti i risultati ignorano tasse e costi di finanziamento.
//
package main
import (
"bufio"
"encoding/csv"
"errors"
"flag"
"fmt"
"log"
"math"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
type Bar struct {
Ts time.Time // timestamp della barra
A float64 // prezzo asset A
B float64 // prezzo asset B
}
type Series struct {
Bars []Bar
}
func must(err error) {
if err != nil { log.Fatal(err) }
}
// ---------------------- Caricamento / simulazione dati ------------------
// Carica un CSV (timestamp,A,B) in formato RFC3339 o "YYYY-MM-DD HH:MM:SS"
func loadCSV(path string) (*Series, error) {
f, err := os.Open(path)
if err != nil { return nil, err }
defer f.Close()
r := csv.NewReader(bufio.NewReader(f))
rec, err := r.ReadAll()
if err != nil { return nil, err }
if len(rec) < 2 { return nil, errors.New("csv troppo corto: servono header + dati") }
bars := make([]Bar, 0, len(rec)-1)
for i:=1; i<len(rec); i++ {
row := rec[i]
if len(row) < 3 { continue }
tsStr := strings.TrimSpace(row[0])
aStr := strings.TrimSpace(row[1])
bStr := strings.TrimSpace(row[2])
ts, err := time.Parse(time.RFC3339, tsStr)
if err != nil {
// parse più permissivo
ts, err = time.Parse("2006-01-02 15:04:05", tsStr)
if err != nil { return nil, fmt.Errorf("parse time %q: %w", tsStr, err) }
}
a, err := strconv.ParseFloat(aStr, 64)
if err != nil { return nil, fmt.Errorf("parse A @row %d: %w", i, err) }
b, err := strconv.ParseFloat(bStr, 64)
if err != nil { return nil, fmt.Errorf("parse B @row %d: %w", i, err) }
if a <= 0 || b <= 0 { return nil, fmt.Errorf("prezzo non positivo alla riga %d", i) }
bars = append(bars, Bar{Ts: ts, A: a, B: b})
}
if len(bars) < 3 { return nil, errors.New("servono almeno 3 righe di dati") }
return &Series{Bars: bars}, nil
}
// Simula due serie su log-prezzi con mean-reversion tipo Ornstein-Uhlenbeck
// e una media che deriva verso il basso. Le scosse sono correlate (negativamente)
// per amplificare la mean-reversion *relativa* tra A e B.
func simulateOU(n int, seed int64) *Series {
if n < 3 { n = 3 }
r := rand.New(rand.NewSource(seed))
bars := make([]Bar, n)
// parametri
dt := 1.0
kappaA, kappaB := 0.08, 0.10 // velocità di mean-reversion
mu0 := 0.0 // media iniziale del log-prezzo
trend := -0.0005 // drift della media per step (negativo)
sigA, sigB := 0.04, 0.045 // volatilitá
corr := -0.6 // correlazione tra shock (negativa)
// valori iniziali
xA, xB := 0.0, 0.0 // log-prezzi
mu := mu0
start := time.Now().Add(-time.Duration(n)*5*time.Minute)
for i:=0; i<n; i++ {
// normali correlate via Cholesky
u1, u2 := normalBM(r), normalBM(r)
eA := u1
eB := corr*u1 + math.Sqrt(1-corr*corr)*u2
mu += trend*dt
xA += kappaA*(mu - xA)*dt + sigA*math.Sqrt(dt)*eA
xB += kappaB*(mu - xB)*dt + sigB*math.Sqrt(dt)*eB
bars[i] = Bar{Ts: start.Add(time.Duration(i)*5*time.Minute), A: math.Exp(xA)*100, B: math.Exp(xB)*100}
}
return &Series{Bars: bars}
}
// Box-Muller per Normale(0,1)
func normalBM(r *rand.Rand) float64 {
u1 := r.Float64()
if u1 == 0 { u1 = 1e-12 }
u2 := r.Float64()
return math.Sqrt(-2*math.Log(u1)) * math.Cos(2*math.Pi*u2)
}
// -------------------------- Motore metriche -----------------------------
type Metrics struct {
TotalReturn float64 // fattore totale (es: 1.23 = +23%)
CAGR float64 // crescita annua composta
Vol float64 // volatilità annua (stdev)
Sharpe float64 // Sharpe (rf=0)
MaxDD float64 // max drawdown (0..1)
Trades int // numero di trade
}
func (m Metrics) String() string {
return fmt.Sprintf("Total: %.2f%%, CAGR: %.2f%%, Vol: %.2f%%, Sharpe: %.2f, MaxDD: %.2f%%, Trades: %d",
(m.TotalReturn-1)*100, (m.CAGR-1)*100, m.Vol*100, m.Sharpe, m.MaxDD*100, m.Trades)
}
// Calcola log-return e metriche da una equity curve
func computeMetrics(eq []float64, dtYears float64, trades int) Metrics {
if len(eq) < 2 { return Metrics{} }
rets := make([]float64, 0, len(eq)-1)
for i:=1; i<len(eq); i++ {
rets = append(rets, math.Log(eq[i]/eq[i-1]))
}
// Annualizzazione basata su barre 5-min: ~252 d * 78 barre/d = 19656 barre/anno
barsPerYear := 252.0*78.0
mu := mean(rets)
varr := variance(rets, mu)
annMu := mu*barsPerYear
annVol := math.Sqrt(varr*barsPerYear)
// Sharpe con rf=0 (approssimazione log-return)
sh := 0.0
if annVol > 0 { sh = annMu/annVol }
// CAGR dal rapporto finale su anni; se dtYears==0, inferisci dalle barre
if dtYears <= 0 { dtYears = float64(len(eq)-1)/barsPerYear }
cagr := math.Pow(eq[len(eq)-1]/eq[0], 1.0/dtYears)
// Max drawdown
peak := eq[0]
maxDD := 0.0
for _, v := range eq {
if v > peak { peak = v }
dd := 1 - v/peak
if dd > maxDD { maxDD = dd }
}
return Metrics{TotalReturn: eq[len(eq)-1]/eq[0], CAGR: cagr, Vol: annVol, Sharpe: sh, MaxDD: maxDD, Trades: trades}
}
func mean(x []float64) float64 { s:=0.0; for _,v := range x { s+=v }; return s/float64(len(x)) }
func variance(x []float64, m float64) float64 { s:=0.0; for _,v := range x { d:=v-m; s+=d*d }; return s/float64(len(x)) }
// ---------------------------- Strategie ---------------------------------
type Strategy interface {
Run(s *Series, fee float64) (eq []float64, trades int)
Name() string
}
// Hold A o B
type Hold struct{ idx int }
func (h Hold) Name() string { if h.idx==0 { return "holdA" } ; return "holdB" }
func (h Hold) Run(s *Series, fee float64) ([]float64, int) {
eq := make([]float64, len(s.Bars))
eq[0] = 1.0
for i:=1; i<len(s.Bars); i++ {
prev := s.Bars[i-1]
cur := s.Bars[i]
var r float64
if h.idx==0 { r = cur.A/prev.A } else { r = cur.B/prev.B }
eq[i] = eq[i-1]*r
}
return eq, 0
}
// Alterna ogni "period" barre: A->B->A->...
type Alternate struct{ period int }
func (a Alternate) Name() string { return fmt.Sprintf("alternate(p=%d)", a.period) }
func (a Alternate) Run(s *Series, fee float64) ([]float64, int) {
if a.period<=0 { a.period=1 }
eq := make([]float64, len(s.Bars))
eq[0] = 1.0
posA := true
trades := 0
for i:=1; i<len(s.Bars); i++ {
prev := s.Bars[i-1]
cur := s.Bars[i]
var r float64
if posA { r = cur.A/prev.A } else { r = cur.B/prev.B }
// switch ogni "period" barre
if i%a.period==0 {
posA = !posA
trades++
// fee sullo switch completo (100% turnover)
eq[i-1] *= (1.0 - fee)
}
eq[i] = eq[i-1]*r
}
return eq, trades
}
// Ribilanciamento a banda verso 50/50 quando |wA-0.5|>thresh
type Rebalance struct{ thresh float64 }
func (rnb Rebalance) Name() string { return fmt.Sprintf("rebalance(band=%.3f)", rnb.thresh) }
func (rnb Rebalance) Run(s *Series, fee float64) ([]float64, int) {
if rnb.thresh <= 0 || rnb.thresh >= 0.5 { rnb.thresh = 0.1 }
wA := 0.5
valA := 0.5
valB := 0.5
eq := make([]float64, len(s.Bars))
eq[0] = 1.0
trades := 0
for i:=1; i<len(s.Bars); i++ {
prev := s.Bars[i-1]
cur := s.Bars[i]
// evoluzione delle due gambe
valA *= cur.A/prev.A
valB *= cur.B/prev.B
port := valA + valB
wA = valA/port
if math.Abs(wA-0.5) > rnb.thresh {
// trade per ripristinare 50/50
desiredA := 0.5*port
trade := math.Abs(desiredA - valA) // notional spostato
cost := trade * fee
// esegui ribilanciamento (fee sottratta)
if desiredA > valA {
valA = desiredA - cost
valB = port - desiredA
} else {
valB = 0.5*port - cost
valA = port - 0.5*port
}
port = valA + valB
trades++
}
eq[i] = port
}
return eq, trades
}
// Contrarian mono-asset basato sulla z-score della variazione del log-rapporto
// Segnale: s_t = (Δ ln(A/B) - media)/std sulle ultime `window` barre
// Se s_t > thresh -> A ha sovraperformato B in modo anomalo -> switch su B (compra il perdente)
// Se s_t < -thresh -> B ha sovraperformato A in modo anomalo -> switch su A
// Altrimenti mantieni la posizione corrente.
type Contrarian struct{ thresh float64; window int }
func (c Contrarian) Name() string { return fmt.Sprintf("contrarian(z>%.2f,win=%d)", c.thresh, c.window) }
func (c Contrarian) Run(s *Series, fee float64) ([]float64, int) {
if c.window < 5 { c.window = 30 }
if c.thresh <= 0 { c.thresh = 0.8 }
eq := make([]float64, len(s.Bars))
eq[0] = 1.0
pos := 0 // 0 = A, 1 = B (teniamo un solo asset)
trades := 0
// pre-calcola Δ ln(A/B)
dlr := make([]float64, len(s.Bars))
for i:=1; i<len(s.Bars); i++ {
prev := s.Bars[i-1]
cur := s.Bars[i]
dlr[i] = math.Log((cur.A/prev.A)/(cur.B/prev.B))
}
for i:=1; i<len(s.Bars); i++ {
prev := s.Bars[i-1]
cur := s.Bars[i]
// evoluzione dell'equity
var r float64
if pos==0 { r = cur.A/prev.A } else { r = cur.B/prev.B }
// segnale
if i >= c.window {
m := mean(dlr[i-c.window+1 : i+1])
v := variance(dlr[i-c.window+1 : i+1], m)
std := math.Sqrt(v)
z := 0.0
if std > 0 { z = (dlr[i]-m)/std }
prevPos := pos
if z > c.thresh { pos = 1 } // A forte vs B -> ruota su B
if z < -c.thresh { pos = 0 } // B forte vs A -> ruota su A
if pos != prevPos {
trades++
// switch a pieno notional -> paga fee una volta
eq[i-1] *= (1.0 - fee)
}
}
eq[i] = eq[i-1]*r
}
return eq, trades
}
// ------------------------------- Main -----------------------------------
func main() {
csvPath := flag.String("csv", "", "Percorso CSV dati (timestamp,A,B)")
simN := flag.Int("simulate", 0, "Genera N barre sintetiche a 5 minuti (OU mean-reversion + drift ribassista)")
seed := flag.Int64("seed", 1, "Seed RNG per simulazione")
fee := flag.Float64("fee", 0.01, "Fee di switching/ribilanciamento come frazione (0.01 = 1%)")
strategy := flag.String("strategy", "contrarian", "Strategia: holdA|holdB|alternate|rebalance|contrarian")
period := flag.Int("period", 1, "Periodo per alternate (in barre)")
thresh := flag.Float64("thresh", 0.1, "Soglia banda/segno (rebalance: 0<th<0.5; contrarian: z-score)")
window := flag.Int("window", 30, "Finestra mobile per la z-score del contrarian")
compare := flag.Bool("compare", false, "Esegui tutte le strategie e confronta")
flag.Parse()
var s *Series
var err error
if *csvPath != "" {
s, err = loadCSV(*csvPath)
must(err)
} else if *simN > 0 {
s = simulateOU(*simN, *seed)
} else {
fmt.Println("Fornisci -csv dati.csv oppure -simulate N")
os.Exit(1)
}
// seleziona strategia/e
strats := []Strategy{}
if *compare {
strats = []Strategy{
Hold{0}, Hold{1},
Alternate{period: *period},
Rebalance{thresh: clamp(*thresh, 0.01, 0.49)},
Contrarian{thresh: *thresh, window: *window},
}
} else {
switch *strategy {
case "holdA": strats = []Strategy{Hold{0}}
case "holdB": strats = []Strategy{Hold{1}}
case "alternate": strats = []Strategy{Alternate{period: *period}}
case "rebalance": strats = []Strategy{Rebalance{thresh: clamp(*thresh, 0.01, 0.49)}}
case "contrarian": fallthrough
default: strats = []Strategy{Contrarian{thresh: *thresh, window: *window}}
}
}
fmt.Printf("Barre: %d, Da %s a %s, Fee=%.2f%%
", len(s.Bars), s.Bars[0].Ts.Format(time.RFC3339), s.Bars[len(s.Bars)-1].Ts.Format(time.RFC3339), *fee*100)
for _, st := range strats {
eq, trades := st.Run(s, *fee)
m := computeMetrics(eq, 0, trades)
fmt.Printf("%-28s -> %s
", st.Name(), m.String())
}
}
func clamp(x, lo, hi float64) float64 {
if x < lo { return lo }
if x > hi { return hi }
return x
}