Add the Go binding over the C ABI hub (#228)

Adds a Go binding (`bindings/go`) over the C ABI hub — the second language stecker after C#.

## What's here
- **`bindings/go`** — a cgo binding exposing all 514 indicators as idiomatic Go types with `New<Indicator>` constructors and `Update`/`Batch`/`Reset`/`Close` methods. The wrappers in `indicators_gen.go` are generated from `bindings/c/include/wickra.h` (same archetype taxonomy as the C# generator: scalar/batch, multi-output, bars, profile, profile-values, array-input). Opaque handles are freed by `Close()` with a `runtime.SetFinalizer` backstop; pointer arguments are caller-owned, panics never cross the boundary.
- **`examples/go`** — the full example suite mirroring C/C#: streaming, backtest, multi_timeframe, parallel_assets (goroutine fan-out), three strategies, and `fetch_btcusdt`/`live_binance`.
- **CI** — a `go` job builds the C ABI library, stages it, and runs `gofmt`/`go vet`/`go test` plus the offline examples on Linux, macOS and Windows.
- **Docs** — Go added to the README languages table, project layout, building/testing, CONTRIBUTING binding table + regenerate note, ARCHITECTURE, examples index, issue/PR templates, the About-description template, and the other binding READMEs.

## Linking / distribution
The binding links the prebuilt C ABI library via cgo (`libwickra.so`/`.dylib`/`wickra.dll` staged under `bindings/go/lib`, gitignored). The native libraries are already shipped per target triple by the existing `c-abi-build` release job; distribution is via the subdirectory module tag `bindings/go/vX.Y.Z` (gated), so `release.yml` needs no new publish job.

No Rust crate or `Cargo.toml` change — the Go module is standalone and additive.

Not for merge yet (gated, per request).
This commit is contained in:
kingchenc
2026-06-09 17:33:37 +02:00
committed by GitHub
parent fce26cf881
commit 23d636fd97
43 changed files with 32701 additions and 24 deletions
+22
View File
@@ -69,6 +69,28 @@ The offline examples run on deterministic synthetic data (and under CI on all
three OSes); `fetch_btcusdt` and `live_binance` reach the network and are built
but not run in CI.
## Go — `examples/go/`
Build the C ABI library first (`cargo build -p wickra-c --release`) and stage it
under `bindings/go/lib/` (see the [Go binding README](../bindings/go)), then run
any example from the `examples/go` module.
| Example | What it does | Run |
| --- | --- | --- |
| `streaming` | Feed a synthetic price series through SMA / EMA / RSI / MACD tick by tick. | `go run ./streaming` |
| `backtest` | Basket of indicators over an OHLCV series (CSV arg or synthetic). | `go run ./backtest <ohlcv.csv>` |
| `multi_timeframe` | Resample a 1-minute series to 5m / 15m and print an indicator per timeframe. | `go run ./multi_timeframe` |
| `parallel_assets` | SMA(20) batch over a panel, serial vs goroutine fan-out, with speedup. | `go run ./parallel_assets 200 5000` |
| `strategy_rsi_mean_reversion` | RSI(14) mean-reversion with PnL / Sharpe / max-DD summary. | `go run ./strategy_rsi_mean_reversion` |
| `strategy_macd_adx` | Trend-follower: MACD crossover entries gated by ADX(14) > 20. | `go run ./strategy_macd_adx` |
| `strategy_bollinger_squeeze` | Bollinger-squeeze breakout with an ATR(14) trailing stop. | `go run ./strategy_bollinger_squeeze` |
| `fetch_btcusdt` | Download real BTCUSDT klines from the Binance REST API into a CSV. | `go run ./fetch_btcusdt` |
| `live_binance` | Stream live Binance klines through EMA(20) over a WebSocket. | `go run ./live_binance` |
The offline examples run on deterministic synthetic data (and under CI on all
three OSes); `fetch_btcusdt` and `live_binance` reach the network and are built
but not run in CI.
## Python — `examples/python/`
| Example | What it does | Run |
+14
View File
@@ -0,0 +1,14 @@
# Data fetched at runtime by fetch_btcusdt
**/data/
# Compiled example binaries
/streaming/streaming
/backtest/backtest
/multi_timeframe/multi_timeframe
/parallel_assets/parallel_assets
/strategy_rsi_mean_reversion/strategy_rsi_mean_reversion
/strategy_macd_adx/strategy_macd_adx
/strategy_bollinger_squeeze/strategy_bollinger_squeeze
/fetch_btcusdt/fetch_btcusdt
/live_binance/live_binance
*.exe
+38
View File
@@ -0,0 +1,38 @@
# Wickra examples — Go
Runnable Go examples for the [Wickra Go binding](../../bindings/go). Each example
is a small `main` program in its own directory; they share the deterministic
synthetic data, CSV loader, and equity summary in
[`internal/market`](internal/market).
The binding links against the prebuilt Wickra C ABI library, so build and stage
it once before running anything:
```bash
cargo build -p wickra-c --release
cp target/release/libwickra.so bindings/go/lib/ # Linux
cp target/release/libwickra.dylib bindings/go/lib/ # macOS
cp target/release/wickra.dll bindings/go/lib/ # Windows (also put it on PATH)
```
Then run any example from the `examples/go` module:
```bash
cd examples/go
go run ./streaming
```
| Example | What it does | Run |
| --- | --- | --- |
| `streaming` | Feed a synthetic price series through SMA / EMA / RSI / MACD tick by tick. | `go run ./streaming` |
| `backtest` | Compute a basket of indicators over an OHLCV series and print a summary. | `go run ./backtest <ohlcv.csv>` |
| `multi_timeframe` | Resample a 1-minute series into 5m / 15m and print an indicator per timeframe. | `go run ./multi_timeframe` |
| `parallel_assets` | SMA(20) batch over a panel of assets, serial vs goroutine fan-out, with speedup. | `go run ./parallel_assets 200 5000` |
| `strategy_rsi_mean_reversion` | RSI(14) mean-reversion with a PnL / Sharpe / max-DD summary. | `go run ./strategy_rsi_mean_reversion` |
| `strategy_macd_adx` | MACD crossover entries gated by ADX(14) > 20. | `go run ./strategy_macd_adx` |
| `strategy_bollinger_squeeze` | Bollinger-squeeze breakout with an ATR(14) trailing stop. | `go run ./strategy_bollinger_squeeze` |
| `fetch_btcusdt` | Download real BTCUSDT klines from the Binance REST API into a CSV. | `go run ./fetch_btcusdt` |
| `live_binance` | Stream live Binance klines through EMA(20) over a WebSocket. | `go run ./live_binance` |
`fetch_btcusdt` and `live_binance` require network access; the rest run offline
on deterministic synthetic data.
+56
View File
@@ -0,0 +1,56 @@
// Compute a basket of indicators over an OHLCV series and print a summary.
// Pass a CSV path (timestamp,open,high,low,close,volume) or run on synthetic data.
package main
import (
"fmt"
"log"
"math"
"os"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
source := "synthetic"
var bars []market.Bar
if len(os.Args) > 1 {
source = os.Args[1]
loaded, err := market.LoadOhlcvCsv(os.Args[1])
if err != nil {
log.Fatalf("load csv: %v", err)
}
bars = loaded
} else {
bars = market.SyntheticCandles(1000)
}
fmt.Printf("Backtest over %d bars (%s):\n", len(bars), source)
sma, _ := wickra.NewSma(20)
defer sma.Close()
ema, _ := wickra.NewEma(50)
defer ema.Close()
rsi, _ := wickra.NewRsi(14)
defer rsi.Close()
atr, _ := wickra.NewAtr(14)
defer atr.Close()
var lastSma, lastEma, lastRsi, lastAtr float64
oversold := 0
for _, b := range bars {
lastSma = sma.Update(b.Close)
lastEma = ema.Update(b.Close)
lastRsi = rsi.Update(b.Close)
lastAtr = atr.Update(b.Open, b.High, b.Low, b.Close, b.Volume, b.Timestamp)
if !math.IsNaN(lastRsi) && lastRsi < 30.0 {
oversold++
}
}
fmt.Printf(" SMA(20) last = %.4f\n", lastSma)
fmt.Printf(" EMA(50) last = %.4f\n", lastEma)
fmt.Printf(" RSI(14) last = %.4f (%d oversold bars)\n", lastRsi, oversold)
fmt.Printf(" ATR(14) last = %.4f\n", lastAtr)
}
+58
View File
@@ -0,0 +1,58 @@
// Download real BTCUSDT hourly klines from the Binance REST API into a CSV that the
// other examples can consume. Requires network access (build-only in CI).
package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
)
func main() {
const url = "https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1h&limit=500"
fmt.Printf("Fetching %s\n", url)
resp, err := http.Get(url)
if err != nil {
log.Fatalf("request: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("read body: %v", err)
}
// Binance kline array: [openTime, open, high, low, close, volume, ...].
var klines [][]any
if err := json.Unmarshal(body, &klines); err != nil {
log.Fatalf("parse json: %v", err)
}
dir := "data"
if err := os.MkdirAll(dir, 0o755); err != nil {
log.Fatalf("mkdir: %v", err)
}
path := filepath.Join(dir, "btcusdt_1h.csv")
file, err := os.Create(path)
if err != nil {
log.Fatalf("create: %v", err)
}
defer file.Close()
writer := bufio.NewWriter(file)
defer writer.Flush()
fmt.Fprintln(writer, "timestamp,open,high,low,close,volume")
count := 0
for _, k := range klines {
ts := int64(k[0].(float64))
fmt.Fprintf(writer, "%d,%s,%s,%s,%s,%s\n", ts, k[1], k[2], k[3], k[4], k[5])
count++
}
fmt.Printf("Wrote %d klines to %s\n", count, path)
}
+9
View File
@@ -0,0 +1,9 @@
module github.com/wickra-lib/wickra/examples/go
go 1.23
require github.com/wickra-lib/wickra/bindings/go v0.0.0
require github.com/coder/websocket v1.8.14
replace github.com/wickra-lib/wickra/bindings/go => ../../bindings/go
+2
View File
@@ -0,0 +1,2 @@
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
+156
View File
@@ -0,0 +1,156 @@
// Package market provides deterministic synthetic market data, a small OHLCV
// CSV loader, and an equity-curve summary shared by the offline Go examples so
// they run without network access. It mirrors the helpers used by the Python,
// C, and C# example suites.
package market
import (
"bufio"
"fmt"
"math"
"os"
"strconv"
"strings"
)
// Bar is one OHLCV bar with a millisecond timestamp.
type Bar struct {
Open float64
High float64
Low float64
Close float64
Volume float64
Timestamp int64
}
// SyntheticPrices returns a reproducible price path (trend + two cycles), with
// no randomness, starting at 100.
func SyntheticPrices(count int) []float64 {
return SyntheticPricesFrom(count, 100.0)
}
// SyntheticPricesFrom is SyntheticPrices with an explicit starting level.
func SyntheticPricesFrom(count int, start float64) []float64 {
prices := make([]float64, count)
for i := range prices {
fi := float64(i)
prices[i] = start + 12.0*math.Sin(fi*0.05) + 5.0*math.Sin(fi*0.013) + fi*0.01
}
return prices
}
// SyntheticCandles returns a reproducible OHLCV series derived from
// SyntheticPrices, one bar per hour.
func SyntheticCandles(count int) []Bar {
return SyntheticCandlesStep(count, 0, 3_600_000)
}
// SyntheticCandlesStep is SyntheticCandles with an explicit start timestamp and
// per-bar step in milliseconds.
func SyntheticCandlesStep(count int, startTimestamp, stepMs int64) []Bar {
prices := SyntheticPrices(count + 1)
bars := make([]Bar, count)
for i := 0; i < count; i++ {
fi := float64(i)
op := prices[i]
cl := prices[i+1]
high := math.Max(op, cl) + 0.5 + math.Abs(math.Sin(fi*0.7))
low := math.Min(op, cl) - 0.5 - math.Abs(math.Cos(fi*0.7))
volume := 1000.0 + 500.0*(1.0+math.Sin(fi*0.1))
bars[i] = Bar{op, high, low, cl, volume, startTimestamp + int64(i)*stepMs}
}
return bars
}
// LoadOhlcvCsv loads an OHLCV CSV. It accepts rows of
// timestamp,open,high,low,close,volume or open,high,low,close,volume; a
// non-numeric first row is treated as a header and skipped.
func LoadOhlcvCsv(path string) ([]Bar, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var bars []Bar
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
cols := strings.Split(line, ",")
if _, err := strconv.ParseFloat(cols[0], 64); err != nil {
continue // header row
}
f := func(i int) float64 {
v, _ := strconv.ParseFloat(strings.TrimSpace(cols[i]), 64)
return v
}
if len(cols) >= 6 {
ts, _ := strconv.ParseInt(strings.TrimSpace(cols[0]), 10, 64)
bars = append(bars, Bar{f(1), f(2), f(3), f(4), f(5), ts})
} else {
bars = append(bars, Bar{f(0), f(1), f(2), f(3), f(4), int64(len(bars))})
}
}
return bars, scanner.Err()
}
// EquityResult holds summary statistics for a long-only equity curve.
type EquityResult struct {
TotalReturnPct float64
Sharpe float64
MaxDrawdownPct float64
Trades int
FinalEquity float64
}
// Summarize turns a stream of per-bar fractional returns (0.01 == +1%) into a
// PnL / Sharpe / max-drawdown summary, annualised by periodsPerYear.
func Summarize(periodReturns []float64, trades int, periodsPerYear float64) EquityResult {
equity, peak, maxDrawdown := 1.0, 1.0, 0.0
for _, r := range periodReturns {
equity *= 1.0 + r
peak = math.Max(peak, equity)
if peak > 0 {
maxDrawdown = math.Max(maxDrawdown, (peak-equity)/peak)
}
}
mean := 0.0
if len(periodReturns) > 0 {
var sum float64
for _, r := range periodReturns {
sum += r
}
mean = sum / float64(len(periodReturns))
}
variance := 0.0
if len(periodReturns) > 1 {
var ss float64
for _, r := range periodReturns {
ss += (r - mean) * (r - mean)
}
variance = ss / float64(len(periodReturns)-1)
}
stdDev := math.Sqrt(variance)
sharpe := 0.0
if stdDev > 1e-12 {
sharpe = mean / stdDev * math.Sqrt(periodsPerYear)
}
return EquityResult{
TotalReturnPct: (equity - 1.0) * 100.0,
Sharpe: sharpe,
MaxDrawdownPct: maxDrawdown * 100.0,
Trades: trades,
FinalEquity: equity,
}
}
// Print writes a one-line summary of an equity result.
func Print(name string, r EquityResult) {
fmt.Printf("%-26s return=%8.2f%% sharpe=%6.2f maxDD=%6.2f%% trades=%d\n",
name, r.TotalReturnPct, r.Sharpe, r.MaxDrawdownPct, r.Trades)
}
+54
View File
@@ -0,0 +1,54 @@
// Stream live BTCUSDT 1-minute klines from Binance and feed each close through EMA(20).
// Requires network access (build-only in CI). Runs for up to 60 seconds.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"strconv"
"time"
"github.com/coder/websocket"
wickra "github.com/wickra-lib/wickra/bindings/go"
)
func main() {
const url = "wss://stream.binance.com:9443/ws/btcusdt@kline_1m"
fmt.Printf("Connecting to %s (up to 60s)...\n", url)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
conn, _, err := websocket.Dial(ctx, url, nil)
if err != nil {
log.Fatalf("dial: %v", err)
}
defer conn.CloseNow()
ema, _ := wickra.NewEma(20)
defer ema.Close()
for {
_, data, err := conn.Read(ctx)
if err != nil {
fmt.Println("Done (time limit reached).")
return
}
var msg struct {
K struct {
Close string `json:"c"`
} `json:"k"`
}
if err := json.Unmarshal(data, &msg); err != nil || msg.K.Close == "" {
continue
}
closePx, err := strconv.ParseFloat(msg.K.Close, 64)
if err != nil {
continue
}
fmt.Printf("close=%.2f EMA(20)=%.2f\n", closePx, ema.Update(closePx))
}
}
+54
View File
@@ -0,0 +1,54 @@
// Resample a 1-minute series into higher timeframes and run an indicator per timeframe.
package main
import (
"fmt"
"math"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
oneMinute := market.SyntheticCandlesStep(1200, 0, 60_000)
fmt.Println("EMA(20) of close across timeframes (resampled from 1-minute bars):")
for _, factor := range []int{1, 5, 15} {
bars := resample(oneMinute, factor)
ema, _ := wickra.NewEma(20)
var last float64
for _, b := range bars {
last = ema.Update(b.Close)
}
ema.Close()
fmt.Printf(" %2dm: %5d bars EMA(20) last = %.4f\n", factor, len(bars), last)
}
}
func resample(source []market.Bar, factor int) []market.Bar {
if factor <= 1 {
return source
}
var out []market.Bar
for i := 0; i < len(source); i += factor {
end := i + factor
if end > len(source) {
end = len(source)
}
high, low, volume := math.Inf(-1), math.Inf(1), 0.0
for j := i; j < end; j++ {
high = math.Max(high, source[j].High)
low = math.Min(low, source[j].Low)
volume += source[j].Volume
}
out = append(out, market.Bar{
Open: source[i].Open,
High: high,
Low: low,
Close: source[end-1].Close,
Volume: volume,
Timestamp: source[i].Timestamp,
})
}
return out
}
+80
View File
@@ -0,0 +1,80 @@
// Run SMA(20) batch over a panel of assets, serial vs goroutine fan-out, and
// report the speedup.
package main
import (
"fmt"
"os"
"runtime"
"strconv"
"sync"
"time"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
assets := argInt(1, 500)
bars := argInt(2, 20_000)
panel := make([][]float64, assets)
for a := 0; a < assets; a++ {
panel[a] = market.SyntheticPricesFrom(bars, 50.0+float64(a)*0.1)
}
// Warm up so the comparison is fair.
if warm, err := wickra.NewSma(20); err == nil {
warm.Batch(panel[0])
warm.Close()
}
sink := 0.0
start := time.Now()
for a := 0; a < assets; a++ {
sma, _ := wickra.NewSma(20)
result := sma.Batch(panel[a])
sma.Close()
sink += result[len(result)-1]
}
serial := time.Since(start)
lasts := make([]float64, assets)
start = time.Now()
var wg sync.WaitGroup
work := make(chan int, assets)
for w := 0; w < runtime.GOMAXPROCS(0); w++ {
wg.Add(1)
go func() {
defer wg.Done()
for a := range work {
sma, _ := wickra.NewSma(20)
result := sma.Batch(panel[a])
sma.Close()
lasts[a] = result[len(result)-1]
}
}()
}
for a := 0; a < assets; a++ {
work <- a
}
close(work)
wg.Wait()
parallel := time.Since(start)
serialMs := float64(serial.Microseconds()) / 1000.0
parallelMs := float64(parallel.Microseconds()) / 1000.0
fmt.Printf("%d assets x %d bars, SMA(20) batch:\n", assets, bars)
fmt.Printf(" serial %8.1f ms\n", serialMs)
fmt.Printf(" parallel %8.1f ms (%.1fx speedup)\n", parallelMs, serialMs/max(parallelMs, 1e-9))
_ = sink
}
func argInt(i, def int) int {
if len(os.Args) > i {
if v, err := strconv.Atoi(os.Args[i]); err == nil {
return v
}
}
return def
}
@@ -0,0 +1,66 @@
// Breakout: when Bollinger bandwidth is tight (a "squeeze") and price closes above
// the upper band, go long with an ATR(14) trailing stop.
package main
import (
"log"
"math"
"os"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
bars := loadBars()
bollinger, _ := wickra.NewBollingerBands(20, 2.0)
defer bollinger.Close()
atr, _ := wickra.NewAtr(14)
defer atr.Close()
var returns []float64
trades := 0
inPosition := false
entry := 0.0
stop := 0.0
for _, b := range bars {
band, okBand := bollinger.Update(b.Close)
atrValue := atr.Update(b.Open, b.High, b.Low, b.Close, b.Volume, b.Timestamp)
if !okBand || math.IsNaN(atrValue) {
continue
}
bandwidth := math.MaxFloat64
if band.Middle != 0.0 {
bandwidth = (band.Upper - band.Lower) / band.Middle
}
if !inPosition && bandwidth < 0.06 && b.Close > band.Upper {
inPosition = true
entry = b.Close
stop = b.Close - 2.0*atrValue
trades++
} else if inPosition {
stop = math.Max(stop, b.Close-2.0*atrValue) // trail the stop up
if b.Close < stop {
returns = append(returns, (b.Close-entry)/entry)
inPosition = false
}
}
}
market.Print("Bollinger squeeze", market.Summarize(returns, trades, 252.0))
}
func loadBars() []market.Bar {
if len(os.Args) > 1 {
bars, err := market.LoadOhlcvCsv(os.Args[1])
if err != nil {
log.Fatalf("load csv: %v", err)
}
return bars
}
return market.SyntheticCandles(2000)
}
+59
View File
@@ -0,0 +1,59 @@
// Trend follower: enter long on a MACD histogram cross up, but only when ADX(14) > 20
// confirms a trend; exit when the histogram crosses back below zero.
package main
import (
"log"
"math"
"os"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
bars := loadBars()
macd, _ := wickra.NewMacdIndicator(12, 26, 9)
defer macd.Close()
adx, _ := wickra.NewAdx(14)
defer adx.Close()
var returns []float64
trades := 0
inPosition := false
entry := 0.0
prevHistogram := math.NaN()
for _, b := range bars {
m, okMacd := macd.Update(b.Close)
a, okAdx := adx.Update(b.Open, b.High, b.Low, b.Close, b.Volume, b.Timestamp)
if !okMacd || !okAdx {
continue
}
trending := a.Adx > 20.0
if !inPosition && trending && !math.IsNaN(prevHistogram) && prevHistogram <= 0.0 && m.Histogram > 0.0 {
inPosition = true
entry = b.Close
trades++
} else if inPosition && m.Histogram < 0.0 {
returns = append(returns, (b.Close-entry)/entry)
inPosition = false
}
prevHistogram = m.Histogram
}
market.Print("MACD + ADX trend", market.Summarize(returns, trades, 252.0))
}
func loadBars() []market.Bar {
if len(os.Args) > 1 {
bars, err := market.LoadOhlcvCsv(os.Args[1])
if err != nil {
log.Fatalf("load csv: %v", err)
}
return bars
}
return market.SyntheticCandles(2000)
}
@@ -0,0 +1,51 @@
// Mean reversion: go long when RSI(14) drops below 30, exit when it recovers above 50.
package main
import (
"log"
"math"
"os"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
bars := loadBars()
rsi, _ := wickra.NewRsi(14)
defer rsi.Close()
var returns []float64
trades := 0
inPosition := false
entry := 0.0
for _, b := range bars {
value := rsi.Update(b.Close)
if math.IsNaN(value) {
continue
}
if !inPosition && value < 30.0 {
inPosition = true
entry = b.Close
trades++
} else if inPosition && value > 50.0 {
returns = append(returns, (b.Close-entry)/entry)
inPosition = false
}
}
market.Print("RSI mean-reversion", market.Summarize(returns, trades, 252.0))
}
func loadBars() []market.Bar {
if len(os.Args) > 1 {
bars, err := market.LoadOhlcvCsv(os.Args[1])
if err != nil {
log.Fatalf("load csv: %v", err)
}
return bars
}
return market.SyntheticCandles(2000)
}
+40
View File
@@ -0,0 +1,40 @@
// Feed a synthetic price series through several indicators tick by tick (O(1) each).
package main
import (
"fmt"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
prices := market.SyntheticPrices(500)
sma, _ := wickra.NewSma(20)
defer sma.Close()
ema, _ := wickra.NewEma(20)
defer ema.Close()
rsi, _ := wickra.NewRsi(14)
defer rsi.Close()
macd, _ := wickra.NewMacdIndicator(12, 26, 9)
defer macd.Close()
var lastSma, lastEma, lastRsi float64
var lastMacd wickra.MacdOutput
var haveMacd bool
for _, price := range prices {
lastSma = sma.Update(price)
lastEma = ema.Update(price)
lastRsi = rsi.Update(price)
lastMacd, haveMacd = macd.Update(price)
}
fmt.Printf("Streamed %d prices through SMA(20), EMA(20), RSI(14), MACD(12,26,9):\n", len(prices))
fmt.Printf(" SMA = %.4f\n", lastSma)
fmt.Printf(" EMA = %.4f\n", lastEma)
fmt.Printf(" RSI = %.4f\n", lastRsi)
if haveMacd {
fmt.Printf(" MACD = %.4f signal=%.4f hist=%.4f\n", lastMacd.Macd, lastMacd.Signal, lastMacd.Histogram)
}
}