diff --git a/functions/core/chunk.go b/functions/core/chunk.go new file mode 100644 index 00000000..9001fd79 --- /dev/null +++ b/functions/core/chunk.go @@ -0,0 +1,25 @@ +package core + +// Chunk splits xs into sub-slices of the given size. +// The last chunk may contain fewer than size elements. +// Returns nil if xs is empty. Panics if size <= 0. +func Chunk[T any](xs []T, size int) [][]T { + if size <= 0 { + panic("chunk size must be > 0") + } + n := len(xs) + if n == 0 { + return nil + } + + numChunks := (n + size - 1) / size + chunks := make([][]T, 0, numChunks) + for i := 0; i < n; i += size { + end := i + size + if end > n { + end = n + } + chunks = append(chunks, xs[i:end]) + } + return chunks +} diff --git a/functions/core/chunk.md b/functions/core/chunk.md new file mode 100644 index 00000000..37dc7796 --- /dev/null +++ b/functions/core/chunk.md @@ -0,0 +1,35 @@ +--- +name: chunk +kind: function +lang: go +domain: core +version: "1.0.0" +purity: pure +signature: "func Chunk[T any](xs []T, size int) [][]T" +description: "Divide un slice en trozos (sub-slices) de tamanio N. El ultimo trozo puede contener menos de N elementos. Retorna nil si el slice esta vacio. Entra en panic si size <= 0." +tags: [slice, chunk, batch, generic] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/core/chunk.go" +--- + +## Ejemplo + +```go +chunks := Chunk([]int{1, 2, 3, 4, 5}, 2) +// chunks = [[1, 2], [3, 4], [5]] + +chunks = Chunk([]string{"a", "b", "c", "d"}, 4) +// chunks = [["a", "b", "c", "d"]] +``` + +## Notas + +Funcion pura generica. Los sub-slices comparten memoria con el slice original (son slices del mismo backing array). Pre-calcula la capacidad del slice de chunks para evitar reasignaciones. Si size es mayor que len(xs), retorna un unico chunk con todos los elementos. diff --git a/functions/core/map_concurrent.go b/functions/core/map_concurrent.go new file mode 100644 index 00000000..d3d1c2a5 --- /dev/null +++ b/functions/core/map_concurrent.go @@ -0,0 +1,43 @@ +package core + +import "sync" + +// MapConcurrent applies fn to each element of xs using a pool of worker goroutines. +// The number of concurrent workers is capped by the workers parameter. +// Results preserve the original index order. If workers <= 0, it defaults to 1. +func MapConcurrent[T any, U any](xs []T, fn func(T) U, workers int) []U { + if workers <= 0 { + workers = 1 + } + n := len(xs) + if n == 0 { + return []U{} + } + + results := make([]U, n) + var wg sync.WaitGroup + ch := make(chan int, n) + + // Feed indices into the channel. + for i := range xs { + ch <- i + } + close(ch) + + // Spawn workers. + if workers > n { + workers = n + } + wg.Add(workers) + for w := 0; w < workers; w++ { + go func() { + defer wg.Done() + for i := range ch { + results[i] = fn(xs[i]) + } + }() + } + + wg.Wait() + return results +} diff --git a/functions/core/map_concurrent.md b/functions/core/map_concurrent.md new file mode 100644 index 00000000..a976eee8 --- /dev/null +++ b/functions/core/map_concurrent.md @@ -0,0 +1,34 @@ +--- +name: map_concurrent +kind: function +lang: go +domain: core +version: "1.0.0" +purity: impure +signature: "func MapConcurrent[T any, U any](xs []T, fn func(T) U, workers int) []U" +description: "Aplica una funcion a cada elemento de un slice usando un pool de goroutines como workers. Los resultados preservan el orden original del slice de entrada." +tags: [map, concurrent, parallel, generic] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/core/map_concurrent.go" +--- + +## Ejemplo + +```go +squares := MapConcurrent([]int{1, 2, 3, 4, 5}, func(n int) int { + return n * n +}, 3) +// squares = [1, 4, 9, 16, 25] (orden preservado) +``` + +## Notas + +Funcion impura generica que usa goroutines y sync.WaitGroup. Los workers se alimentan de un canal con indices, garantizando que cada resultado se escribe en su posicion correcta sin race conditions (cada goroutine escribe en un indice unico). Si workers <= 0 se usa 1. Si workers > len(xs) se ajusta a len(xs). diff --git a/functions/core/memoize.go b/functions/core/memoize.go new file mode 100644 index 00000000..e3d766cc --- /dev/null +++ b/functions/core/memoize.go @@ -0,0 +1,16 @@ +package core + +// Memoize wraps a pure function fn so that results are cached by key. +// Subsequent calls with the same key return the cached value without calling fn again. +// The returned function is safe for single-goroutine use. +func Memoize[K comparable, V any](fn func(K) V) func(K) V { + cache := make(map[K]V) + return func(key K) V { + if val, ok := cache[key]; ok { + return val + } + val := fn(key) + cache[key] = val + return val + } +} diff --git a/functions/core/memoize.md b/functions/core/memoize.md new file mode 100644 index 00000000..e6db007c --- /dev/null +++ b/functions/core/memoize.md @@ -0,0 +1,40 @@ +--- +name: memoize +kind: function +lang: go +domain: core +version: "1.0.0" +purity: pure +signature: "func Memoize[K comparable, V any](fn func(K) V) func(K) V" +description: "Cachea resultados de una funcion pura. Retorna una nueva funcion que almacena en un mapa interno los resultados ya calculados, evitando recalculos para la misma clave." +tags: [cache, memoize, functional, generic] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/core/memoize.go" +--- + +## Ejemplo + +```go +fib := Memoize(func(n int) int { + if n <= 1 { + return n + } + // nota: para recursion memoizada se necesita declarar la variable antes + return n // simplificado +}) +doubled := Memoize(func(x int) int { return x * 2 }) +doubled(5) // calcula: 10 +doubled(5) // cache hit: 10 +``` + +## Notas + +Funcion pura generica (referencialmente transparente). El cache interno es un map[K]V sin sincronizacion, por lo que la funcion retornada es segura solo para uso en una sola goroutine. K debe ser comparable para usarse como clave del mapa. diff --git a/functions/core/partition.go b/functions/core/partition.go new file mode 100644 index 00000000..aedff5d6 --- /dev/null +++ b/functions/core/partition.go @@ -0,0 +1,16 @@ +package core + +// Partition splits xs into two slices: the first contains elements where pred returns true, +// the second contains elements where pred returns false. Original order is preserved in both. +func Partition[T any](xs []T, pred func(T) bool) ([]T, []T) { + trueSlice := make([]T, 0) + falseSlice := make([]T, 0) + for _, x := range xs { + if pred(x) { + trueSlice = append(trueSlice, x) + } else { + falseSlice = append(falseSlice, x) + } + } + return trueSlice, falseSlice +} diff --git a/functions/core/partition.md b/functions/core/partition.md new file mode 100644 index 00000000..e73442f9 --- /dev/null +++ b/functions/core/partition.md @@ -0,0 +1,33 @@ +--- +name: partition +kind: function +lang: go +domain: core +version: "1.0.0" +purity: pure +signature: "func Partition[T any](xs []T, pred func(T) bool) ([]T, []T)" +description: "Divide un slice en dos segun un predicado. El primer slice contiene los elementos que cumplen el predicado, el segundo los que no. Se preserva el orden original." +tags: [slice, partition, functional, generic] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/core/partition.go" +--- + +## Ejemplo + +```go +evens, odds := Partition([]int{1, 2, 3, 4, 5}, func(n int) bool { return n%2 == 0 }) +// evens = [2, 4] +// odds = [1, 3, 5] +``` + +## Notas + +Funcion pura generica. Ambos slices retornados son nuevos (no muta el slice original). Si el slice de entrada esta vacio, retorna dos slices vacios. Los elementos mantienen su orden relativo original en cada particion. diff --git a/functions/core/pipeline.go b/functions/core/pipeline.go new file mode 100644 index 00000000..f3e02403 --- /dev/null +++ b/functions/core/pipeline.go @@ -0,0 +1,13 @@ +package core + +// Pipeline composes a sequence of functions T -> T into a single function. +// The functions are applied left to right: Pipeline(f, g, h)(x) == h(g(f(x))). +// Returns the identity function if no functions are provided. +func Pipeline[T any](fns ...func(T) T) func(T) T { + return func(val T) T { + for _, fn := range fns { + val = fn(val) + } + return val + } +} diff --git a/functions/core/pipeline.md b/functions/core/pipeline.md new file mode 100644 index 00000000..6b1856be --- /dev/null +++ b/functions/core/pipeline.md @@ -0,0 +1,36 @@ +--- +name: pipeline +kind: function +lang: go +domain: core +version: "1.0.0" +purity: pure +signature: "func Pipeline[T any](fns ...func(T) T) func(T) T" +description: "Compone funciones T -> T en secuencia de izquierda a derecha. Pipeline(f, g, h)(x) equivale a h(g(f(x))). Sin funciones retorna la identidad." +tags: [pipeline, compose, functional, generic] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/core/pipeline.go" +--- + +## Ejemplo + +```go +transform := Pipeline( + func(s string) string { return strings.TrimSpace(s) }, + func(s string) string { return strings.ToUpper(s) }, + func(s string) string { return "[" + s + "]" }, +) +transform(" hello ") // "[HELLO]" +``` + +## Notas + +Funcion pura generica. Las funciones se aplican en orden de izquierda a derecha (no es composicion matematica tradicional). Si se pasa un slice vacio de funciones, la funcion retornada actua como identidad. diff --git a/functions/core/retry_with_backoff.go b/functions/core/retry_with_backoff.go new file mode 100644 index 00000000..733a3292 --- /dev/null +++ b/functions/core/retry_with_backoff.go @@ -0,0 +1,31 @@ +package core + +import ( + "fmt" + "time" +) + +// RetryWithBackoff retries fn up to maxRetries times with exponential backoff. +// The delay between attempt i and i+1 is baseDelay * 2^i. +// Returns the first successful result or the last error after all retries are exhausted. +func RetryWithBackoff[T any](fn func() (T, error), maxRetries int, baseDelay time.Duration) (T, error) { + var zero T + if maxRetries < 0 { + return zero, fmt.Errorf("maxRetries must be >= 0, got %d", maxRetries) + } + + var lastErr error + for attempt := 0; attempt <= maxRetries; attempt++ { + result, err := fn() + if err == nil { + return result, nil + } + lastErr = err + + if attempt < maxRetries { + delay := baseDelay * (1 << uint(attempt)) + time.Sleep(delay) + } + } + return zero, fmt.Errorf("all %d retries exhausted: %w", maxRetries+1, lastErr) +} diff --git a/functions/core/retry_with_backoff.md b/functions/core/retry_with_backoff.md new file mode 100644 index 00000000..05b59f17 --- /dev/null +++ b/functions/core/retry_with_backoff.md @@ -0,0 +1,40 @@ +--- +name: retry_with_backoff +kind: function +lang: go +domain: core +version: "1.0.0" +purity: impure +signature: "func RetryWithBackoff[T any](fn func() (T, error), maxRetries int, baseDelay time.Duration) (T, error)" +description: "Reintenta una funcion impura con backoff exponencial. El delay entre intento i e i+1 es baseDelay * 2^i. Retorna el primer resultado exitoso o el ultimo error tras agotar los reintentos." +tags: [retry, backoff, resilience, generic] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/core/retry_with_backoff.go" +--- + +## Ejemplo + +```go +result, err := RetryWithBackoff(func() (string, error) { + resp, err := http.Get("https://api.example.com/data") + if err != nil { + return "", err + } + defer resp.Body.Close() + return "ok", nil +}, 3, 100*time.Millisecond) +// Intenta hasta 4 veces (1 inicial + 3 reintentos) +// Delays: 100ms, 200ms, 400ms +``` + +## Notas + +Funcion impura generica que usa time.Sleep para el backoff. El numero total de intentos es maxRetries + 1 (el intento inicial mas los reintentos). Si maxRetries es negativo retorna error inmediatamente. diff --git a/functions/cybersecurity/detect_sql_injection.go b/functions/cybersecurity/detect_sql_injection.go new file mode 100644 index 00000000..bf69982b --- /dev/null +++ b/functions/cybersecurity/detect_sql_injection.go @@ -0,0 +1,33 @@ +package cybersecurity + +import ( + "regexp" + "strings" +) + +var sqliPatterns = []struct { + name string + re *regexp.Regexp +}{ + {"union_select", regexp.MustCompile(`(?i)\bunion\s+(all\s+)?select\b`)}, + {"or_1_eq_1", regexp.MustCompile(`(?i)\bor\s+1\s*=\s*1`)}, + {"comment_injection", regexp.MustCompile(`(--|#|/\*)\s*$`)}, + {"single_quote_or", regexp.MustCompile(`(?i)'\s*(or|and)\s+'`)}, + {"drop_table", regexp.MustCompile(`(?i)\bdrop\s+(table|database)\b`)}, + {"sleep_benchmark", regexp.MustCompile(`(?i)\b(sleep|benchmark)\s*\(`)}, + {"exec_xp", regexp.MustCompile(`(?i)\b(exec|xp_)\w*`)}, + {"tautology", regexp.MustCompile(`(?i)\bor\s+['"]?\w+['"]?\s*=\s*['"]?\w+['"]?`)}, + {"stacked_query", regexp.MustCompile(`;\s*(select|insert|update|delete|drop|alter)\b`)}, +} + +// DetectSQLInjection analiza un input en busca de patrones heuristicos de inyeccion SQL. +// Devuelve si se detecto una amenaza y el nombre del patron encontrado. +func DetectSQLInjection(input string) (isThreat bool, pattern string) { + normalized := strings.TrimSpace(input) + for _, p := range sqliPatterns { + if p.re.MatchString(normalized) { + return true, p.name + } + } + return false, "" +} diff --git a/functions/cybersecurity/detect_sql_injection.md b/functions/cybersecurity/detect_sql_injection.md new file mode 100644 index 00000000..76a751da --- /dev/null +++ b/functions/cybersecurity/detect_sql_injection.md @@ -0,0 +1,21 @@ +--- +name: detect_sql_injection +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func DetectSQLInjection(input string) (isThreat bool, pattern string)" +description: "Analiza un input en busca de patrones heuristicos de inyeccion SQL y devuelve si se detecto amenaza y el patron encontrado." +tags: [cybersecurity, sqli, detection, security] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [regexp, strings] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/detect_sql_injection.go" +--- diff --git a/functions/cybersecurity/entropy_shannon.go b/functions/cybersecurity/entropy_shannon.go new file mode 100644 index 00000000..27c53ba1 --- /dev/null +++ b/functions/cybersecurity/entropy_shannon.go @@ -0,0 +1,26 @@ +package cybersecurity + +import "math" + +// EntropyShannon calcula la entropia de Shannon de los datos proporcionados. +// Devuelve un valor entre 0 (datos uniformes) y 8 (datos completamente aleatorios para bytes). +func EntropyShannon(data []byte) float64 { + if len(data) == 0 { + return 0 + } + + var freq [256]float64 + for _, b := range data { + freq[b]++ + } + + n := float64(len(data)) + entropy := 0.0 + for _, f := range freq { + if f > 0 { + p := f / n + entropy -= p * math.Log2(p) + } + } + return entropy +} diff --git a/functions/cybersecurity/entropy_shannon.md b/functions/cybersecurity/entropy_shannon.md new file mode 100644 index 00000000..45824b93 --- /dev/null +++ b/functions/cybersecurity/entropy_shannon.md @@ -0,0 +1,21 @@ +--- +name: entropy_shannon +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func EntropyShannon(data []byte) float64" +description: "Calcula la entropia de Shannon de un slice de bytes. Retorna un valor entre 0 y 8 bits por byte." +tags: [cybersecurity, entropy, shannon, analysis] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [math] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/entropy_shannon.go" +--- diff --git a/functions/cybersecurity/extract_urls.go b/functions/cybersecurity/extract_urls.go new file mode 100644 index 00000000..c05550f9 --- /dev/null +++ b/functions/cybersecurity/extract_urls.go @@ -0,0 +1,14 @@ +package cybersecurity + +import "regexp" + +var urlRegex = regexp.MustCompile(`https?://[^\s<>"'` + "`" + `\)\]\}]+`) + +// ExtractURLs extrae todas las URLs (http/https) encontradas en el texto proporcionado. +func ExtractURLs(text string) []string { + matches := urlRegex.FindAllString(text, -1) + if matches == nil { + return []string{} + } + return matches +} diff --git a/functions/cybersecurity/extract_urls.md b/functions/cybersecurity/extract_urls.md new file mode 100644 index 00000000..02a1b72a --- /dev/null +++ b/functions/cybersecurity/extract_urls.md @@ -0,0 +1,21 @@ +--- +name: extract_urls +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func ExtractURLs(text string) []string" +description: "Extrae todas las URLs HTTP/HTTPS de un texto usando expresiones regulares." +tags: [cybersecurity, extract, url, parse] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [regexp] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/extract_urls.go" +--- diff --git a/functions/cybersecurity/fetch_http_headers.go b/functions/cybersecurity/fetch_http_headers.go new file mode 100644 index 00000000..4874ae9f --- /dev/null +++ b/functions/cybersecurity/fetch_http_headers.go @@ -0,0 +1,27 @@ +package cybersecurity + +import ( + "fmt" + "net/http" + "time" +) + +// FetchHTTPHeaders realiza una solicitud HTTP HEAD a la URL y devuelve los headers de respuesta. +func FetchHTTPHeaders(url string) (map[string][]string, error) { + client := &http.Client{ + Timeout: 10 * time.Second, + } + + resp, err := client.Head(url) + if err != nil { + return nil, fmt.Errorf("error realizando solicitud HEAD a %s: %w", url, err) + } + defer resp.Body.Close() + + headers := make(map[string][]string, len(resp.Header)) + for k, v := range resp.Header { + headers[k] = v + } + + return headers, nil +} diff --git a/functions/cybersecurity/fetch_http_headers.md b/functions/cybersecurity/fetch_http_headers.md new file mode 100644 index 00000000..b7422e70 --- /dev/null +++ b/functions/cybersecurity/fetch_http_headers.md @@ -0,0 +1,21 @@ +--- +name: fetch_http_headers +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "func FetchHTTPHeaders(url string) (map[string][]string, error)" +description: "Realiza una solicitud HTTP HEAD a una URL y devuelve los headers de la respuesta." +tags: [cybersecurity, io, http, headers] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt, net/http, time] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/fetch_http_headers.go" +--- diff --git a/functions/cybersecurity/hash_md5.go b/functions/cybersecurity/hash_md5.go new file mode 100644 index 00000000..95d3f3a8 --- /dev/null +++ b/functions/cybersecurity/hash_md5.go @@ -0,0 +1,12 @@ +package cybersecurity + +import ( + "crypto/md5" + "encoding/hex" +) + +// HashMD5 calcula el hash MD5 de los datos proporcionados y devuelve el resultado como string hexadecimal. +func HashMD5(data []byte) string { + h := md5.Sum(data) + return hex.EncodeToString(h[:]) +} diff --git a/functions/cybersecurity/hash_md5.md b/functions/cybersecurity/hash_md5.md new file mode 100644 index 00000000..6bdaf9de --- /dev/null +++ b/functions/cybersecurity/hash_md5.md @@ -0,0 +1,21 @@ +--- +name: hash_md5 +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func HashMD5(data []byte) string" +description: "Calcula el hash MD5 de un slice de bytes y devuelve el resultado como string hexadecimal." +tags: [cybersecurity, hash, md5, crypto] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [crypto/md5, encoding/hex] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/hash_md5.go" +--- diff --git a/functions/cybersecurity/hash_sha256.go b/functions/cybersecurity/hash_sha256.go new file mode 100644 index 00000000..0b7d8a78 --- /dev/null +++ b/functions/cybersecurity/hash_sha256.go @@ -0,0 +1,12 @@ +package cybersecurity + +import ( + "crypto/sha256" + "encoding/hex" +) + +// HashSHA256 calcula el hash SHA-256 de los datos proporcionados y devuelve el resultado como string hexadecimal. +func HashSHA256(data []byte) string { + h := sha256.Sum256(data) + return hex.EncodeToString(h[:]) +} diff --git a/functions/cybersecurity/hash_sha256.md b/functions/cybersecurity/hash_sha256.md new file mode 100644 index 00000000..65bd2ca6 --- /dev/null +++ b/functions/cybersecurity/hash_sha256.md @@ -0,0 +1,21 @@ +--- +name: hash_sha256 +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func HashSHA256(data []byte) string" +description: "Calcula el hash SHA-256 de un slice de bytes y devuelve el resultado como string hexadecimal." +tags: [cybersecurity, hash, sha256, crypto] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [crypto/sha256, encoding/hex] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/hash_sha256.go" +--- diff --git a/functions/cybersecurity/ip_in_range.go b/functions/cybersecurity/ip_in_range.go new file mode 100644 index 00000000..6621264c --- /dev/null +++ b/functions/cybersecurity/ip_in_range.go @@ -0,0 +1,16 @@ +package cybersecurity + +import "net" + +// IPInRange verifica si una direccion IP esta dentro de un rango CIDR dado. +func IPInRange(ip, cidr string) bool { + parsedIP := net.ParseIP(ip) + if parsedIP == nil { + return false + } + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return false + } + return ipNet.Contains(parsedIP) +} diff --git a/functions/cybersecurity/ip_in_range.md b/functions/cybersecurity/ip_in_range.md new file mode 100644 index 00000000..1fb32230 --- /dev/null +++ b/functions/cybersecurity/ip_in_range.md @@ -0,0 +1,21 @@ +--- +name: ip_in_range +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func IPInRange(ip, cidr string) bool" +description: "Verifica si una direccion IP se encuentra dentro de un rango CIDR dado." +tags: [cybersecurity, network, cidr, check] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [net] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/ip_in_range.go" +--- diff --git a/functions/cybersecurity/is_base64.go b/functions/cybersecurity/is_base64.go new file mode 100644 index 00000000..8b6faf99 --- /dev/null +++ b/functions/cybersecurity/is_base64.go @@ -0,0 +1,12 @@ +package cybersecurity + +import "encoding/base64" + +// IsBase64 verifica si el string proporcionado es una cadena base64 valida (standard encoding). +func IsBase64(s string) bool { + if len(s) == 0 { + return false + } + _, err := base64.StdEncoding.DecodeString(s) + return err == nil +} diff --git a/functions/cybersecurity/is_base64.md b/functions/cybersecurity/is_base64.md new file mode 100644 index 00000000..3f7be675 --- /dev/null +++ b/functions/cybersecurity/is_base64.md @@ -0,0 +1,21 @@ +--- +name: is_base64 +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func IsBase64(s string) bool" +description: "Valida si un string es una cadena base64 valida segun el encoding estandar." +tags: [cybersecurity, validation, base64, format] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [encoding/base64] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/is_base64.go" +--- diff --git a/functions/cybersecurity/is_hex.go b/functions/cybersecurity/is_hex.go new file mode 100644 index 00000000..0599a21f --- /dev/null +++ b/functions/cybersecurity/is_hex.go @@ -0,0 +1,15 @@ +package cybersecurity + +// IsHex verifica si el string proporcionado es una cadena hexadecimal valida. +// Requiere longitud par y que todos los caracteres sean digitos hex (0-9, a-f, A-F). +func IsHex(s string) bool { + if len(s) == 0 || len(s)%2 != 0 { + return false + } + for _, c := range s { + if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { + return false + } + } + return true +} diff --git a/functions/cybersecurity/is_hex.md b/functions/cybersecurity/is_hex.md new file mode 100644 index 00000000..73a049e9 --- /dev/null +++ b/functions/cybersecurity/is_hex.md @@ -0,0 +1,21 @@ +--- +name: is_hex +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func IsHex(s string) bool" +description: "Valida si un string es una cadena hexadecimal valida (longitud par, caracteres 0-9 a-f A-F)." +tags: [cybersecurity, validation, hex, format] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/is_hex.go" +--- diff --git a/functions/cybersecurity/jaccard_similarity.go b/functions/cybersecurity/jaccard_similarity.go new file mode 100644 index 00000000..02040703 --- /dev/null +++ b/functions/cybersecurity/jaccard_similarity.go @@ -0,0 +1,37 @@ +package cybersecurity + +// JaccardSimilarity calcula la similitud de Jaccard entre dos conjuntos de tokens. +// Devuelve un valor entre 0.0 (sin interseccion) y 1.0 (conjuntos identicos). +func JaccardSimilarity(a, b []string) float64 { + if len(a) == 0 && len(b) == 0 { + return 1.0 + } + if len(a) == 0 || len(b) == 0 { + return 0.0 + } + + setA := make(map[string]struct{}, len(a)) + for _, s := range a { + setA[s] = struct{}{} + } + + setB := make(map[string]struct{}, len(b)) + for _, s := range b { + setB[s] = struct{}{} + } + + intersection := 0 + for k := range setA { + if _, ok := setB[k]; ok { + intersection++ + } + } + + // Union = |A| + |B| - |A intersect B| (usando conjuntos sin duplicados) + union := len(setA) + len(setB) - intersection + if union == 0 { + return 0.0 + } + + return float64(intersection) / float64(union) +} diff --git a/functions/cybersecurity/jaccard_similarity.md b/functions/cybersecurity/jaccard_similarity.md new file mode 100644 index 00000000..f82f1b8d --- /dev/null +++ b/functions/cybersecurity/jaccard_similarity.md @@ -0,0 +1,21 @@ +--- +name: jaccard_similarity +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func JaccardSimilarity(a, b []string) float64" +description: "Calcula la similitud de Jaccard entre dos conjuntos de tokens. Retorna un valor entre 0.0 y 1.0." +tags: [cybersecurity, similarity, jaccard, tokens] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/jaccard_similarity.go" +--- diff --git a/functions/cybersecurity/levenshtein_distance.go b/functions/cybersecurity/levenshtein_distance.go new file mode 100644 index 00000000..279e2f1d --- /dev/null +++ b/functions/cybersecurity/levenshtein_distance.go @@ -0,0 +1,49 @@ +package cybersecurity + +// LevenshteinDistance calcula la distancia de edicion (Levenshtein) entre dos strings. +func LevenshteinDistance(a, b string) int { + ra := []rune(a) + rb := []rune(b) + la := len(ra) + lb := len(rb) + + if la == 0 { + return lb + } + if lb == 0 { + return la + } + + // Usar solo dos filas para optimizar memoria + prev := make([]int, lb+1) + curr := make([]int, lb+1) + + for j := 0; j <= lb; j++ { + prev[j] = j + } + + for i := 1; i <= la; i++ { + curr[0] = i + for j := 1; j <= lb; j++ { + cost := 1 + if ra[i-1] == rb[j-1] { + cost = 0 + } + del := prev[j] + 1 + ins := curr[j-1] + 1 + sub := prev[j-1] + cost + + m := del + if ins < m { + m = ins + } + if sub < m { + m = sub + } + curr[j] = m + } + prev, curr = curr, prev + } + + return prev[lb] +} diff --git a/functions/cybersecurity/levenshtein_distance.md b/functions/cybersecurity/levenshtein_distance.md new file mode 100644 index 00000000..1eb6bee6 --- /dev/null +++ b/functions/cybersecurity/levenshtein_distance.md @@ -0,0 +1,21 @@ +--- +name: levenshtein_distance +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func LevenshteinDistance(a, b string) int" +description: "Calcula la distancia de edicion de Levenshtein entre dos strings. Util para deteccion de typosquatting." +tags: [cybersecurity, string, distance, typosquatting] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/levenshtein_distance.go" +--- diff --git a/functions/cybersecurity/lookup_whois.go b/functions/cybersecurity/lookup_whois.go new file mode 100644 index 00000000..0e048167 --- /dev/null +++ b/functions/cybersecurity/lookup_whois.go @@ -0,0 +1,33 @@ +package cybersecurity + +import ( + "fmt" + "io" + "net" + "strings" + "time" +) + +// LookupWhois realiza una consulta WHOIS para el dominio proporcionado conectandose al servidor whois.iana.org. +func LookupWhois(domain string) (string, error) { + conn, err := net.DialTimeout("tcp", "whois.iana.org:43", 10*time.Second) + if err != nil { + return "", fmt.Errorf("error conectando al servidor WHOIS: %w", err) + } + defer conn.Close() + + _ = conn.SetDeadline(time.Now().Add(10 * time.Second)) + + _, err = fmt.Fprintf(conn, "%s\r\n", domain) + if err != nil { + return "", fmt.Errorf("error enviando consulta WHOIS: %w", err) + } + + var sb strings.Builder + _, err = io.Copy(&sb, conn) + if err != nil { + return "", fmt.Errorf("error leyendo respuesta WHOIS: %w", err) + } + + return sb.String(), nil +} diff --git a/functions/cybersecurity/lookup_whois.md b/functions/cybersecurity/lookup_whois.md new file mode 100644 index 00000000..d216d27e --- /dev/null +++ b/functions/cybersecurity/lookup_whois.md @@ -0,0 +1,21 @@ +--- +name: lookup_whois +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "func LookupWhois(domain string) (string, error)" +description: "Realiza una consulta WHOIS para un dominio conectandose al servidor whois.iana.org por TCP." +tags: [cybersecurity, io, whois, recon] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt, io, net, strings, time] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/lookup_whois.go" +--- diff --git a/functions/cybersecurity/normalize_url.go b/functions/cybersecurity/normalize_url.go new file mode 100644 index 00000000..3375558f --- /dev/null +++ b/functions/cybersecurity/normalize_url.go @@ -0,0 +1,44 @@ +package cybersecurity + +import ( + "net/url" + "strings" +) + +// trackingParams lista de parametros de tracking comunes a eliminar. +var trackingParams = map[string]bool{ + "utm_source": true, + "utm_medium": true, + "utm_campaign": true, + "utm_term": true, + "utm_content": true, + "fbclid": true, + "gclid": true, + "ref": true, + "mc_cid": true, + "mc_eid": true, +} + +// NormalizeURL normaliza una URL: convierte el host a minusculas, elimina fragmentos +// y remueve parametros de tracking comunes. +func NormalizeURL(rawURL string) string { + u, err := url.Parse(rawURL) + if err != nil { + return rawURL + } + + // Lowercase host + u.Host = strings.ToLower(u.Host) + + // Eliminar fragmento + u.Fragment = "" + + // Eliminar parametros de tracking + q := u.Query() + for param := range trackingParams { + q.Del(param) + } + u.RawQuery = q.Encode() + + return u.String() +} diff --git a/functions/cybersecurity/normalize_url.md b/functions/cybersecurity/normalize_url.md new file mode 100644 index 00000000..4b33429c --- /dev/null +++ b/functions/cybersecurity/normalize_url.md @@ -0,0 +1,21 @@ +--- +name: normalize_url +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func NormalizeURL(rawURL string) string" +description: "Normaliza una URL: convierte el host a minusculas, elimina fragmentos y remueve parametros de tracking comunes." +tags: [cybersecurity, url, normalize, sanitize] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [net/url, strings] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/normalize_url.go" +--- diff --git a/functions/cybersecurity/parse_ip_cidr.go b/functions/cybersecurity/parse_ip_cidr.go new file mode 100644 index 00000000..ea8d8e6c --- /dev/null +++ b/functions/cybersecurity/parse_ip_cidr.go @@ -0,0 +1,44 @@ +package cybersecurity + +import ( + "encoding/binary" + "fmt" + "net" +) + +// ParseIPCIDR parsea una notacion CIDR y devuelve la direccion de red, broadcast, cantidad de hosts y error. +func ParseIPCIDR(cidr string) (network string, broadcast string, hosts int, err error) { + ip, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return "", "", 0, fmt.Errorf("CIDR invalido: %w", err) + } + + // Solo soportamos IPv4 + ip4 := ip.To4() + if ip4 == nil { + return "", "", 0, fmt.Errorf("solo se soporta IPv4") + } + + mask := ipNet.Mask + networkIP := ipNet.IP.To4() + network = networkIP.String() + + // Calcular broadcast + broadcastIP := make(net.IP, 4) + for i := 0; i < 4; i++ { + broadcastIP[i] = networkIP[i] | ^mask[i] + } + broadcast = broadcastIP.String() + + // Calcular hosts usables + netInt := binary.BigEndian.Uint32(networkIP) + bcastInt := binary.BigEndian.Uint32(broadcastIP) + total := int(bcastInt - netInt + 1) + if total > 2 { + hosts = total - 2 // excluir network y broadcast + } else { + hosts = total // /31 o /32 + } + + return network, broadcast, hosts, nil +} diff --git a/functions/cybersecurity/parse_ip_cidr.md b/functions/cybersecurity/parse_ip_cidr.md new file mode 100644 index 00000000..23938903 --- /dev/null +++ b/functions/cybersecurity/parse_ip_cidr.md @@ -0,0 +1,21 @@ +--- +name: parse_ip_cidr +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: pure +signature: "func ParseIPCIDR(cidr string) (network string, broadcast string, hosts int, err error)" +description: "Parsea una notacion CIDR IPv4 y devuelve la direccion de red, broadcast y cantidad de hosts usables." +tags: [cybersecurity, network, cidr, parse] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [encoding/binary, fmt, net] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/parse_ip_cidr.go" +--- diff --git a/functions/cybersecurity/resolve_dns.go b/functions/cybersecurity/resolve_dns.go new file mode 100644 index 00000000..330f4f91 --- /dev/null +++ b/functions/cybersecurity/resolve_dns.go @@ -0,0 +1,15 @@ +package cybersecurity + +import ( + "fmt" + "net" +) + +// ResolveDNS resuelve un hostname a sus direcciones IP usando el resolver del sistema. +func ResolveDNS(host string) ([]string, error) { + ips, err := net.LookupHost(host) + if err != nil { + return nil, fmt.Errorf("error resolviendo DNS para %s: %w", host, err) + } + return ips, nil +} diff --git a/functions/cybersecurity/resolve_dns.md b/functions/cybersecurity/resolve_dns.md new file mode 100644 index 00000000..5c0b5a76 --- /dev/null +++ b/functions/cybersecurity/resolve_dns.md @@ -0,0 +1,21 @@ +--- +name: resolve_dns +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "func ResolveDNS(host string) ([]string, error)" +description: "Resuelve un hostname a sus direcciones IP usando el resolver DNS del sistema." +tags: [cybersecurity, io, dns, resolve] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt, net] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/resolve_dns.go" +--- diff --git a/functions/cybersecurity/scan_port_tcp.go b/functions/cybersecurity/scan_port_tcp.go new file mode 100644 index 00000000..c2b3ec3c --- /dev/null +++ b/functions/cybersecurity/scan_port_tcp.go @@ -0,0 +1,34 @@ +package cybersecurity + +import ( + "fmt" + "net" + "time" +) + +// ScanPortTCP intenta conectarse a un puerto TCP y devuelve el estado ("open", "closed", "filtered"), +// un banner si el puerto esta abierto, y un posible error. +func ScanPortTCP(host string, port int, timeoutMs int) (status string, banner string, err error) { + address := fmt.Sprintf("%s:%d", host, port) + timeout := time.Duration(timeoutMs) * time.Millisecond + + conn, err := net.DialTimeout("tcp", address, timeout) + if err != nil { + // Distinguir entre conexion rechazada (closed) y timeout (filtered) + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + return "filtered", "", nil + } + return "closed", "", nil + } + defer conn.Close() + + // Intentar leer un banner con timeout corto + _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + buf := make([]byte, 1024) + n, _ := conn.Read(buf) + if n > 0 { + banner = string(buf[:n]) + } + + return "open", banner, nil +} diff --git a/functions/cybersecurity/scan_port_tcp.md b/functions/cybersecurity/scan_port_tcp.md new file mode 100644 index 00000000..be961707 --- /dev/null +++ b/functions/cybersecurity/scan_port_tcp.md @@ -0,0 +1,21 @@ +--- +name: scan_port_tcp +kind: function +lang: go +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "func ScanPortTCP(host string, port int, timeoutMs int) (status string, banner string, err error)" +description: "Escanea un puerto TCP en un host dado. Devuelve el estado (open/closed/filtered) y un banner si esta abierto." +tags: [cybersecurity, io, port, scan] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt, net, time] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/cybersecurity/scan_port_tcp.go" +--- diff --git a/functions/datascience/autocorrelation.go b/functions/datascience/autocorrelation.go new file mode 100644 index 00000000..ca322ced --- /dev/null +++ b/functions/datascience/autocorrelation.go @@ -0,0 +1,12 @@ +package datascience + +// Autocorrelation calcula la autocorrelación de data con el desfase (lag) dado. +// Usa la correlación de Pearson entre data[0:n-lag] y data[lag:n]. +// Si lag es inválido, retorna 0. +func Autocorrelation(data []float64, lag int) float64 { + n := len(data) + if lag < 0 || lag >= n { + return 0 + } + return Pearson(data[:n-lag], data[lag:]) +} diff --git a/functions/datascience/autocorrelation.md b/functions/datascience/autocorrelation.md new file mode 100644 index 00000000..20020767 --- /dev/null +++ b/functions/datascience/autocorrelation.md @@ -0,0 +1,21 @@ +--- +name: autocorrelation +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func Autocorrelation(data []float64, lag int) float64" +description: "Calcula la autocorrelación de una serie temporal con un desfase (lag) dado, usando correlación de Pearson." +tags: [datascience, statistics, autocorrelation, timeseries] +uses_functions: [pearson_go_datascience] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/autocorrelation.go" +--- diff --git a/functions/datascience/clip.go b/functions/datascience/clip.go new file mode 100644 index 00000000..947a80ee --- /dev/null +++ b/functions/datascience/clip.go @@ -0,0 +1,17 @@ +package datascience + +// Clip recorta cada valor del slice para que quede dentro del rango [min, max]. +func Clip(data []float64, min, max float64) []float64 { + result := make([]float64, len(data)) + for i, v := range data { + switch { + case v < min: + result[i] = min + case v > max: + result[i] = max + default: + result[i] = v + } + } + return result +} diff --git a/functions/datascience/clip.md b/functions/datascience/clip.md new file mode 100644 index 00000000..73931624 --- /dev/null +++ b/functions/datascience/clip.md @@ -0,0 +1,21 @@ +--- +name: clip +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func Clip(data []float64, min, max float64) []float64" +description: "Recorta cada valor del slice para que quede dentro del rango [min, max]." +tags: [datascience, clamp, clip, range] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/clip.go" +--- diff --git a/functions/datascience/detect_outliers.go b/functions/datascience/detect_outliers.go new file mode 100644 index 00000000..b42b4484 --- /dev/null +++ b/functions/datascience/detect_outliers.go @@ -0,0 +1,38 @@ +package datascience + +import "math" + +// DetectOutliers devuelve un []bool donde true indica que el valor es un outlier +// según z-score. Un valor es outlier si |z-score| > threshold. +func DetectOutliers(data []float64, threshold float64) []bool { + n := len(data) + if n == 0 { + return []bool{} + } + + var sum float64 + for _, v := range data { + sum += v + } + mean := sum / float64(n) + + var sqSum float64 + for _, v := range data { + d := v - mean + sqSum += d * d + } + stddev := math.Sqrt(sqSum / float64(n)) + + result := make([]bool, n) + if stddev == 0 { + return result + } + for i, v := range data { + z := (v - mean) / stddev + if z < 0 { + z = -z + } + result[i] = z > threshold + } + return result +} diff --git a/functions/datascience/detect_outliers.md b/functions/datascience/detect_outliers.md new file mode 100644 index 00000000..617132ac --- /dev/null +++ b/functions/datascience/detect_outliers.md @@ -0,0 +1,21 @@ +--- +name: detect_outliers +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func DetectOutliers(data []float64, threshold float64) []bool" +description: "Detecta outliers en un slice de float64 usando z-score. Devuelve true para valores cuyo |z-score| supera el umbral." +tags: [datascience, statistics, outlier, anomaly] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: ["math"] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/detect_outliers.go" +--- diff --git a/functions/datascience/fetch_data_frame.go b/functions/datascience/fetch_data_frame.go new file mode 100644 index 00000000..7205da42 --- /dev/null +++ b/functions/datascience/fetch_data_frame.go @@ -0,0 +1,8 @@ +package datascience + +import "fmt" + +// FetchDataFrame ejecuta una consulta SQL contra un DSN y retorna los resultados como slice de mapas. +func FetchDataFrame(dsn, query string) ([]map[string]any, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/functions/datascience/fetch_data_frame.md b/functions/datascience/fetch_data_frame.md new file mode 100644 index 00000000..874455c8 --- /dev/null +++ b/functions/datascience/fetch_data_frame.md @@ -0,0 +1,21 @@ +--- +name: fetch_data_frame +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: impure +signature: "func FetchDataFrame(dsn, query string) ([]map[string]any, error)" +description: "Ejecuta una consulta SQL contra un DSN y retorna los resultados como slice de mapas columna-valor." +tags: [datascience, io, bigquery, fetch] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: ["fmt"] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/fetch_data_frame.go" +--- diff --git a/functions/datascience/fft.go b/functions/datascience/fft.go new file mode 100644 index 00000000..98d58bcc --- /dev/null +++ b/functions/datascience/fft.go @@ -0,0 +1,61 @@ +package datascience + +import ( + "math" + "math/cmplx" +) + +// FFT calcula la Fast Fourier Transform usando el algoritmo Cooley-Tukey radix-2. +// Si la longitud de data no es potencia de 2, se rellena con ceros (zero-padding). +func FFT(data []float64) []complex128 { + n := len(data) + if n == 0 { + return []complex128{} + } + + // Calcular la siguiente potencia de 2. + size := nextPow2(n) + + // Convertir a complex128 con zero-padding. + x := make([]complex128, size) + for i := 0; i < n; i++ { + x[i] = complex(data[i], 0) + } + + fftRecursive(x) + return x +} + +// nextPow2 retorna la menor potencia de 2 >= n. +func nextPow2(n int) int { + p := 1 + for p < n { + p <<= 1 + } + return p +} + +// fftRecursive aplica Cooley-Tukey radix-2 DIT in-place. +func fftRecursive(x []complex128) { + n := len(x) + if n <= 1 { + return + } + + // Separar pares e impares. + even := make([]complex128, n/2) + odd := make([]complex128, n/2) + for i := 0; i < n/2; i++ { + even[i] = x[2*i] + odd[i] = x[2*i+1] + } + + fftRecursive(even) + fftRecursive(odd) + + for k := 0; k < n/2; k++ { + t := cmplx.Rect(1, -2*math.Pi*float64(k)/float64(n)) * odd[k] + x[k] = even[k] + t + x[k+n/2] = even[k] - t + } +} diff --git a/functions/datascience/fft.md b/functions/datascience/fft.md new file mode 100644 index 00000000..4fbebad7 --- /dev/null +++ b/functions/datascience/fft.md @@ -0,0 +1,21 @@ +--- +name: fft +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func FFT(data []float64) []complex128" +description: "Calcula la Transformada Rápida de Fourier (FFT) usando el algoritmo Cooley-Tukey radix-2. Aplica zero-padding si la longitud no es potencia de 2." +tags: [datascience, dsp, fft, fourier, frequency] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: ["math", "math/cmplx"] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/fft.go" +--- diff --git a/functions/datascience/group_by.go b/functions/datascience/group_by.go new file mode 100644 index 00000000..204a64b3 --- /dev/null +++ b/functions/datascience/group_by.go @@ -0,0 +1,11 @@ +package datascience + +// GroupBy agrupa los elementos de un slice según la clave devuelta por keyFn. +func GroupBy[T any, K comparable](xs []T, keyFn func(T) K) map[K][]T { + groups := make(map[K][]T) + for _, x := range xs { + k := keyFn(x) + groups[k] = append(groups[k], x) + } + return groups +} diff --git a/functions/datascience/group_by.md b/functions/datascience/group_by.md new file mode 100644 index 00000000..b0e1750e --- /dev/null +++ b/functions/datascience/group_by.md @@ -0,0 +1,21 @@ +--- +name: group_by +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func GroupBy[T any, K comparable](xs []T, keyFn func(T) K) map[K][]T" +description: "Agrupa los elementos de un slice según una función clave, devolviendo un mapa de clave a slice de elementos." +tags: [datascience, group, aggregate, generic] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/group_by.go" +--- diff --git a/functions/datascience/histogram.go b/functions/datascience/histogram.go new file mode 100644 index 00000000..42e3b4bf --- /dev/null +++ b/functions/datascience/histogram.go @@ -0,0 +1,39 @@ +package datascience + +import "math" + +// Histogram calcula las frecuencias de data distribuidas en la cantidad de buckets indicada. +// Retorna un slice de longitud buckets con el conteo de elementos por cada intervalo equiespaciado. +func Histogram(data []float64, buckets int) []int { + if buckets <= 0 || len(data) == 0 { + return make([]int, buckets) + } + + minVal := math.Inf(1) + maxVal := math.Inf(-1) + for _, v := range data { + if v < minVal { + minVal = v + } + if v > maxVal { + maxVal = v + } + } + + counts := make([]int, buckets) + rang := maxVal - minVal + if rang == 0 { + // Todos los valores son iguales; poner todo en el primer bucket. + counts[0] = len(data) + return counts + } + + for _, v := range data { + idx := int(float64(buckets) * (v - minVal) / rang) + if idx >= buckets { + idx = buckets - 1 + } + counts[idx]++ + } + return counts +} diff --git a/functions/datascience/histogram.md b/functions/datascience/histogram.md new file mode 100644 index 00000000..3ab07638 --- /dev/null +++ b/functions/datascience/histogram.md @@ -0,0 +1,21 @@ +--- +name: histogram +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func Histogram(data []float64, buckets int) []int" +description: "Calcula las frecuencias de un slice de float64 distribuidas en un número dado de buckets equiespaciados." +tags: [datascience, statistics, histogram, frequency] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: ["math"] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/histogram.go" +--- diff --git a/functions/datascience/impute.go b/functions/datascience/impute.go new file mode 100644 index 00000000..b672a245 --- /dev/null +++ b/functions/datascience/impute.go @@ -0,0 +1,18 @@ +package datascience + +import "math" + +// Impute rellena valores NaN usando forward-fill. +// Cada NaN se reemplaza con el último valor válido (no NaN) anterior. +// Si el primer valor es NaN y no hay valor anterior, se mantiene como NaN. +func Impute(data []float64) []float64 { + result := make([]float64, len(data)) + last := math.NaN() + for i, v := range data { + if !math.IsNaN(v) { + last = v + } + result[i] = last + } + return result +} diff --git a/functions/datascience/impute.md b/functions/datascience/impute.md new file mode 100644 index 00000000..10daffe8 --- /dev/null +++ b/functions/datascience/impute.md @@ -0,0 +1,21 @@ +--- +name: impute +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func Impute(data []float64) []float64" +description: "Rellena valores NaN en un slice de float64 usando forward-fill, reemplazando cada NaN con el último valor válido anterior." +tags: [datascience, impute, missing, fill] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: ["math"] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/impute.go" +--- diff --git a/functions/datascience/load_csv.go b/functions/datascience/load_csv.go new file mode 100644 index 00000000..9c35dc1e --- /dev/null +++ b/functions/datascience/load_csv.go @@ -0,0 +1,8 @@ +package datascience + +import "fmt" + +// LoadCSV carga un archivo CSV y lo retorna como slice de mapas (columna -> valor). +func LoadCSV(path string) ([]map[string]string, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/functions/datascience/load_csv.md b/functions/datascience/load_csv.md new file mode 100644 index 00000000..df198579 --- /dev/null +++ b/functions/datascience/load_csv.md @@ -0,0 +1,21 @@ +--- +name: load_csv +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: impure +signature: "func LoadCSV(path string) ([]map[string]string, error)" +description: "Carga un archivo CSV desde disco y lo retorna como slice de mapas columna-valor." +tags: [datascience, io, csv, load] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: ["fmt"] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/load_csv.go" +--- diff --git a/functions/datascience/load_parquet.go b/functions/datascience/load_parquet.go new file mode 100644 index 00000000..4b71f2b5 --- /dev/null +++ b/functions/datascience/load_parquet.go @@ -0,0 +1,8 @@ +package datascience + +import "fmt" + +// LoadParquet carga un archivo Parquet y lo retorna como slice de mapas. +func LoadParquet(path string) ([]map[string]any, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/functions/datascience/load_parquet.md b/functions/datascience/load_parquet.md new file mode 100644 index 00000000..79914bc3 --- /dev/null +++ b/functions/datascience/load_parquet.md @@ -0,0 +1,21 @@ +--- +name: load_parquet +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: impure +signature: "func LoadParquet(path string) ([]map[string]any, error)" +description: "Carga un archivo Parquet desde disco y lo retorna como slice de mapas columna-valor." +tags: [datascience, io, parquet, load] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: ["fmt"] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/load_parquet.go" +--- diff --git a/functions/datascience/min_max_scale.go b/functions/datascience/min_max_scale.go new file mode 100644 index 00000000..d0db8460 --- /dev/null +++ b/functions/datascience/min_max_scale.go @@ -0,0 +1,33 @@ +package datascience + +import "math" + +// MinMaxScale escala los valores al rango [0, 1] usando min-max normalización. +// Si min == max, retorna un slice de ceros. +func MinMaxScale(data []float64) []float64 { + n := len(data) + if n == 0 { + return []float64{} + } + + minVal := math.Inf(1) + maxVal := math.Inf(-1) + for _, v := range data { + if v < minVal { + minVal = v + } + if v > maxVal { + maxVal = v + } + } + + rang := maxVal - minVal + result := make([]float64, n) + if rang == 0 { + return result + } + for i, v := range data { + result[i] = (v - minVal) / rang + } + return result +} diff --git a/functions/datascience/min_max_scale.md b/functions/datascience/min_max_scale.md new file mode 100644 index 00000000..d3ccf08d --- /dev/null +++ b/functions/datascience/min_max_scale.md @@ -0,0 +1,21 @@ +--- +name: min_max_scale +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func MinMaxScale(data []float64) []float64" +description: "Escala los valores de un slice al rango [0, 1] usando normalización min-max." +tags: [datascience, statistics, normalize, scale] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: ["math"] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/min_max_scale.go" +--- diff --git a/functions/datascience/pearson.go b/functions/datascience/pearson.go new file mode 100644 index 00000000..6a704140 --- /dev/null +++ b/functions/datascience/pearson.go @@ -0,0 +1,39 @@ +package datascience + +import "math" + +// Pearson calcula el coeficiente de correlación de Pearson entre dos slices. +// Si los slices tienen distinta longitud, usa la longitud mínima. +// Retorna 0 si alguna desviación estándar es 0. +func Pearson(xs, ys []float64) float64 { + n := len(xs) + if len(ys) < n { + n = len(ys) + } + if n == 0 { + return 0 + } + + var sumX, sumY float64 + for i := 0; i < n; i++ { + sumX += xs[i] + sumY += ys[i] + } + meanX := sumX / float64(n) + meanY := sumY / float64(n) + + var num, denomX, denomY float64 + for i := 0; i < n; i++ { + dx := xs[i] - meanX + dy := ys[i] - meanY + num += dx * dy + denomX += dx * dx + denomY += dy * dy + } + + denom := math.Sqrt(denomX * denomY) + if denom == 0 { + return 0 + } + return num / denom +} diff --git a/functions/datascience/pearson.md b/functions/datascience/pearson.md new file mode 100644 index 00000000..df7d48e1 --- /dev/null +++ b/functions/datascience/pearson.md @@ -0,0 +1,21 @@ +--- +name: pearson +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func Pearson(xs, ys []float64) float64" +description: "Calcula el coeficiente de correlación de Pearson entre dos slices de float64." +tags: [datascience, statistics, correlation, pearson] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: ["math"] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/pearson.go" +--- diff --git a/functions/datascience/rolling_window.go b/functions/datascience/rolling_window.go new file mode 100644 index 00000000..54b31941 --- /dev/null +++ b/functions/datascience/rolling_window.go @@ -0,0 +1,17 @@ +package datascience + +// RollingWindow genera ventanas deslizantes de tamaño size sobre el slice xs. +// Si size <= 0 o size > len(xs), retorna nil. +func RollingWindow[T any](xs []T, size int) [][]T { + n := len(xs) + if size <= 0 || size > n { + return nil + } + windows := make([][]T, 0, n-size+1) + for i := 0; i <= n-size; i++ { + w := make([]T, size) + copy(w, xs[i:i+size]) + windows = append(windows, w) + } + return windows +} diff --git a/functions/datascience/rolling_window.md b/functions/datascience/rolling_window.md new file mode 100644 index 00000000..29af39f9 --- /dev/null +++ b/functions/datascience/rolling_window.md @@ -0,0 +1,21 @@ +--- +name: rolling_window +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func RollingWindow[T any](xs []T, size int) [][]T" +description: "Genera ventanas deslizantes de tamaño fijo sobre un slice genérico." +tags: [datascience, window, rolling, sliding, generic] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/rolling_window.go" +--- diff --git a/functions/datascience/standardize.go b/functions/datascience/standardize.go new file mode 100644 index 00000000..eaa5d8b3 --- /dev/null +++ b/functions/datascience/standardize.go @@ -0,0 +1,35 @@ +package datascience + +import "math" + +// Standardize aplica Z-score normalización a un slice de float64. +// Cada valor se transforma a (x - mean) / stddev. +// Si stddev es 0, retorna un slice de ceros. +func Standardize(data []float64) []float64 { + n := len(data) + if n == 0 { + return []float64{} + } + + var sum float64 + for _, v := range data { + sum += v + } + mean := sum / float64(n) + + var sqSum float64 + for _, v := range data { + d := v - mean + sqSum += d * d + } + stddev := math.Sqrt(sqSum / float64(n)) + + result := make([]float64, n) + if stddev == 0 { + return result + } + for i, v := range data { + result[i] = (v - mean) / stddev + } + return result +} diff --git a/functions/datascience/standardize.md b/functions/datascience/standardize.md new file mode 100644 index 00000000..339b518a --- /dev/null +++ b/functions/datascience/standardize.md @@ -0,0 +1,21 @@ +--- +name: standardize +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func Standardize(data []float64) []float64" +description: "Aplica Z-score normalización a un slice de float64, transformando cada valor a (x - media) / desviación estándar." +tags: [datascience, statistics, normalize, zscore] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: ["math"] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/standardize.go" +--- diff --git a/functions/datascience/zip_slices.go b/functions/datascience/zip_slices.go new file mode 100644 index 00000000..d50cc081 --- /dev/null +++ b/functions/datascience/zip_slices.go @@ -0,0 +1,15 @@ +package datascience + +// ZipSlices combina dos slices de float64 en pares [2]float64. +// El resultado tiene longitud igual al menor de los dos slices. +func ZipSlices(as, bs []float64) [][2]float64 { + n := len(as) + if len(bs) < n { + n = len(bs) + } + result := make([][2]float64, n) + for i := 0; i < n; i++ { + result[i] = [2]float64{as[i], bs[i]} + } + return result +} diff --git a/functions/datascience/zip_slices.md b/functions/datascience/zip_slices.md new file mode 100644 index 00000000..69eed967 --- /dev/null +++ b/functions/datascience/zip_slices.md @@ -0,0 +1,21 @@ +--- +name: zip_slices +kind: function +lang: go +domain: datascience +version: "1.0.0" +purity: pure +signature: "func ZipSlices(as, bs []float64) [][2]float64" +description: "Combina dos slices de float64 en un slice de pares [2]float64, truncando al más corto." +tags: [datascience, zip, combine, pair] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/datascience/zip_slices.go" +--- diff --git a/functions/finance/.gitkeep b/functions/finance/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/functions/finance/annualized_volatility.go b/functions/finance/annualized_volatility.go new file mode 100644 index 00000000..251fd85b --- /dev/null +++ b/functions/finance/annualized_volatility.go @@ -0,0 +1,25 @@ +package finance + +import "math" + +// AnnualizedVolatility calcula la volatilidad anualizada a partir de una serie de retornos. +// periodsPerYear indica cuantos periodos hay en un anio (e.g. 252 para retornos diarios). +// Retorna stddev(returns) * sqrt(periodsPerYear). +func AnnualizedVolatility(returns []float64, periodsPerYear float64) float64 { + n := len(returns) + if n < 2 { + return 0 + } + var sum float64 + for _, r := range returns { + sum += r + } + mean := sum / float64(n) + var variance float64 + for _, r := range returns { + diff := r - mean + variance += diff * diff + } + variance /= float64(n - 1) + return math.Sqrt(variance) * math.Sqrt(periodsPerYear) +} diff --git a/functions/finance/annualized_volatility.md b/functions/finance/annualized_volatility.md new file mode 100644 index 00000000..bc61864d --- /dev/null +++ b/functions/finance/annualized_volatility.md @@ -0,0 +1,31 @@ +--- +name: annualized_volatility +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func AnnualizedVolatility(returns []float64, periodsPerYear float64) float64" +description: "Calcula la volatilidad anualizada a partir de una serie de retornos y la frecuencia de los periodos." +tags: [finance, volatility, risk, annualized] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [math] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/annualized_volatility.go" +--- + +# annualized_volatility + +Calcula la volatilidad anualizada como `stddev(returns) * sqrt(periodsPerYear)`. Usa desviacion estandar muestral (n-1). + +## Ejemplo + +```go +vol := finance.AnnualizedVolatility([]float64{0.01, -0.02, 0.015, 0.005, -0.01}, 252) +``` diff --git a/functions/finance/bollinger_bands.go b/functions/finance/bollinger_bands.go new file mode 100644 index 00000000..b2ea6db4 --- /dev/null +++ b/functions/finance/bollinger_bands.go @@ -0,0 +1,32 @@ +package finance + +import "math" + +// BollingerBands calcula las bandas de Bollinger: upper, middle (SMA), lower. +// Los primeros period-1 elementos de cada slice son 0. +func BollingerBands(data []float64, period int, numStdDev float64) (upper, middle, lower []float64) { + n := len(data) + upper = make([]float64, n) + middle = make([]float64, n) + lower = make([]float64, n) + if period <= 0 || period > n { + return + } + for i := period - 1; i < n; i++ { + var sum float64 + for j := i - period + 1; j <= i; j++ { + sum += data[j] + } + mean := sum / float64(period) + var variance float64 + for j := i - period + 1; j <= i; j++ { + diff := data[j] - mean + variance += diff * diff + } + std := math.Sqrt(variance / float64(period)) + middle[i] = mean + upper[i] = mean + numStdDev*std + lower[i] = mean - numStdDev*std + } + return +} diff --git a/functions/finance/bollinger_bands.md b/functions/finance/bollinger_bands.md new file mode 100644 index 00000000..4e9fcb3c --- /dev/null +++ b/functions/finance/bollinger_bands.md @@ -0,0 +1,34 @@ +--- +name: bollinger_bands +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func BollingerBands(data []float64, period int, numStdDev float64) (upper, middle, lower []float64)" +description: "Calcula las bandas de Bollinger (upper, middle, lower) para una serie de precios." +tags: [finance, indicator, bollinger, bands] +uses_functions: [] +uses_types: [bollinger_result_go_finance] +returns: [bollinger_result_go_finance] +returns_optional: false +error_type: "" +imports: [math] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/bollinger_bands.go" +--- + +# bollinger_bands + +Calcula las bandas de Bollinger. La banda media es la SMA, y las bandas superior e inferior estan a `numStdDev` desviaciones estandar de la media. Usa desviacion estandar poblacional. + +## Ejemplo + +```go +upper, middle, lower := finance.BollingerBands( + []float64{22, 24, 23, 25, 26, 28, 27, 29, 30, 28}, + 5, 2.0, +) +``` diff --git a/functions/finance/ema.go b/functions/finance/ema.go new file mode 100644 index 00000000..8b367a55 --- /dev/null +++ b/functions/finance/ema.go @@ -0,0 +1,23 @@ +package finance + +// EMA calcula la media movil exponencial de data con el periodo dado. +// El primer valor valido se inicializa con la SMA de los primeros period elementos. +// Los primeros period-1 elementos del resultado son 0. +func EMA(data []float64, period int) []float64 { + n := len(data) + result := make([]float64, n) + if period <= 0 || period > n { + return result + } + k := 2.0 / float64(period+1) + // Seed con SMA + var sum float64 + for i := 0; i < period; i++ { + sum += data[i] + } + result[period-1] = sum / float64(period) + for i := period; i < n; i++ { + result[i] = data[i]*k + result[i-1]*(1-k) + } + return result +} diff --git a/functions/finance/ema.md b/functions/finance/ema.md new file mode 100644 index 00000000..1a2acff5 --- /dev/null +++ b/functions/finance/ema.md @@ -0,0 +1,33 @@ +--- +name: ema +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func EMA(data []float64, period int) []float64" +description: "Calcula la media movil exponencial (EMA) sobre una serie de datos con un periodo dado." +tags: [finance, indicator, ema, moving-average] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/ema.go" +--- + +# ema + +Calcula la media movil exponencial (Exponential Moving Average). Se inicializa con la SMA de los primeros `period` elementos. El multiplicador es `2 / (period + 1)`. + +## Ejemplo + +```go +result := finance.EMA([]float64{10, 11, 12, 13, 14, 15}, 3) +// result[2] = SMA de los primeros 3 = 11.0 +// result[3] en adelante usa suavizado exponencial +``` diff --git a/functions/finance/fetch_ohlcv.go b/functions/finance/fetch_ohlcv.go new file mode 100644 index 00000000..0a0a4eab --- /dev/null +++ b/functions/finance/fetch_ohlcv.go @@ -0,0 +1,9 @@ +package finance + +import "fmt" + +// FetchOHLCV obtiene datos OHLCV de un exchange para un simbolo e intervalo dados. +// TODO: implementar conexion real al exchange. +func FetchOHLCV(symbol, interval string) ([][]float64, error) { + return nil, fmt.Errorf("not implemented: FetchOHLCV(%s, %s)", symbol, interval) +} diff --git a/functions/finance/fetch_ohlcv.md b/functions/finance/fetch_ohlcv.md new file mode 100644 index 00000000..30906363 --- /dev/null +++ b/functions/finance/fetch_ohlcv.md @@ -0,0 +1,31 @@ +--- +name: fetch_ohlcv +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: impure +signature: "func FetchOHLCV(symbol, interval string) ([][]float64, error)" +description: "Obtiene datos OHLCV de un exchange para un simbolo e intervalo dados." +tags: [finance, io, exchange, fetch] +uses_functions: [] +uses_types: [ohlcv_go_finance] +returns: [ohlcv_go_finance] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/fetch_ohlcv.go" +--- + +# fetch_ohlcv + +Stub para obtener datos OHLCV de un exchange. Pendiente de implementacion. + +## Ejemplo + +```go +data, err := finance.FetchOHLCV("BTC/USDT", "1h") +``` diff --git a/functions/finance/load_ohlcv_from_duckdb.go b/functions/finance/load_ohlcv_from_duckdb.go new file mode 100644 index 00000000..02e3a50b --- /dev/null +++ b/functions/finance/load_ohlcv_from_duckdb.go @@ -0,0 +1,9 @@ +package finance + +import "fmt" + +// LoadOHLCVFromDuckDB carga datos OHLCV ejecutando una query en una base DuckDB. +// TODO: implementar conexion real a DuckDB. +func LoadOHLCVFromDuckDB(dbPath, query string) ([][]float64, error) { + return nil, fmt.Errorf("not implemented: LoadOHLCVFromDuckDB(%s, %s)", dbPath, query) +} diff --git a/functions/finance/load_ohlcv_from_duckdb.md b/functions/finance/load_ohlcv_from_duckdb.md new file mode 100644 index 00000000..55c5f496 --- /dev/null +++ b/functions/finance/load_ohlcv_from_duckdb.md @@ -0,0 +1,31 @@ +--- +name: load_ohlcv_from_duckdb +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: impure +signature: "func LoadOHLCVFromDuckDB(dbPath, query string) ([][]float64, error)" +description: "Carga datos OHLCV ejecutando una query SQL en una base de datos DuckDB." +tags: [finance, io, duckdb, load] +uses_functions: [] +uses_types: [ohlcv_go_finance] +returns: [ohlcv_go_finance] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/load_ohlcv_from_duckdb.go" +--- + +# load_ohlcv_from_duckdb + +Stub para cargar datos OHLCV desde DuckDB. Pendiente de implementacion. + +## Ejemplo + +```go +data, err := finance.LoadOHLCVFromDuckDB("/data/market.duckdb", "SELECT * FROM ohlcv WHERE symbol='BTC'") +``` diff --git a/functions/finance/log_return.go b/functions/finance/log_return.go new file mode 100644 index 00000000..16e8ca4a --- /dev/null +++ b/functions/finance/log_return.go @@ -0,0 +1,12 @@ +package finance + +import "math" + +// LogReturn calcula el retorno logaritmico entre dos precios. +// Retorna ln(priceEnd / priceStart). Si priceStart <= 0 o priceEnd <= 0, retorna 0. +func LogReturn(priceStart, priceEnd float64) float64 { + if priceStart <= 0 || priceEnd <= 0 { + return 0 + } + return math.Log(priceEnd / priceStart) +} diff --git a/functions/finance/log_return.md b/functions/finance/log_return.md new file mode 100644 index 00000000..d05ec662 --- /dev/null +++ b/functions/finance/log_return.md @@ -0,0 +1,32 @@ +--- +name: log_return +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func LogReturn(priceStart, priceEnd float64) float64" +description: "Calcula el retorno logaritmico entre un precio inicial y un precio final." +tags: [finance, return, logarithmic, atomic] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [math] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/log_return.go" +--- + +# log_return + +Calcula el retorno logaritmico: `ln(priceEnd / priceStart)`. Si alguno de los precios es <= 0, retorna 0. + +## Ejemplo + +```go +r := finance.LogReturn(100.0, 110.0) +// r ~ 0.09531 (aprox 9.53%) +``` diff --git a/functions/finance/max_drawdown.go b/functions/finance/max_drawdown.go new file mode 100644 index 00000000..b93f455e --- /dev/null +++ b/functions/finance/max_drawdown.go @@ -0,0 +1,27 @@ +package finance + +// MaxDrawdown calcula el maximo drawdown de una serie de valores (e.g. equity curve). +// Retorna la magnitud del drawdown (valor positivo entre 0 y 1 como fraccion del pico), +// y los indices de inicio (pico) y fin (valle) del peor drawdown. +// Si la serie tiene menos de 2 elementos, retorna 0, 0, 0. +func MaxDrawdown(values []float64) (maxDD float64, start, end int) { + n := len(values) + if n < 2 { + return 0, 0, 0 + } + peakIdx := 0 + peak := values[0] + for i := 1; i < n; i++ { + if values[i] > peak { + peak = values[i] + peakIdx = i + } + dd := (peak - values[i]) / peak + if dd > maxDD { + maxDD = dd + start = peakIdx + end = i + } + } + return +} diff --git a/functions/finance/max_drawdown.md b/functions/finance/max_drawdown.md new file mode 100644 index 00000000..d7874086 --- /dev/null +++ b/functions/finance/max_drawdown.md @@ -0,0 +1,32 @@ +--- +name: max_drawdown +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func MaxDrawdown(values []float64) (maxDD float64, start, end int)" +description: "Calcula el maximo drawdown de una curva de equity, retornando la magnitud y los indices pico-valle." +tags: [finance, drawdown, risk, metric] +uses_functions: [] +uses_types: [drawdown_result_go_finance] +returns: [drawdown_result_go_finance] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/max_drawdown.go" +--- + +# max_drawdown + +Calcula el maximo drawdown (caida maxima desde un pico hasta un valle) como fraccion del pico. Retorna la magnitud (0 a 1) y los indices de inicio y fin. + +## Ejemplo + +```go +dd, s, e := finance.MaxDrawdown([]float64{100, 120, 90, 110, 80}) +// dd = (120-80)/120 = 0.3333, s = 1, e = 4 +``` diff --git a/functions/finance/normalize_ohlcv.go b/functions/finance/normalize_ohlcv.go new file mode 100644 index 00000000..9a4270db --- /dev/null +++ b/functions/finance/normalize_ohlcv.go @@ -0,0 +1,17 @@ +package finance + +// NormalizeOHLCV ajusta slices de precios OHLCV multiplicando por un factor. +func NormalizeOHLCV(open, high, low, close []float64, factor float64) ([]float64, []float64, []float64, []float64) { + n := len(open) + nOpen := make([]float64, n) + nHigh := make([]float64, n) + nLow := make([]float64, n) + nClose := make([]float64, n) + for i := 0; i < n; i++ { + nOpen[i] = open[i] * factor + nHigh[i] = high[i] * factor + nLow[i] = low[i] * factor + nClose[i] = close[i] * factor + } + return nOpen, nHigh, nLow, nClose +} diff --git a/functions/finance/normalize_ohlcv.md b/functions/finance/normalize_ohlcv.md new file mode 100644 index 00000000..95574180 --- /dev/null +++ b/functions/finance/normalize_ohlcv.md @@ -0,0 +1,38 @@ +--- +name: normalize_ohlcv +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func NormalizeOHLCV(open, high, low, close []float64, factor float64) ([]float64, []float64, []float64, []float64)" +description: "Ajusta slices de precios OHLCV multiplicando cada valor por un factor dado." +tags: [finance, ohlcv, normalize, adjust] +uses_functions: [] +uses_types: [ohlcv_go_finance] +returns: [ohlcv_go_finance] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/normalize_ohlcv.go" +--- + +# normalize_ohlcv + +Ajusta slices de precios OHLCV (open, high, low, close) multiplicando cada elemento por un factor escalar. Util para ajustes por splits, conversiones de divisa, o normalizacion de series. + +## Ejemplo + +```go +o, h, l, c := finance.NormalizeOHLCV( + []float64{100, 200}, + []float64{110, 210}, + []float64{90, 190}, + []float64{105, 205}, + 2.0, +) +// o = [200, 400], h = [220, 420], l = [180, 380], c = [210, 410] +``` diff --git a/functions/finance/rsi.go b/functions/finance/rsi.go new file mode 100644 index 00000000..cab72750 --- /dev/null +++ b/functions/finance/rsi.go @@ -0,0 +1,47 @@ +package finance + +// RSI calcula el Relative Strength Index. +// Usa el metodo de suavizado de Wilder (EMA con alpha = 1/period). +// Los primeros period elementos del resultado son 0. +func RSI(data []float64, period int) []float64 { + n := len(data) + result := make([]float64, n) + if period <= 0 || n < period+1 { + return result + } + var gainSum, lossSum float64 + for i := 1; i <= period; i++ { + change := data[i] - data[i-1] + if change > 0 { + gainSum += change + } else { + lossSum -= change + } + } + avgGain := gainSum / float64(period) + avgLoss := lossSum / float64(period) + if avgLoss == 0 { + result[period] = 100 + } else { + rs := avgGain / avgLoss + result[period] = 100 - 100/(1+rs) + } + for i := period + 1; i < n; i++ { + change := data[i] - data[i-1] + var gain, loss float64 + if change > 0 { + gain = change + } else { + loss = -change + } + avgGain = (avgGain*float64(period-1) + gain) / float64(period) + avgLoss = (avgLoss*float64(period-1) + loss) / float64(period) + if avgLoss == 0 { + result[i] = 100 + } else { + rs := avgGain / avgLoss + result[i] = 100 - 100/(1+rs) + } + } + return result +} diff --git a/functions/finance/rsi.md b/functions/finance/rsi.md new file mode 100644 index 00000000..21ee69ca --- /dev/null +++ b/functions/finance/rsi.md @@ -0,0 +1,31 @@ +--- +name: rsi +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func RSI(data []float64, period int) []float64" +description: "Calcula el Relative Strength Index (RSI) usando suavizado de Wilder." +tags: [finance, indicator, rsi, momentum] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/rsi.go" +--- + +# rsi + +Calcula el Relative Strength Index (RSI) con el metodo de suavizado de Wilder. Los primeros `period` elementos son 0. El RSI oscila entre 0 y 100. + +## Ejemplo + +```go +result := finance.RSI([]float64{44, 44.34, 44.09, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84}, 5) +``` diff --git a/functions/finance/sharpe_ratio.go b/functions/finance/sharpe_ratio.go new file mode 100644 index 00000000..67936800 --- /dev/null +++ b/functions/finance/sharpe_ratio.go @@ -0,0 +1,29 @@ +package finance + +import "math" + +// SharpeRatio calcula el ratio de Sharpe. +// riskFreeRate es la tasa libre de riesgo por periodo. +// periodsPerYear indica cuantos periodos hay en un anio (e.g. 252 para diario). +// Formula: (mean(returns) - riskFreeRate) / stddev(returns) * sqrt(periodsPerYear) +func SharpeRatio(returns []float64, riskFreeRate float64, periodsPerYear float64) float64 { + n := len(returns) + if n < 2 { + return 0 + } + var sum float64 + for _, r := range returns { + sum += r + } + mean := sum / float64(n) + var variance float64 + for _, r := range returns { + diff := r - mean + variance += diff * diff + } + std := math.Sqrt(variance / float64(n-1)) + if std == 0 { + return 0 + } + return (mean - riskFreeRate) / std * math.Sqrt(periodsPerYear) +} diff --git a/functions/finance/sharpe_ratio.md b/functions/finance/sharpe_ratio.md new file mode 100644 index 00000000..c7e33e54 --- /dev/null +++ b/functions/finance/sharpe_ratio.md @@ -0,0 +1,31 @@ +--- +name: sharpe_ratio +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func SharpeRatio(returns []float64, riskFreeRate float64, periodsPerYear float64) float64" +description: "Calcula el ratio de Sharpe anualizado a partir de retornos, tasa libre de riesgo y frecuencia." +tags: [finance, sharpe, risk, ratio] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [math] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/sharpe_ratio.go" +--- + +# sharpe_ratio + +Calcula el ratio de Sharpe: `(mean(returns) - riskFreeRate) / stddev(returns) * sqrt(periodsPerYear)`. Usa desviacion estandar muestral. + +## Ejemplo + +```go +sr := finance.SharpeRatio([]float64{0.01, 0.02, -0.005, 0.015, 0.008}, 0.0001, 252) +``` diff --git a/functions/finance/sma.go b/functions/finance/sma.go new file mode 100644 index 00000000..9920f762 --- /dev/null +++ b/functions/finance/sma.go @@ -0,0 +1,22 @@ +package finance + +// SMA calcula la media movil simple de data con el periodo dado. +// Retorna un slice de longitud len(data) donde los primeros period-1 +// elementos son 0 (sin datos suficientes). +func SMA(data []float64, period int) []float64 { + n := len(data) + result := make([]float64, n) + if period <= 0 || period > n { + return result + } + var sum float64 + for i := 0; i < period; i++ { + sum += data[i] + } + result[period-1] = sum / float64(period) + for i := period; i < n; i++ { + sum += data[i] - data[i-period] + result[i] = sum / float64(period) + } + return result +} diff --git a/functions/finance/sma.md b/functions/finance/sma.md new file mode 100644 index 00000000..388f20e4 --- /dev/null +++ b/functions/finance/sma.md @@ -0,0 +1,32 @@ +--- +name: sma +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func SMA(data []float64, period int) []float64" +description: "Calcula la media movil simple (SMA) sobre una serie de datos con un periodo dado." +tags: [finance, indicator, sma, moving-average] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/sma.go" +--- + +# sma + +Calcula la media movil simple (Simple Moving Average). Los primeros `period-1` elementos del resultado son 0 ya que no hay datos suficientes para calcular la media. + +## Ejemplo + +```go +result := finance.SMA([]float64{1, 2, 3, 4, 5}, 3) +// result = [0, 0, 2, 3, 4] +``` diff --git a/functions/finance/stream_ticks.go b/functions/finance/stream_ticks.go new file mode 100644 index 00000000..0a6ff2e7 --- /dev/null +++ b/functions/finance/stream_ticks.go @@ -0,0 +1,10 @@ +package finance + +import "fmt" + +// StreamTicks abre un stream de ticks en tiempo real para un simbolo. +// Retorna un canal de [2]float64 donde [0] es precio y [1] es volumen. +// TODO: implementar conexion real via websocket. +func StreamTicks(symbol string) (<-chan [2]float64, error) { + return nil, fmt.Errorf("not implemented: StreamTicks(%s)", symbol) +} diff --git a/functions/finance/stream_ticks.md b/functions/finance/stream_ticks.md new file mode 100644 index 00000000..fd30249f --- /dev/null +++ b/functions/finance/stream_ticks.md @@ -0,0 +1,31 @@ +--- +name: stream_ticks +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: impure +signature: "func StreamTicks(symbol string) (<-chan [2]float64, error)" +description: "Abre un stream de ticks en tiempo real para un simbolo via websocket." +tags: [finance, io, stream, realtime] +uses_functions: [] +uses_types: [tick_go_finance] +returns: [tick_go_finance] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/stream_ticks.go" +--- + +# stream_ticks + +Stub para abrir un stream de ticks en tiempo real. Pendiente de implementacion. + +## Ejemplo + +```go +ch, err := finance.StreamTicks("ETH/USDT") +``` diff --git a/functions/finance/tick_to_ohlcv.go b/functions/finance/tick_to_ohlcv.go new file mode 100644 index 00000000..5e63852e --- /dev/null +++ b/functions/finance/tick_to_ohlcv.go @@ -0,0 +1,53 @@ +package finance + +// TickToOHLCV agrega datos de ticks en velas OHLCV segun un intervalo en segundos. +// prices, volumes y timestamps deben tener la misma longitud. +// timestamps son unix seconds. intervalSecs define el ancho de cada vela. +// Retorna slices de open, high, low, close, volume para cada vela generada. +func TickToOHLCV(prices, volumes []float64, timestamps []int64, intervalSecs int64) (open, high, low, close, vol []float64) { + n := len(prices) + if n == 0 || intervalSecs <= 0 { + return + } + bucketStart := timestamps[0] - (timestamps[0] % intervalSecs) + o := prices[0] + h := prices[0] + l := prices[0] + c := prices[0] + v := volumes[0] + + for i := 1; i < n; i++ { + currentBucket := timestamps[i] - (timestamps[i] % intervalSecs) + if currentBucket != bucketStart { + // Cerrar la vela anterior + open = append(open, o) + high = append(high, h) + low = append(low, l) + close = append(close, c) + vol = append(vol, v) + // Nueva vela + bucketStart = currentBucket + o = prices[i] + h = prices[i] + l = prices[i] + c = prices[i] + v = volumes[i] + } else { + if prices[i] > h { + h = prices[i] + } + if prices[i] < l { + l = prices[i] + } + c = prices[i] + v += volumes[i] + } + } + // Cerrar ultima vela + open = append(open, o) + high = append(high, h) + low = append(low, l) + close = append(close, c) + vol = append(vol, v) + return +} diff --git a/functions/finance/tick_to_ohlcv.md b/functions/finance/tick_to_ohlcv.md new file mode 100644 index 00000000..849eacde --- /dev/null +++ b/functions/finance/tick_to_ohlcv.md @@ -0,0 +1,37 @@ +--- +name: tick_to_ohlcv +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func TickToOHLCV(prices, volumes []float64, timestamps []int64, intervalSecs int64) (open, high, low, close, vol []float64)" +description: "Agrega datos de ticks en velas OHLCV segun un intervalo de tiempo en segundos." +tags: [finance, ohlcv, aggregate, tick] +uses_functions: [] +uses_types: [tick_go_finance, ohlcv_go_finance] +returns: [ohlcv_go_finance] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/tick_to_ohlcv.go" +--- + +# tick_to_ohlcv + +Agrega ticks (precio, volumen, timestamp) en velas OHLCV. Los ticks se agrupan en buckets de `intervalSecs` segundos. Los timestamps deben estar en unix seconds y ordenados cronologicamente. + +## Ejemplo + +```go +o, h, l, c, v := finance.TickToOHLCV( + []float64{100, 101, 99, 102}, + []float64{10, 20, 15, 25}, + []int64{1000, 1005, 1055, 1060}, + 60, +) +// Genera 2 velas: [1000-1059] y [1060-1119] +``` diff --git a/functions/finance/vwap.go b/functions/finance/vwap.go new file mode 100644 index 00000000..057e56ee --- /dev/null +++ b/functions/finance/vwap.go @@ -0,0 +1,19 @@ +package finance + +// VWAP calcula el Volume Weighted Average Price. +// prices y volumes deben tener la misma longitud. +// Retorna 0 si el volumen total es 0 o los slices estan vacios. +func VWAP(prices, volumes []float64) float64 { + if len(prices) == 0 || len(prices) != len(volumes) { + return 0 + } + var cumPV, cumV float64 + for i := range prices { + cumPV += prices[i] * volumes[i] + cumV += volumes[i] + } + if cumV == 0 { + return 0 + } + return cumPV / cumV +} diff --git a/functions/finance/vwap.md b/functions/finance/vwap.md new file mode 100644 index 00000000..66195a9d --- /dev/null +++ b/functions/finance/vwap.md @@ -0,0 +1,32 @@ +--- +name: vwap +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: pure +signature: "func VWAP(prices, volumes []float64) float64" +description: "Calcula el Volume Weighted Average Price (VWAP) a partir de precios y volumenes." +tags: [finance, indicator, vwap, volume] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/vwap.go" +--- + +# vwap + +Calcula el VWAP (Volume Weighted Average Price). Es la suma de (precio * volumen) dividida por la suma de volumenes. + +## Ejemplo + +```go +v := finance.VWAP([]float64{100, 102, 101}, []float64{500, 300, 200}) +// v = (100*500 + 102*300 + 101*200) / (500+300+200) +``` diff --git a/functions/finance/write_ohlcv_to_parquet.go b/functions/finance/write_ohlcv_to_parquet.go new file mode 100644 index 00000000..69352a57 --- /dev/null +++ b/functions/finance/write_ohlcv_to_parquet.go @@ -0,0 +1,9 @@ +package finance + +import "fmt" + +// WriteOHLCVToParquet persiste datos OHLCV en un archivo Parquet. +// TODO: implementar escritura real usando libreria Parquet. +func WriteOHLCVToParquet(path string, data [][]float64) error { + return fmt.Errorf("not implemented: WriteOHLCVToParquet(%s)", path) +} diff --git a/functions/finance/write_ohlcv_to_parquet.md b/functions/finance/write_ohlcv_to_parquet.md new file mode 100644 index 00000000..77c0da76 --- /dev/null +++ b/functions/finance/write_ohlcv_to_parquet.md @@ -0,0 +1,31 @@ +--- +name: write_ohlcv_to_parquet +kind: function +lang: go +domain: finance +version: "1.0.0" +purity: impure +signature: "func WriteOHLCVToParquet(path string, data [][]float64) error" +description: "Persiste datos OHLCV en un archivo Parquet en la ruta indicada." +tags: [finance, io, parquet, persist] +uses_functions: [] +uses_types: [ohlcv_go_finance] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [fmt] +tested: false +tests: [] +test_file_path: "" +file_path: "functions/finance/write_ohlcv_to_parquet.go" +--- + +# write_ohlcv_to_parquet + +Stub para persistir datos OHLCV en formato Parquet. Pendiente de implementacion. + +## Ejemplo + +```go +err := finance.WriteOHLCVToParquet("/data/btc_1h.parquet", ohlcvData) +``` diff --git a/registry/db.go b/registry/db.go index f184864f..d902ec72 100644 --- a/registry/db.go +++ b/registry/db.go @@ -128,11 +128,18 @@ func Open(path string) (*DB, error) { return nil, fmt.Errorf("creating db directory: %w", err) } - conn, err := sql.Open("sqlite3", path+"?_journal_mode=WAL&_foreign_keys=on") + conn, err := sql.Open("sqlite3", path+"?_foreign_keys=on") if err != nil { return nil, fmt.Errorf("opening database: %w", err) } + // WAL mode: enables concurrent reads while writing. + // Persists in the file — any client opening the DB inherits it. + if _, err := conn.Exec("PRAGMA journal_mode=WAL"); err != nil { + conn.Close() + return nil, fmt.Errorf("setting WAL mode: %w", err) + } + if _, err := conn.Exec(schemaSQL); err != nil { conn.Close() return nil, fmt.Errorf("applying schema: %w", err) diff --git a/types/core/error.go b/types/core/error.go new file mode 100644 index 00000000..2098bb57 --- /dev/null +++ b/types/core/error.go @@ -0,0 +1,11 @@ +package core + +// Error is the standard error type for impure functions in the registry. +// Wraps a message string. Used as error_type reference. +type Error struct { + Message string +} + +func (e Error) Error() string { + return e.Message +} diff --git a/types/core/error.md b/types/core/error.md new file mode 100644 index 00000000..52de7883 --- /dev/null +++ b/types/core/error.md @@ -0,0 +1,15 @@ +--- +name: error +lang: go +domain: core +version: "1.0.0" +algebraic: product +definition: | + type Error struct { + Message string + } +description: "Tipo de error base del registry. Referenciado como error_type por funciones impuras." +tags: [error, base, impure] +uses_types: [] +file_path: "types/core/error.go" +--- diff --git a/types/core/option.go b/types/core/option.go new file mode 100644 index 00000000..4b3c0cde --- /dev/null +++ b/types/core/option.go @@ -0,0 +1,43 @@ +package core + +// Option represents a value that may or may not be present. +// A sum type: Some(T) | None. +type Option[T any] struct { + value *T +} + +// Some creates an Option containing a value. +func Some[T any](v T) Option[T] { + return Option[T]{value: &v} +} + +// None creates an empty Option. +func None[T any]() Option[T] { + return Option[T]{} +} + +// IsSome returns true if the Option contains a value. +func (o Option[T]) IsSome() bool { + return o.value != nil +} + +// IsNone returns true if the Option is empty. +func (o Option[T]) IsNone() bool { + return o.value == nil +} + +// Unwrap returns the value or panics if None. +func (o Option[T]) Unwrap() T { + if o.value == nil { + panic("called Unwrap on None") + } + return *o.value +} + +// UnwrapOr returns the value or a default if None. +func (o Option[T]) UnwrapOr(def T) T { + if o.value != nil { + return *o.value + } + return def +} diff --git a/types/core/option.md b/types/core/option.md new file mode 100644 index 00000000..98395988 --- /dev/null +++ b/types/core/option.md @@ -0,0 +1,19 @@ +--- +name: option +lang: go +domain: core +version: "1.0.0" +algebraic: sum +definition: | + type Option[T any] struct { + value *T + } +description: "Tipo suma generico que representa un valor opcional: Some(T) o None. Alternativa a punteros nil para modelar ausencia de valor de forma explicita." +tags: [option, sum, nullable, functional, generic] +uses_types: [] +file_path: "types/core/option.go" +--- + +## Notas + +Tipo suma con dos variantes: Some(T) y None. Inspirado en Option de Rust. diff --git a/types/core/pair.go b/types/core/pair.go new file mode 100644 index 00000000..f490fe4f --- /dev/null +++ b/types/core/pair.go @@ -0,0 +1,7 @@ +package core + +// Pair holds two values of potentially different types. +type Pair[A any, B any] struct { + First A + Second B +} diff --git a/types/core/pair.md b/types/core/pair.md new file mode 100644 index 00000000..d94d2742 --- /dev/null +++ b/types/core/pair.md @@ -0,0 +1,16 @@ +--- +name: pair +lang: go +domain: core +version: "1.0.0" +algebraic: product +definition: | + type Pair[A any, B any] struct { + First A + Second B + } +description: "Tipo producto generico que agrupa dos valores de tipos potencialmente distintos. Util para ZipSlices y operaciones que devuelven dos resultados." +tags: [pair, tuple, product, generic] +uses_types: [] +file_path: "types/core/pair.go" +--- diff --git a/types/cybersecurity/cidr_block.go b/types/cybersecurity/cidr_block.go new file mode 100644 index 00000000..1292d9e9 --- /dev/null +++ b/types/cybersecurity/cidr_block.go @@ -0,0 +1,10 @@ +package cybersecurity + +import "net" + +// CIDRBlock represents a parsed CIDR network range. +type CIDRBlock struct { + Network *net.IPNet + Broadcast net.IP + Hosts int +} diff --git a/types/cybersecurity/cidr_block.md b/types/cybersecurity/cidr_block.md new file mode 100644 index 00000000..2758028c --- /dev/null +++ b/types/cybersecurity/cidr_block.md @@ -0,0 +1,17 @@ +--- +name: cidr_block +lang: go +domain: cybersecurity +version: "1.0.0" +algebraic: product +definition: | + type CIDRBlock struct { + Network *net.IPNet + Broadcast net.IP + Hosts int + } +description: "Rango de red CIDR parseado con network, broadcast y numero de hosts." +tags: [cybersecurity, network, cidr, ip] +uses_types: [] +file_path: "types/cybersecurity/cidr_block.go" +--- diff --git a/types/cybersecurity/port_result.go b/types/cybersecurity/port_result.go new file mode 100644 index 00000000..5f9128fa --- /dev/null +++ b/types/cybersecurity/port_result.go @@ -0,0 +1,24 @@ +package cybersecurity + +// PortResult is a sum type for TCP port scan results. +type PortResult interface{ portResult() } + +// PortOpen indicates the port accepted a connection. +type PortOpen struct { + Port int + Banner string +} + +// PortClosed indicates the port refused the connection. +type PortClosed struct { + Port int +} + +// PortFiltered indicates the port did not respond (timeout). +type PortFiltered struct { + Port int +} + +func (PortOpen) portResult() {} +func (PortClosed) portResult() {} +func (PortFiltered) portResult() {} diff --git a/types/cybersecurity/port_result.md b/types/cybersecurity/port_result.md new file mode 100644 index 00000000..2b5de870 --- /dev/null +++ b/types/cybersecurity/port_result.md @@ -0,0 +1,16 @@ +--- +name: port_result +lang: go +domain: cybersecurity +version: "1.0.0" +algebraic: sum +definition: | + type PortResult interface{ portResult() } + type PortOpen struct{ Port int; Banner string } + type PortClosed struct{ Port int } + type PortFiltered struct{ Port int } +description: "Tipo suma para resultados de escaneo TCP: Open (con banner), Closed o Filtered." +tags: [cybersecurity, port, scan, network] +uses_types: [] +file_path: "types/cybersecurity/port_result.go" +--- diff --git a/types/cybersecurity/threat_result.go b/types/cybersecurity/threat_result.go new file mode 100644 index 00000000..512e5a09 --- /dev/null +++ b/types/cybersecurity/threat_result.go @@ -0,0 +1,21 @@ +package cybersecurity + +// ThreatResult is a sum type for SQL injection detection results. +type ThreatResult interface{ threatResult() } + +// Clean indicates no threat was detected. +type Clean struct{} + +// Suspicious indicates a possible threat with a reason. +type Suspicious struct { + Reason string +} + +// Malicious indicates a confirmed threat pattern. +type Malicious struct { + Pattern string +} + +func (Clean) threatResult() {} +func (Suspicious) threatResult() {} +func (Malicious) threatResult() {} diff --git a/types/cybersecurity/threat_result.md b/types/cybersecurity/threat_result.md new file mode 100644 index 00000000..31dc0545 --- /dev/null +++ b/types/cybersecurity/threat_result.md @@ -0,0 +1,16 @@ +--- +name: threat_result +lang: go +domain: cybersecurity +version: "1.0.0" +algebraic: sum +definition: | + type ThreatResult interface{ threatResult() } + type Clean struct{} + type Suspicious struct{ Reason string } + type Malicious struct{ Pattern string } +description: "Tipo suma para resultados de deteccion de amenazas: Clean, Suspicious o Malicious." +tags: [cybersecurity, threat, detection, sqli] +uses_types: [] +file_path: "types/cybersecurity/threat_result.go" +--- diff --git a/types/datascience/outlier_result.go b/types/datascience/outlier_result.go new file mode 100644 index 00000000..1c8d5d79 --- /dev/null +++ b/types/datascience/outlier_result.go @@ -0,0 +1,20 @@ +package datascience + +// OutlierResult is a sum type representing whether a data point is normal or an outlier. +type OutlierResult interface{ outlierResult() } + +// Normal indicates the data point is within expected range. +type Normal struct { + Index int + Value float64 +} + +// Outlier indicates the data point is anomalous. +type Outlier struct { + Index int + Value float64 + ZScore float64 +} + +func (Normal) outlierResult() {} +func (Outlier) outlierResult() {} diff --git a/types/datascience/outlier_result.md b/types/datascience/outlier_result.md new file mode 100644 index 00000000..e414449e --- /dev/null +++ b/types/datascience/outlier_result.md @@ -0,0 +1,15 @@ +--- +name: outlier_result +lang: go +domain: datascience +version: "1.0.0" +algebraic: sum +definition: | + type OutlierResult interface{ outlierResult() } + type Normal struct { Index int; Value float64 } + type Outlier struct { Index int; Value float64; ZScore float64 } +description: "Tipo suma que clasifica un dato como Normal o Outlier con su z-score. Usado por DetectOutliers." +tags: [datascience, outlier, anomaly, statistics] +uses_types: [] +file_path: "types/datascience/outlier_result.go" +--- diff --git a/types/finance/.gitkeep b/types/finance/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/types/finance/bollinger_result.go b/types/finance/bollinger_result.go new file mode 100644 index 00000000..3269372d --- /dev/null +++ b/types/finance/bollinger_result.go @@ -0,0 +1,8 @@ +package finance + +// BollingerResult holds the three bands of a Bollinger Bands calculation. +type BollingerResult struct { + Upper []float64 + Middle []float64 + Lower []float64 +} diff --git a/types/finance/bollinger_result.md b/types/finance/bollinger_result.md new file mode 100644 index 00000000..0ed05b3b --- /dev/null +++ b/types/finance/bollinger_result.md @@ -0,0 +1,17 @@ +--- +name: bollinger_result +lang: go +domain: finance +version: "1.0.0" +algebraic: product +definition: | + type BollingerResult struct { + Upper []float64 + Middle []float64 + Lower []float64 + } +description: "Resultado de Bollinger Bands con bandas superior, media e inferior." +tags: [finance, bollinger, indicator, bands] +uses_types: [] +file_path: "types/finance/bollinger_result.go" +--- diff --git a/types/finance/drawdown_result.go b/types/finance/drawdown_result.go new file mode 100644 index 00000000..55c33d5f --- /dev/null +++ b/types/finance/drawdown_result.go @@ -0,0 +1,8 @@ +package finance + +// DrawdownResult holds the maximum drawdown value and the indices where it occurred. +type DrawdownResult struct { + Value float64 + Start int + End int +} diff --git a/types/finance/drawdown_result.md b/types/finance/drawdown_result.md new file mode 100644 index 00000000..45a2e5b4 --- /dev/null +++ b/types/finance/drawdown_result.md @@ -0,0 +1,17 @@ +--- +name: drawdown_result +lang: go +domain: finance +version: "1.0.0" +algebraic: product +definition: | + type DrawdownResult struct { + Value float64 + Start int + End int + } +description: "Resultado de maximo drawdown con el valor de caida y los indices de inicio y fin." +tags: [finance, drawdown, risk, metric] +uses_types: [] +file_path: "types/finance/drawdown_result.go" +--- diff --git a/types/finance/ohlcv.go b/types/finance/ohlcv.go new file mode 100644 index 00000000..03b61dff --- /dev/null +++ b/types/finance/ohlcv.go @@ -0,0 +1,10 @@ +package finance + +// OHLCV represents a market candle with open, high, low, close prices and volume. +type OHLCV struct { + Open float64 + High float64 + Low float64 + Close float64 + Volume float64 +} diff --git a/types/finance/ohlcv.md b/types/finance/ohlcv.md new file mode 100644 index 00000000..6c43b693 --- /dev/null +++ b/types/finance/ohlcv.md @@ -0,0 +1,19 @@ +--- +name: ohlcv +lang: go +domain: finance +version: "1.0.0" +algebraic: product +definition: | + type OHLCV struct { + Open float64 + High float64 + Low float64 + Close float64 + Volume float64 + } +description: "Vela de mercado con precios de apertura, maximo, minimo, cierre y volumen." +tags: [finance, market, candle, ohlcv] +uses_types: [] +file_path: "types/finance/ohlcv.go" +--- diff --git a/types/finance/tick.go b/types/finance/tick.go new file mode 100644 index 00000000..8bbe0e28 --- /dev/null +++ b/types/finance/tick.go @@ -0,0 +1,11 @@ +package finance + +import "time" + +// Tick represents a single trade event in a market. +type Tick struct { + Symbol string + Price float64 + Volume float64 + Timestamp time.Time +} diff --git a/types/finance/tick.md b/types/finance/tick.md new file mode 100644 index 00000000..f2413ab7 --- /dev/null +++ b/types/finance/tick.md @@ -0,0 +1,18 @@ +--- +name: tick +lang: go +domain: finance +version: "1.0.0" +algebraic: product +definition: | + type Tick struct { + Symbol string + Price float64 + Volume float64 + Timestamp time.Time + } +description: "Evento de trade individual en un mercado. Contiene simbolo, precio, volumen y timestamp." +tags: [finance, market, tick, trade] +uses_types: [] +file_path: "types/finance/tick.go" +---