# techan gives Go programs technical analysis without float prices

> techan is a technical analysis library for Go, with indicators, profit analysis and rule based trading strategies over a series of candles. Prices are big.Int rather than float64, indicators cache their results at 256 bits, and the README documents warmup indices, cache resets and analysis conventions in unusual detail for a library at this size.

**sdcoffey/techan** — Technical Analysis Library for Golang

- Repository: https://github.com/sdcoffey/techan
- Website: https://godoc.org/github.com/sdcoffey/techan
- Stars: 904 · Forks: 155
- Language: Go
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/sdcoffey-techan

## Indicators over candles, with prices as big.Int

techan is a technical analysis library for Go, and the README describes it as providing a suite of tools and frameworks to analyze financial data and make trading decisions. The feature list is three lines: basic and advanced technical analysis indicators, profit and trade analysis, and strategy building.

The design decision that sets it apart from most charting libraries is the price type. The quickstart builds candles with big.NewFromString, so the open, close, high, low and volume are arbitrary precision integers rather than float64. The library depends on github.com/sdcoffey/big for that, and the README explains the reasoning indirectly through the cache behaviour: rounding stored values to 256 binary bits, about 77 significant decimal digits, keeps memory proportional to history length without narrowing prices to float64.

That is a considered choice for a trading library. A price represented as a float cannot be compared exactly against a decimal threshold, and the accumulated error in an indicator over a long history is a real problem when the strategy logic turns on a comparison. Working in exact decimals and rounding deliberately to a fixed width gives exact arithmetic in the common case and bounded storage in the general one.

The repository layout is flat and file per indicator, which makes the coverage easy to see. There are files for the Aroon indicator, averages, average true range, basic indicators, Bollinger bands, commodity channel index, composition, constants, derivatives, differences, exponential and modified moving averages, relative strength, relative vigor index, fixed indicators, gains, Keltner channel, MACD, maximum drawdown, maximum and minimum value, mean deviation, the candle type itself, the cached indicator base, and the analysis code. Alongside them are analysis.go, candle.go, indicator.go, cached_indicator.go and the example and test files that mirror each one.

## The quickstart builds a candle series by hand

The quickstart is short and shows the whole shape of the API. You create a time series, then feed it candles:

```go
series := techan.NewTimeSeries()

// fetch this from your preferred exchange
dataset := [][]string{
	// Timestamp, Open, Close, High, Low, volume
	{"1609459200", "99", "100", "101", "98", "12"},
	{"1609545600", "100", "102", "103", "99", "15"},
	{"1609632000", "102", "101", "104", "100", "11"},
	{"1609718400", "101", "104", "105", "100", "18"},
}
```

The comment tells you the two things the library does not do for you. The dataset comes from your preferred exchange, so techan contains no market data acquisition, no exchange connectivity and no symbol handling. And the schema is fixed, timestamp, open, close, high, low and volume, all as strings so they can be parsed into exact decimals.

Each row becomes a candle tied to a time period, and the series enforces ordering. The example parses the timestamp, creates a period with a 24 hour duration, constructs a candle and sets its prices with big.NewFromString, and then adds it to the series, where AddCandle returns false if the candles are not in chronological order.

That ordering constraint is worth noticing. A library that silently accepted out of order candles would produce indicators that are quietly wrong, so the series refuses and the example panics on the failure. It is a small design decision that tells you what the author values.

## Warmup indices: a 10-period EMA starts at index 9

The part of the README that will save you the most time is the section on when an indicator becomes usable, and it is unusually explicit about the numbers.

Windowed indicators such as SMA and EMA return 0 until enough data exists to fill their first window, so a 10-period EMA starts producing calculated values at index 9. The library gives you a way to ask: use techan.FirstValidIndex(indicator) when composing indicators. The examples given are MACD with 12 and 26 periods, which first becomes usable at index 25, and its 9-period signal at index 33.

That single call is the difference between an indicator that looks broken and one that is working. A zero value from a warmup period is indistinguishable from a real zero if you do not know the index, and a strategy that reads a zero can fire at the wrong moment.

