sr

package module
v1.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

README

go-sr

CI Go Reference

Deterministic, no-lookahead support/resistance detection for Go trading systems.

go-sr is a focused Go module for support/resistance detection that is designed for backtests and live systems where reproducibility and no-lookahead behavior matter.

  • Deterministic results for the same candle prefix and options
  • Closed-candle inputs and confirmation-based zone pivots
  • Legacy line-based and ATR-aware zone modes
  • Nearest support/resistance metadata for strategy logic
  • Multi-timeframe candle aggregation and sizing helpers
  • No third-party runtime dependencies
  • CI with race detection, static analysis, 100% statement coverage, and fuzz smoke tests

Example

BTC 5m support/resistance zones detected by go-sr

Zone-mode output generated from the repository's BTC 5m fixture. The preview shows detected support/resistance structure, qualified zones, and nearest levels.

Install

go get github.com/laclance/go-sr@latest
import sr "github.com/laclance/go-sr"

Quick Start

levels, err := sr.Compute(candles, sr.Options{
    Timeframe:   "5m",
    Lookback:    120,
    Mode:        sr.ModeZones,
    MinStrength: 2,
})
if err != nil {
    return err
}

fmt.Printf("support=%.2f resistance=%.2f\n",
    levels.NearestSupport,
    levels.NearestResistance,
)

candles is a slice of closed OHLCV candles:

[]sr.Candle{
    {
        OpenTime:  openTime,
        CloseTime: closeTime,
        Open:      100.0,
        High:      103.0,
        Low:       99.0,
        Close:     102.0,
        Volume:    1250,
    },
}

The result includes the detected levels plus strategy-friendly nearest-level metadata:

levels.Levels
levels.NearestSupport
levels.NearestResistance
levels.NearestSupportDistance
levels.NearestResistanceDistance
levels.NearestSupportStrength
levels.NearestResistanceStrength
levels.NearestSupportScore
levels.NearestResistanceScore
levels.NearSupport
levels.NearResistance

Standalone Example

examples/basic is a self-contained runnable program. It generates deterministic closed 5m candles, runs zone mode, and prints the detected level count plus nearest support and resistance.

From a clean directory, copy and run:

git clone --depth=1 https://github.com/laclance/go-sr.git
cd go-sr
go run ./examples/basic

The example imports only the Go standard library plus github.com/laclance/go-sr; it does not depend on repository test helpers. You can also copy examples/basic/main.go into another Go module unchanged, then replace demoCandles with candles from your exchange, broker, backtest fixture, or market-data pipeline.

Integrations

  • BBGO closed-kline adapter shows how to map BBGO types.KLine events into a bounded slice of closed sr.Candle values without adding BBGO to this module.

Why go-sr?

Many trading implementations accidentally make support/resistance look better in backtests by allowing future candles to influence historical pivots. go-sr is built around prefix-stable, confirmation-based behavior so the same candle history produces the same result whether it is processed in a backtest or a live strategy.

That makes it a good fit when you need S/R as a dependable input rather than a chart-only visual indicator.

Modes

Zone mode
levels, err := sr.Compute(candles, sr.Options{
    Timeframe:   "5m",
    Lookback:    120,
    Mode:        sr.ModeZones,
    MinStrength: 2,
})

Zone mode clusters confirmed swing pivots into support/resistance zones. MinStrength filters qualified zones; values <= 0 use the default of 2.

Legacy mode
levels, err := sr.Compute(candles, sr.Options{
    Timeframe: "5m",
    Lookback:  120,
    Mode:      sr.ModeLegacy,
    Tolerance: 0.002,
})

Legacy mode provides line-based S/R behavior. Tolerance applies only to legacy mode; values <= 0 use the default 0.002.

Multi-Timeframe Support

Aggregate lower-timeframe candles before computing higher-timeframe S/R:

candles15m := sr.AggregateCandlesToTimeframe(candles5m, "5m", "15m")

levels15m, err := sr.Compute(candles15m, sr.Options{
    Timeframe: "15m",
    Lookback:  50,
    Mode:      sr.ModeZones,
})

Helpers are also available for calculating warmup and exchange-fetch requirements:

warmup := sr.WarmupCandles(50, sr.ModeZones)
limit := sr.RequiredKlineLimit("5m", "1h", 50, sr.ModeZones)

RequiredKlineLimit includes enough slack for UTC target-bucket alignment plus one potentially live final candle. Exclude that still-open candle before calling AggregateCandlesToTimeframe or Compute; both APIs expect closed candles.

Public API

type Mode string

const (
    ModeLegacy Mode = "legacy"
    ModeZones  Mode = "zone"
)

type Options struct {
    Timeframe   string
    Lookback    int
    Mode        Mode
    Tolerance   float64
    MinStrength int
}

