initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
.idea/*
|
||||
cli/*
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
module beejay.kim/lib/talib
|
||||
|
||||
go 1.26
|
||||
|
||||
require github.com/ebitengine/purego v0.10.2
|
||||
@@ -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=
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
Reference in New Issue
Block a user