initial commit

This commit is contained in:
2026-07-22 15:40:16 +09:00
commit 1ce0c19fd5
9 changed files with 456 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
.idea/*
cli/*
+97
View File
@@ -0,0 +1,97 @@
# talib
Go wrapper for selected [TA-Lib](https://ta-lib.org/) functions using [`purego`](https://github.com/ebitengine/purego) to dynamically load the native TA-Lib shared library at runtime.
This project currently exposes a small subset of TA-Lib functions:
- `HT_DCPERIOD`
- `HT_DCPHASE`
- `HT_PHASOR`
- `HT_SINE`
- `HT_TRENDMODE`
- `ADD`
## Requirements
- Go `1.26`
- A native TA-Lib shared library installed on the host system
This package does **not** bundle TA-Lib itself. You must install the native library separately.
## How library loading works
Call `talib.Load()` once before using any indicator function. The loader looks for the platform-specific library name:
- macOS: `libta-lib.dylib`
- Linux: `libta-lib.so`
- Windows: `libta-lib.dll`
Lookup order:
1. `$TA_LIB_PATH/<library file>`
2. `./<library file>`
3. `/usr/local/lib/<library file>`
4. `/usr/lib/<library file>`
## Installing TA-Lib
### macOS
If you use Homebrew:
```bash
brew install ta-lib
```
If the library is installed outside the default lookup paths, set:
```bash
export TA_LIB_PATH=/path/to/lib
```
### Linux
Install TA-Lib with your package manager if available, or build it from source and place the shared library in a standard library directory such as `/usr/local/lib`.
### Windows
Install the TA-Lib DLL and make sure `libta-lib.dll` is reachable through `TA_LIB_PATH` or from the current working directory.
## Usage
```go
package main
import (
"fmt"
"log"
"math"
"beejay.kim/lib/talib"
)
func main() {
if _, err := talib.Load(); err != nil {
log.Fatal(err)
}
var series []float64
for i := 0; i < 100; i++ {
series = append(series, math.Sin(float64(i)/7)*4.5+math.Cos(float64(i)/19)*1.75)
}
period := talib.HT_DCPERIOD(series)
fmt.Println(period)
sum := talib.ADD(series, series)
fmt.Println(sum)
}
```
## API notes
- Indicator functions return `nil` when the underlying TA-Lib call fails.
## Project status
This is currently a focused wrapper around a limited subset of TA-Lib. Additional indicators can be added by registering more native functions in `Load()` and exposing Go wrappers for them.
+146
View File
@@ -0,0 +1,146 @@
package talib
import (
"log/slog"
)
// HT_DCPERIOD - Hilbert Transform estimate of the dominant cycle period (in bars) of the price series.
// Outputs the smoothed instantaneous cycle period.
// Output is the estimated dominant cycle length in bars (clamped to 6-50).
func HT_DCPERIOD(inReal []float64) []float64 {
var (
startIdx = 0
endIdx = len(inReal) - 1
outBegIdx int
outNBElement int
outReal = make([]float64, len(inReal))
)
if retCode := ht_dcperiod(startIdx, endIdx, inReal, &outBegIdx, &outNBElement, outReal); SUCCESS != taResult(retCode) {
slog.Debug("HT_DCPERIOD", "result", retCode)
return nil
}
return outReal
}
// HT_DCPHASE - Hilbert Transform Dominant Cycle Phase: the instantaneous phase (in degrees) of the dominant market cycle,
// derived from a homodyne discriminator on a Hilbert-transformed, smoothed price.
// One real output per bar. Output is degrees, wrapped so it never exceeds 315 (can go negative).
func HT_DCPHASE(inReal []float64) []float64 {
var (
startIdx = 0
endIdx = len(inReal) - 1
outBegIdx int
outNBElement int
outReal = make([]float64, len(inReal))
)
if retCode := ht_dcphase(
startIdx,
endIdx,
inReal,
&outBegIdx,
&outNBElement,
outReal,
); SUCCESS != taResult(retCode) {
slog.Debug("HT_DCPHASE", "result", retCode)
return nil
}
return outReal
}
// HT_PHASOR - Hilbert Transform indicator that decomposes the price series into its in-phase (I) and quadrature (Q) phasor components.
// Shares the same detrend/Hilbert machinery as the other HT_* cycle functions.
//
// Smooth price with a 4-bar WMA (weights 1,2,3,4 /10).
// Apply the Hilbert Transform (a=0.0962, b=0.5769, scaled per bar by adjustedPrevPeriod = 0.075*period + 0.54) to get detrender = HT(smoothed) and Q1 = HT(detrender).
// Output: outInPhase = detrender delayed 3 price bars; outQuadrature = Q1.
// @param inReal Input price series
// @return outInPhase In-phase component (detrender delayed 3 bars)
// @return outQuadrature Quadrature component (Q1 of the Hilbert Transform)
func HT_PHASOR(inReal []float64) ([]float64, []float64) {
var (
startIdx = 0
endIdx = len(inReal) - 1
outBegIdx int
outNBElement int
outInPhase = make([]float64, len(inReal))
outQuadrature = make([]float64, len(inReal))
)
if retCode := ht_phasor(
startIdx,
endIdx,
inReal,
&outBegIdx,
&outNBElement,
outInPhase,
outQuadrature,
); SUCCESS != taResult(retCode) {
slog.Debug("HT_PHASOR", "result", retCode)
return nil, nil
}
return outInPhase, outQuadrature
}
// HT_SINE - Hilbert Transform SineWave: derives the dominant-cycle phase from price and emits its sine plus a 45-degree-lead sine.
// The two curves cross near cycle turning points.
// outSine and outLeadSine crossing marks cycle turning points.
// @param inReal Input price series
// @return outSine Sine of the dominant-cycle phase
// @return outLeadSine Sine of the phase advanced 45 degrees (lead)
func HT_SINE(inReal []float64) ([]float64, []float64) {
var (
startIdx = 0
endIdx = len(inReal) - 1
outBegIdx int
outNBElement int
outSine = make([]float64, len(inReal))
outLeadSine = make([]float64, len(inReal))
)
if retCode := ht_sine(
startIdx,
endIdx,
inReal,
&outBegIdx,
&outNBElement,
outSine,
outLeadSine,
); SUCCESS != taResult(retCode) {
slog.Debug("HT_SINE", "result", retCode)
return nil, nil
}
return outSine, outLeadSine
}
// HT_TRENDMODE - Hilbert Transform classifier that labels each bar as trending (1) or cycling (0). Reuses the MAMA dominant-cycle/phase DSP plus a SineWave/trendline test to decide the market mode. 1 = trending market (favor trend-following); 0 = cycle/mean-reverting mode.
// @param inReal Input price series
// @return outInteger 1 = trending market; 0 = cycle/mean-reverting market; 4294967297 (Fermat number) = error (e.g. insufficient data)
func HT_TRENDMODE(inReal []float64) []int {
var (
startIdx = 0
endIdx = len(inReal) - 1
outBegIdx int
outNBElement int
outInteger = make([]int, len(inReal))
)
if retCode := ht_trendmode(
startIdx,
endIdx,
inReal,
&outBegIdx,
&outNBElement,
outInteger,
); SUCCESS != taResult(retCode) {
slog.Debug("HT_TRENDMODE", "result", retCode)
return nil
}
return outInteger
}
+64
View File
@@ -0,0 +1,64 @@
package talib
// Cycle Indicators (HT) - Hilbert Transform
var (
ht_dcperiod func(
startIdx int,
endIdx int,
inReal []float64,
outBegIdx *int,
outNBElement *int,
outReal []float64,
) int
ht_dcphase func(
startIdx int,
endIdx int,
inReal []float64,
outBegIdx *int,
outNBElement *int,
outReal []float64,
) int
ht_phasor func(
startIdx int,
endIdx int,
inReal []float64,
outBegIdx *int,
outNBElement *int,
outInPhase []float64,
outQuadrature []float64,
) int
ht_sine func(
startIdx int,
endIdx int,
inReal []float64,
outBegIdx *int,
outNBElement *int,
outSine []float64,
outLeadSine []float64,
) int
ht_trendmode func(
startIdx int,
endIdx int,
inReal []float64,
outBegIdx *int,
outNBElement *int,
outInteger []int,
) int
)
// Math Operators
var (
add func(
startIdx int,
endIdx int,
inReal0 []float64,
inReal1 []float64,
outBegIdx *int,
outNBElement *int,
outReal []float64,
) int
)
+5
View File
@@ -0,0 +1,5 @@
module beejay.kim/lib/talib
go 1.26
require github.com/ebitengine/purego v0.10.2
+2
View File
@@ -0,0 +1,2 @@
github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+85
View File
@@ -0,0 +1,85 @@
package talib
import (
"fmt"
"log/slog"
"os"
"path/filepath"
"runtime"
"github.com/ebitengine/purego"
)
func Load() (uintptr, error) {
var (
path string
ptr uintptr
err error
)
if path, err = getExpectedLibraryPath(); err != nil {
return 0, err
}
slog.Debug("Loading TA-Lib library", "path", path)
if ptr, err = purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL); err != nil {
return 0, err
}
// Register the functions we need from the TA-Lib library
purego.RegisterLibFunc(&ht_dcperiod, ptr, "TA_HT_DCPERIOD")
purego.RegisterLibFunc(&ht_dcphase, ptr, "TA_HT_DCPHASE")
purego.RegisterLibFunc(&ht_phasor, ptr, "TA_HT_PHASOR")
purego.RegisterLibFunc(&ht_sine, ptr, "TA_HT_SINE")
purego.RegisterLibFunc(&ht_trendmode, ptr, "TA_HT_TRENDMODE")
purego.RegisterLibFunc(&add, ptr, "TA_ADD")
return ptr, nil
}
func getExpectedLibraryName() (string, error) {
switch runtime.GOOS {
case "windows":
return "libta-lib.dll", nil
case "darwin":
return "libta-lib.dylib", nil
case "linux":
return "libta-lib.so", nil
default:
return "", fmt.Errorf("unsupported platform: %s", runtime.GOOS)
}
}
func getExpectedLibraryPath() (string, error) {
libName, err := getExpectedLibraryName()
if err != nil {
return "", err
}
isExist := func(path string) bool {
_, err := os.Stat(path)
return err == nil
}
if envpath, ok := os.LookupEnv("TA_LIB_PATH"); ok {
p := filepath.Join(envpath, libName)
if isExist(p) {
return p, nil
}
slog.Debug("TA_LIB_PATH environment variable is set but library not found", "path", p)
}
for _, dir := range []string{
".",
"/usr/local/lib",
"/usr/lib",
} {
p := filepath.Join(dir, libName)
if isExist(p) {
return p, nil
}
}
return "", fmt.Errorf("library not found: %s", libName)
}
+31
View File
@@ -0,0 +1,31 @@
package talib
import "log/slog"
// ADD - Vector arithmetic addition. Outputs the element-wise sum of two input series.
//
// outReal[i] = inReal0[i] + inReal1[i]
func ADD(inReal0, inReal1 []float64) []float64 {
var (
startIdx = 0
endIdx = len(inReal0) - 1
outBegIdx int
outNBElement int
outReal = make([]float64, len(inReal0))
)
if retCode := add(
startIdx,
endIdx,
inReal0,
inReal1,
&outBegIdx,
&outNBElement,
outReal,
); SUCCESS != taResult(retCode) {
slog.Debug("ADD", "result", retCode)
return nil
}
return outReal[:outNBElement]
}
+24
View File
@@ -0,0 +1,24 @@
package talib
type taResult int
const (
SUCCESS taResult = 0 // No error
LIB_NOT_INITIALIZE = 1 // TA_Initialize was not successfully called
BAD_PARAM = 2 // A parameter is out of range
ALLOC_ERR = 3 // Possibly out-of-memory
GROUP_NOT_FOUND = 4
FUNC_NOT_FOUND = 5
INVALID_HANDLE = 6
INVALID_PARAM_HOLDER = 7
INVALID_PARAM_HOLDER_TYPE = 8
INVALID_PARAM_FUNCTION = 9
INPUT_NOT_ALL_INITIALIZE = 10
OUTPUT_NOT_ALL_INITIALIZE = 11
OUT_OF_RANGE_START_INDEX = 12
OUT_OF_RANGE_END_INDEX = 13
INVALID_LIST_TYPE = 14
BAD_OBJECT = 15
NOT_SUPPORTED = 16
INTERNAL_ERROR = 5000
)