func Compute(candles []Candle, opts Options) (Levels, error)
func EmptyLevels(timeframe string) Levels
func AggregateCandlesToTimeframe(candles []Candle, fromInterval, toInterval string) []Candle
func WarmupCandles(lookback int, mode Mode) int
func RequiredKlineLimit(baseInterval, targetInterval string, lookback int, mode Mode) int

See the standalone program in examples/basic, runnable package examples in examples_test.go, and the generated API documentation on pkg.go.dev.

Behavioral Contract

  • Compute is deterministic for the same candle prefix and options.
  • Compute returns an empty level bundle and an error for an unknown Mode.
  • Zone-mode pivots are confirmation-based; no future candles are read beyond the current prefix.
  • AggregateCandlesToTimeframe uses UTC-aligned buckets and drops leading/trailing partial buckets.
  • RequiredKlineLimit includes enough raw candles to preserve the higher-timeframe warmup after UTC alignment, plus one potentially live candle for exchange REST responses.
  • Callers must exclude still-open candles before passing data to AggregateCandlesToTimeframe or Compute.
  • Supported interval strings use <n><unit> with m, h, or d; the target interval must be larger than and evenly divisible by the base interval.
  • NearSupport / NearResistance describe whether the nearest level on each side is within the mode-specific near threshold.
  • In zone mode, the near threshold is 2 × the zone half-width; zero-width zones fall back to 0.1% of the current price.
  • In legacy mode, the near threshold is Tolerance × close.

Scope

This module owns:

  • Closed-candle S/R detection
  • Legacy line-based and zone-based S/R modes
  • Deterministic nearest support/resistance metadata
  • S/R-specific multi-timeframe aggregation, warmup sizing, and fetch sizing

This module intentionally does not own:

  • Exchange or Binance parsing
  • Strategy scoring or trade evaluation
  • App-specific timeframe policy
  • Order execution

Keeping exchange and strategy concerns outside the package makes go-sr usable across backtest engines, bots, and brokers.

Manual Chart Inspection

The repository includes a BTC fixture and an HTML chart generator for visually inspecting detected zones:

GO_SR_CHART=/tmp/go-sr-btc-5m.html \
  go test -run TestGenerateManualSRChart -count=1 -v

xdg-open /tmp/go-sr-btc-5m.html

Optional overrides:

GO_SR_CHART_TIMEFRAME=15m
GO_SR_CHART_MODE=legacy
GO_SR_CHART_LOOKBACK=80
GO_SR_CHART_WINDOW=300
GO_SR_CHART_MIN_STRENGTH=1

Quality Gate

CI runs on every push and pull request and requires:

  • gofmt
  • go test ./...
  • go test -race ./...
  • go vet ./...
  • staticcheck ./...
  • golangci-lint run
  • 100.0% statement coverage
  • Fuzz smoke tests for aggregation and compute invariants

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md before making a change, and use SECURITY.md for security reports.

If you are using go-sr in a project, opening a discussion or issue with your use case is also useful feedback for the API.

License

Apache-2.0. See LICENSE.

Documentation

Overview

Package sr provides deterministic, closed-candle support/resistance detection for Go trading systems, backtests, and live strategies.

It is designed for reproducible S/R signals without lookahead: zone-mode pivots are confirmation-based, and a given closed-candle prefix plus options produces deterministic output. Results include qualified levels, raw zones, nearest support/resistance prices, distances, strengths, scores, and proximity flags suitable for strategy logic.

Two compute modes are supported:

  • ModeLegacy: line-based pivots with fixed-tolerance proximity
  • ModeZones: zone-based detection with composite scoring and raw/qualified output

The package also provides S/R-specific multi-timeframe helpers for aggregating candles and calculating warmup/fetch requirements. Exchange parsing, app-specific timeframe policy, strategy scoring, and order execution remain outside the package.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func RequiredKlineLimit

func RequiredKlineLimit(baseInterval, targetInterval string, lookback int, mode Mode) int

RequiredKlineLimit returns the raw kline fetch size needed to build an S/R bundle for targetInterval from a baseInterval stream. The returned size includes alignment slack for UTC target buckets and one extra live candle because exchange REST responses usually include the currently forming bar.

func WarmupCandles

func WarmupCandles(lookback int, mode Mode) int

WarmupCandles returns the minimum closed-candle history needed before a support/resistance calculation can be considered fully warmed up.

Types

type Candle

type Candle struct {
	OpenTime  time.Time
	CloseTime time.Time
	Open      float64
	High      float64
	Low       float64
	Close     float64
	Volume    float64
}

Candle represents a single OHLCV closed candlestick.

func AggregateCandlesToTimeframe

