money/dataset.go

146 lines
3.4 KiB
Go

package main
import (
"fmt"
"math"
)
// ================== 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
Labels []float64
Mean float64
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
}
logp := make([]float64, len(prices))
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{}
}
X := make([][]float64, 0, N)
Y := make([]float64, 0, N)
for i := 0; i < N; i++ {
win := make([]float64, lookback)
copy(win, lr[i:i+lookback])
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)
for j := range X[i] {
zX[i][j] = (X[i][j] - mean) / std
}
}
zY := make([]float64, len(Y))
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
}
func flatten(x [][]float64) []float64 {
out := make([]float64, 0, len(x)*max(1, len(x[0])))
for _, r := range x {
out = append(out, r...)
}
return out
}
func meanStd(x []float64) (float64, float64) {
if len(x) == 0 {
return 0, 1
}
var m float64
for _, v := range x {
m += v
}
m /= float64(len(x))
var s float64
for _, v := range x {
d := v - m
s += d * d
}
s = math.Sqrt(s / float64(len(x)))
return m, s
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
// Istruzioni manuali + gestione stato (lock)
func printManualInstruction(fromLabel, toLabel string, amountFrom, expectedBps, feeBps, safetyBps float64) string {
fromS := assetShort(fromLabel)
toS := assetShort(toLabel)
maxFeeBps := expectedBps - safetyBps
if maxFeeBps < 0 {
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,
)
}