Correlazione legala al segno di binance corretta.
parent
0e42130508
commit
5b52cd031a
34
dataset.go
34
dataset.go
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
10
decision.go
10
decision.go
|
@ -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
61
rnn.go
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue