Correlazione legala al segno di binance corretta.

main
Uriel Fanelli 2025-10-14 19:23:59 +02:00
parent 0e42130508
commit 5b52cd031a
3 changed files with 98 additions and 7 deletions

View File

@ -6,6 +6,12 @@ import (
)
// ================== Dataset ==================
//
// - Seqs: finestre di log-return (dimensione lookback) in z-score.
// - Labels: log-return successivo (t+1) in z-score.
// - Mean/Std: media e dev std calcolate SOLO sul training (sulle feature X),
// riutilizzate per normalizzare sia X che Y. In inferenza denormalizziamo
// con predLR = predZ*Std + Mean (vedi decision.go).
type Dataset struct {
Seqs [][]float64
@ -14,10 +20,17 @@ type Dataset struct {
Std float64
}
// buildDataset costruisce (train, val) a partire dalle Kline:
// 1) prezzi di chiusura -> log(p)
// 2) differenze -> log-return (lr[t] = log(p[t]) - log(p[t-1]))
// 3) finestre X = lr[i : i+lookback], label Y = lr[i+lookback]
// 4) split train/val e z-score usando Mean/Std del SOLO training (sulle X)
func buildDataset(kl []Kline, lookback int, valSplit float64) (Dataset, Dataset) {
if len(kl) < lookback+2 {
return Dataset{}, Dataset{}
}
// 1) Close -> log price
prices := make([]float64, len(kl))
for i, k := range kl {
prices[i] = k.Close
@ -26,10 +39,14 @@ func buildDataset(kl []Kline, lookback int, valSplit float64) (Dataset, Dataset)
for i, p := range prices {
logp[i] = math.Log(p)
}
// 2) log-return
lr := make([]float64, len(logp)-1)
for i := 1; i < len(logp); i++ {
lr[i-1] = logp[i] - logp[i-1]
}
// 3) finestre X e label Y (prossimo step)
N := len(lr) - lookback
if N <= 0 {
return Dataset{}, Dataset{}
@ -42,15 +59,26 @@ func buildDataset(kl []Kline, lookback int, valSplit float64) (Dataset, Dataset)
X = append(X, win)
Y = append(Y, lr[i+lookback])
}
// 4) split train/val
valN := int(float64(len(X)) * valSplit)
if valN < 1 {
valN = 1
}
trainN := len(X) - valN
if trainN < 1 {
// fallback minimo: tutto train meno 1 per val
trainN = max(1, len(X)-1)
valN = len(X) - trainN
}
// 5) mean/std SOLO sul training (sulle feature)
mean, std := meanStd(flatten(X[:trainN]))
if std == 0 {
std = 1e-6
}
// 6) z-score per X e Y con la stessa mean/std
zX := make([][]float64, len(X))
for i := range X {
zX[i] = make([]float64, lookback)
@ -62,6 +90,7 @@ func buildDataset(kl []Kline, lookback int, valSplit float64) (Dataset, Dataset)
for i := range Y {
zY[i] = (Y[i] - mean) / std
}
train := Dataset{Seqs: zX[:trainN], Labels: zY[:trainN], Mean: mean, Std: std}
val := Dataset{Seqs: zX[trainN:], Labels: zY[trainN:], Mean: mean, Std: std}
return train, val
@ -109,5 +138,8 @@ func printManualInstruction(fromLabel, toLabel string, amountFrom, expectedBps,
maxFeeBps = 0
}
maxFeeAbs := amountFrom * (maxFeeBps / 1e4)
return fmt.Sprintf("ISTRUZIONI MANUALI: esegui swap %s->%s amount=%.8f; procedi solo se feeTotali<=%.3f bps (stima THOR=%.3f bps) e costo<=~%.8f %s", fromS, toS, amountFrom, maxFeeBps, feeBps, maxFeeAbs, fromS)
return fmt.Sprintf(
"ISTRUZIONI MANUALI: esegui swap %s->%s amount=%.8f; procedi solo se feeTotali<=%.3f bps (stima THOR=%.3f bps) e costo<=~%.8f %s",
fromS, toS, amountFrom, maxFeeBps, feeBps, maxFeeAbs, fromS,
)
}

View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"log"
"math"
"net/http"
"net/url"
@ -35,9 +36,12 @@ func decideTrade(cfg Config, dsTrain, dsVal Dataset, model *LSTM, lastSeq []floa
// ---- inferenza movimento previsto ----
model.resetState()
predZ, _ := model.forward(lastSeq)
predLR := predZ*dsTrain.Std + dsTrain.Mean
moveBps := (math.Exp(predLR) - 1) * 1e4
predZ, _ := model.forward(lastSeq) // pred in z-score
predLR := predZ*dsTrain.Std + dsTrain.Mean // ritorno log previsto (scala reale)
moveBps := (math.Exp(predLR) - 1) * 1e4 // bps sul sottostante coerente con ThorFrom->ThorTo
log.Printf("inferenza: predZ=%.6f predLR=%.6f moveBps=%.3f → direzione=%s",
predZ, predLR, moveBps, map[bool]string{true: "AB (From→To)", false: "BA (To→From)"}[moveBps >= 0])
// ---- confidenza valida? ----
valMAE := 0.0

61
rnn.go
View File

@ -8,7 +8,12 @@ import (
"gonum.org/v1/gonum/mat"
)
/* Dataset + LSTM + helper come elencato */
/* LSTM con:
- training invariato (Huber)
- calibrazione automatica del segno alla 1ª epoca (flip Why/by) se anti-segnale
- utilità numeriche locali, no dipendenze sparse
*/
// ================== LSTM (gonum) ==================
type LSTM struct {
@ -59,6 +64,8 @@ func newLSTM(inputSize, hiddenSize int, lr float64, seed int64) *LSTM {
func (m *LSTM) resetState() { m.h.Zero(); m.c.Zero() }
// --- utilità numeriche locali ---
func denseOf(a mat.Matrix) *mat.Dense {
r, c := a.Dims()
out := mat.NewDense(r, c, nil)
@ -95,6 +102,34 @@ func scaleM(a mat.Matrix, s float64) *mat.Dense {
return applyM(a, func(v float64) float64 { return v * s })
}
func sigmoid(x float64) float64 { return 1.0 / (1.0 + math.Exp(-x)) }
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
// --- flip output (calibrazione segno) ---
func (m *LSTM) flipOutputSign() {
m.Why.Scale(-1, m.Why)
m.by.Scale(-1, m.by)
}
// Somma dei prodotti pred*label in z-score (misura grezza della direzionalità)
func (m *LSTM) directionSum(ds Dataset) float64 {
if len(ds.Seqs) == 0 {
return 0
}
sum := 0.0
for i := range ds.Seqs {
m.resetState()
pZ, _ := m.forward(ds.Seqs[i])
yZ := ds.Labels[i]
sum += pZ * yZ
}
return sum
}
type lstmCache struct {
xs, hs, cs []*mat.Dense
@ -316,6 +351,8 @@ func (m *LSTM) train(dsTrain, dsVal Dataset, batchSize int, maxEpochs int, early
}
bestValMAE = math.Inf(1)
var baseLoss float64
calibrated := false
for epoch := 1; epoch <= maxEpochs; epoch++ {
idx := indices(len(dsTrain.Seqs))
totalLoss := 0.0
@ -333,7 +370,7 @@ func (m *LSTM) train(dsTrain, dsVal Dataset, batchSize int, maxEpochs int, early
n++
}
}
avgLoss := totalLoss / float64(max(1, n))
avgLoss := totalLoss / float64(maxInt(n, 1))
curVal := valMAE()
if epoch == 1 {
firstLoss = avgLoss
@ -344,8 +381,26 @@ func (m *LSTM) train(dsTrain, dsVal Dataset, batchSize int, maxEpochs int, early
}
epochsRun = epoch
log.Printf("epoca=%d avgLoss=%.6f firstLoss=%.6f valMAE=%.6f", epoch, avgLoss, firstLoss, curVal)
// Calibrazione: dopo la prima epoca, se anti-segnale, flippa output una volta
if epoch == 1 && !calibrated {
baseDS := dsVal
if len(baseDS.Seqs) == 0 {
baseDS = dsTrain
}
dir := m.directionSum(baseDS)
if dir < 0 {
log.Printf("calibrazione: segno inverso rilevato (directionSum=%.6f) → flip output", dir)
m.flipOutputSign()
dir2 := m.directionSum(baseDS)
log.Printf("dopo flip: directionSum=%.6f", dir2)
}
calibrated = true
}
if avgLoss <= baseLoss*earlyStopFrac {
log.Printf("early-stopping: avgLoss=%.6f soglia=%.6f (%.2f%% di firstLoss) epoca=%d", avgLoss, baseLoss*earlyStopFrac, earlyStopFrac*100, epoch)
log.Printf("early-stopping: avgLoss=%.6f soglia=%.6f (%.2f%% di firstLoss) epoca=%d",
avgLoss, baseLoss*earlyStopFrac, earlyStopFrac*100, epoch)
break
}
}