The rest of the section gives the same treatment to the other families. Custom indicators can implement WarmupIndicator to declare their first usable index, and without it they are assumed ready at zero. RSI uses Wilder's seed of N actual price changes, so RSI with 14 periods starts at index 14, and a flat window returns zero. Bollinger bands and windowed standard deviation return zero until a complete window is available.

Then you read the value only if the series is long enough:

```go
closePrices := techan.NewClosePriceIndicator(series)
movingAverage := techan.NewEMAIndicator(closePrices, 3) // Three observations per window

if series.LastIndex() >= techan.FirstValidIndex(movingAverage) {
	fmt.Println(movingAverage.Calculate(series.LastIndex()).FormattedString(2))
}
```

The guard is the point. The README also says the executable examples in the test files cover indicator composition, cache resets and a complete trading record, which is where to look for the patterns rather than the snippets.

## Indicator caches are filled iteratively and rounded to 256 bits

The caching behaviour is documented in the same detail as the warmup indices, and it has consequences for how you use the library.

EMA and MMA fill their caches iteratively and round stored values to 256 binary bits, about 77 significant decimal digits. The stated purpose is to keep memory proportional to history length without narrowing prices to float64, and the README is explicit that the caches still retain historical results. So an exponential moving average over a long history keeps the whole history, at 77 digits of precision, rather than collapsing to a single running value.

The consequence for users is that editing candles invalidates those caches, and the API for that is techan.ResetCacheFrom(rootIndicator, index), which you call on each indicator graph you use and which reaches built in nested caches. For a reindexed or truncated series, you reset from zero or rebuild the indicators. A full reset recomputes from the retained history, and the README is careful to note that it does not preserve EMA history that has been removed.

That last sentence is a design statement rather than a caveat. Removing data from the series also removes the influence of the removed data on the average, which is correct for a backtest but is not what you want if you are correcting a data error and expecting the previous result.

The last point in that section is the one that affects concurrency. Mutation, calculation and resetting must be serialised by the caller when indicators are shared between goroutines. techan is not documented as safe for concurrent use on a shared indicator graph, and in a service that serves several requests at once, the indicator graph per request or a lock around it is your responsibility, not the library's.

## Strategies are rules: an entry condition, an exit condition and a trading record

The strategy model is the second half of the library, and it is a rules engine rather than a backtester with a config file.

You build a rule, and a rule is a combination of conditions. The README's example is a strategy that enters when the close price crosses above a constant of 30 and the current position is new, and exits when the close price crosses below a constant of 10 and the current position is open. The rules are combined with techan.And, and the position conditions are techan.PositionNewRule and techan.PositionOpenRule.

The strategy object then holds an entry rule, an exit rule and an unstable period:

```go
strategy := techan.RuleStrategy{
	UnstablePeriod: 10, // Period before which ShouldEnter and ShouldExit will always return false
	EntryRule:      entryRule,
	ExitRule:       exitRule,
}

strategy.ShouldEnter(0, record) // returns false
```

UnstablePeriod is the index before which the strategy may not enter or exit at all, and the example shows it returning false at index 0. Trades are recorded on a techan.NewTradingRecord object, which is what the profit analysis later reads.

The crossing rules have a precise semantic that is easy to get wrong. Crossover rules fire only on a crossing bar, equality alone does not trigger a crossing, and an equality plateau is resolved using the preceding unequal values. That last clause matters in practice, because a price that sits exactly on a threshold for several bars would otherwise produce a different result depending on how the plateau was handled.

The example directory in the repository has a basic example and a strategy example, with a strategy.go file, so the composed form of this API is readable in the tree rather than only in the README.

## The analysis conventions say what is not modelled

The README has a section on analysis conventions, and it is the most valuable documentation in the file because it states the limits of the numbers rather than only their shape.

Profit analyses account for long and short order direction. PercentGainAnalysis returns total realized profit divided by the first closed position's entry cost, which is treated as the initial capital. It does not model deposits, withdrawals, or fees. PeriodProfitAnalysis includes fractional periods. The buy and hold comparison uses only completed candles through the record's last closed trade.

Maximum drawdown measures declines after positive peaks within the requested window, and a window without a positive peak returns zero.

Read together, that is a clear statement of what the analysis is. The percent gain figure is a ratio against the first entry, which is a convenient normalisation and not a statement about return on capital. Fees are absent, and for a strategy that trades often that omission is the difference between a profitable strategy and a losing one. A buy and hold comparison computed on completed candles up to the last closed trade is a fair baseline, but it is a baseline on the same data and with the same omissions.

None of this makes the library wrong. It makes the library honest, and it is rare for a trading library to say out loud that fees are not modelled. A user who reads this section knows exactly which conclusions the output supports and which ones need their own adjustment before they go into a decision.

## Contributing runs goimports, vet, Staticcheck and a fuzz target

The contributing section is short and tells you how the project is developed, which is a reasonable proxy for how much attention the code gets.

make test runs all the tests without rewriting source files, make format applies goimports, and make lint runs vet and Staticcheck. make bench reports timing and allocations, including EMA and MMA history scaling, which is the benchmark that matters for a library whose caches retain history.

There is also a fuzz target given as a command, go test with a run filter of an empty expression and a fuzz target of FuzzParseTimePeriod for ten seconds, which exercises the time period parsing. Time period parsing is exactly the kind of code that benefits from fuzzing, because a malformed period produces a wrong indicator window rather than an error.

The Makefile pins the tool versions. goimports is pinned to v0.49.0 and Staticcheck to v0.8.1, and the README says the formatting and lint tools are version pinned in the Makefile and run separately from the library module, and that they may automatically select a newer Go toolchain while the library's minimum remains Go 1.21. The go.mod confirms that: module github.com/sdcoffey/techan, go 1.21, with only two direct dependencies, the big library and testify.

The lineage is stated as well. Techan is heavily influenced by ta4j, and the README says many of the ideas and frameworks owe their genesis to that work. Techan is released under the MIT license, the project has a CHANGELOG.md at the root, and the recent releases are v0.14.0 on 2026-09-04, v0.13.0 on 2026-05-29 and 0.3.0 on 2018-04-12, with the last push on 2026-09-04.

## Conclusion

Adopt techan if you are writing a Go trading bot or analysis service and you want a tested library of indicators, strategy rules and profit analysis rather than your own implementation, and if your price data comes from an exchange you can adapt to the candle model. Do not adopt it if you need market data acquisition, since the library expects you to fetch the dataset yourself, and do not adopt it expecting the analysis to model a real account, because the profit analysis explicitly ignores deposits, withdrawals and fees. Before you start, read the warmup and cache sections of the README, call FirstValidIndex before you read an indicator value, and decide how you will serialise access, since the README requires mutation, calculation and resetting to be serialised by the caller when indicators are shared between goroutines.

## FAQ

### What is techan and what does it provide?

It is a technical analysis library for Go that provides a suite of tools and frameworks to analyze financial data and make trading decisions, covering basic and advanced indicators, profit and trade analysis, and strategy building. It requires Go 1.21 or newer.

### How do I install techan?

With go get github.com/sdcoffey/techan. The library takes no responsibility for fetching data, so you supply the dataset yourself from your preferred exchange, with timestamp, open, close, high, low and volume fields.

### Why does my EMA indicator return zero?

Windowed indicators return zero until their first window is full, so a 10-period EMA starts at index 9. Use techan.FirstValidIndex(indicator) to find the first usable index and guard your read with it, as the README describes for a 12 and 26 period MACD becoming usable at index 25.

### Do the profit analysis results include fees?

No. The README states that PercentGainAnalysis returns total realized profit divided by the first closed position's entry cost, treated as initial capital, and that it does not model deposits, withdrawals or fees.

### Is techan safe to use from multiple goroutines?

Not on a shared indicator graph. The README says mutation, calculation and resetting must be serialised by the caller when indicators are shared between goroutines, and it also notes that after editing candles you should call techan.ResetCacheFrom on each indicator graph you use.

## Sources

- [License: MIT](https://github.com/sdcoffey/techan/blob/main/LICENSE)
- [Project website](https://godoc.org/github.com/sdcoffey/techan)
- [README](https://github.com/sdcoffey/techan/blob/main/README.md)
- [Releases](https://github.com/sdcoffey/techan/releases)
- [sdcoffey/techan on GitHub](https://github.com/sdcoffey/techan)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sdcoffey-techan