func AggregateCandlesToTimeframe(candles []Candle, fromInterval, toInterval string) []Candle

AggregateCandlesToTimeframe rolls a closed-candle slice into a higher timeframe using UTC-aligned buckets. Any leading or trailing partial bucket is dropped.

Example
start := time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC)
candles := make5mCandles(start, []struct {
	open   float64
	high   float64
	low    float64
	close  float64
	volume float64
}{
	{100, 101, 99, 100.5, 10},
	{100.5, 102, 100, 101.5, 20},
	{101.5, 103, 101, 102.5, 30},
})

agg := AggregateCandlesToTimeframe(candles, "5m", "15m")
fmt.Println(len(agg), agg[0].Open, agg[0].Close, agg[0].Volume)
Output:
1 100 102.5 60

func (Candle) Body

func (c Candle) Body() float64

func (Candle) IsBearish

func (c Candle) IsBearish() bool

func (Candle) IsBullish

func (c Candle) IsBullish() bool

func (Candle) LowerWick

func (c Candle) LowerWick() float64

func (Candle) MidPoint

func (c Candle) MidPoint() float64

func (Candle) Range

func (c Candle) Range() float64

func (Candle) UpperWick

func (c Candle) UpperWick() float64

type Level

type Level struct {
	Price              float64
	Top                float64
	Bottom             float64
	Strength           int
	Score              float64
	IsHigh             bool
	Timeframe          string
	LastTouchIndex     int
	SourcePivotIndexes []int
	Pivots             []PivotInfo
}

Level represents a support or resistance zone built from clustered swing pivots.

type Levels

type Levels struct {
	Timeframe string

	Levels                    []Level
	RawZones                  []Level
	NearSupport               bool
	NearResistance            bool
	NearestSupport            float64
	NearestResistance         float64
	NearestSupportDistance    float64
	NearestResistanceDistance float64
	NearestSupportStrength    int
	NearestResistanceStrength int
	NearestSupportScore       float64
	NearestResistanceScore    float64
}

Levels holds computed support/resistance data for the current candle series.

func Compute

func Compute(candles []Candle, opts Options) (Levels, error)

Compute detects support/resistance levels for the given candle prefix.

Example (Legacy)
levels, err := Compute(buildSRCategoryCandles(), Options{
	Timeframe: "5m",
	Lookback:  120,
	Mode:      ModeLegacy,
	Tolerance: 0.002,
})
if err != nil {
	fmt.Println(err)
	return
}

fmt.Println(levels.Timeframe, len(levels.Levels) >= 2, levels.NearestSupport > 0, levels.NearestResistance > 0)
Output:
5m true true true
Example (Zone)
levels, err := Compute(buildSRCategoryCandles(), Options{
	Timeframe: "5m",
	Lookback:  120,
	Mode:      ModeZones,
})
if err != nil {
	fmt.Println(err)
	return
}

fmt.Println(levels.Timeframe, len(levels.Levels) >= 2, levels.NearestSupport > 0, levels.NearestResistance > 0)
Output:
5m true true true

func EmptyLevels

func EmptyLevels(timeframe string) Levels

EmptyLevels returns the zero-value bundle for a timeframe label.

type Mode

type Mode string

Mode selects the support/resistance algorithm.

const (
	ModeLegacy Mode = "legacy"
	ModeZones  Mode = "zone"
)

type Options

type Options struct {
	Timeframe string
	Lookback  int
	Mode      Mode
	// Tolerance applies only to ModeLegacy. When Tolerance <= 0, legacy mode
	// uses the default fallback of 0.002.
	Tolerance float64
	// MinStrength filters zone-mode raw zones by Strength.
	// 0 or negative -> use the default of 2 (back-compat).
	// 1+           -> require Strength >= MinStrength.
	// No effect on Mode=Legacy.
	MinStrength int
}

Options configures a support/resistance computation.

type PivotInfo

type PivotInfo struct {
	Index             int       `json:"index"`
	ConfirmedAtIndex  int       `json:"confirmed_at_index"`
	Time              time.Time `json:"time"`
	Price             float64   `json:"price"`
	IsHigh            bool      `json:"is_high"`
	Timeframe         string    `json:"timeframe"`
	ATRSnapshot       float64   `json:"atr_snapshot"`
	AvgVolumeSnapshot float64   `json:"avg_volume_snapshot"`
	Volume            float64   `json:"volume"`
	VolumeRatio       float64   `json:"volume_ratio"`
	MergeWidth        float64   `json:"merge_width"`
	BounceATR         float64   `json:"bounce_atr"`
}

PivotInfo captures the local snapshot data used to build a support or resistance zone.

Directories

Path Synopsis
examples
basic command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL