Indicator is a Golang library for technical analysis, providing a wide range of indicators, strategies, and backtesting capabilities. It leverages Go 1.22+ generics and channels for streaming data processing.
- Whenever I correct your behavior, establish a new architectural constraint, or express a coding preference (e.g., 'no dependencies'), you MUST immediately use your save_memory tool to persist this rule.
- Streaming: Most indicators process data through channels (
<-chan T), allowing for efficient, pipeline-based calculations. - Generics: Indicators are generic over
helper.Number(integers and floats). - Package Organization:
asset: Asset data management and repositories.backtest: Framework for testing strategies against historical data.helper: Low-level channel and math utilities.strategy: Logic for generating buy/sell/hold signals.trend,momentum,volatility,volume,valuation: Category-specific indicators.
- Finding Tasks: Use the
gh issue listcommand to identify open issues. Issues labeledgood first issueare typically self-contained indicator or strategy implementations. - No External Dependencies: This project aims to have no external dependencies. Do not add any new dependencies.
- Composition & Reusability: Build and utilize reusable blocks, particularly those in the
helper/package. Avoid re-implementing existing logic within a single indicator. For example, if an indicator uses a moving average, it should employ the existing implementation rather than duplicating the logic internally. - Copyright Header: Every file must start with the copyright notice:
// Copyright (c) 2021-2026 Onur Cinar. // The source code is provided under GNU AGPLv3 License. // https://github.com/cinar/indicator
- Indicator Pattern:
- Struct named after the indicator (e.g.,
Ema[T helper.Number]). New[Indicator]andNew[Indicator]With[Param]constructors.Compute(<-chan T) <-chan Tfor the main logic.IdlePeriod() intto indicate when it starts producing values.String() stringfor a descriptive name.
- Struct named after the indicator (e.g.,
- Testing:
- Packages should use
[package]_testsuffix (e.g.,package trend_test). - Use
helperutilities for test setup:SliceToChan,ChanToSlice,RoundDigits. - 100% coverage is required for all indicators.
- Packages should use
All indicators and strategies must be tested using historical data from CSV files (typically located in testdata/).
- Define Data Struct: Create a struct with tags matching the CSV headers.
- Read CSV: Use
helper.ReadFromCsvFileto load the data. - Prepare Streams: Use
helper.Duplicateto branch the input for processing and validation. - Extract Fields: Use
helper.Mapto create specific data channels (e.g., closing prices). - Align Streams: Use
helper.Skipon the expected data stream to account for the indicator'sIdlePeriod(). - Validate: Iterate through the channels and compare
actualvsexpected.
func TestIndicator(t *testing.T) {
type Data struct {
Close float64 `header:"Close"`
Expected float64 `header:"Expected"`
}
input, err := helper.ReadFromCsvFile[Data]("testdata/indicator.csv")
if err != nil {
t.Fatal(err)
}
inputs := helper.Duplicate(input, 2)
closing := helper.Map(inputs[0], func(d *Data) float64 { return d.Close })
ind := trend.NewIndicator[float64]()
actuals := ind.Compute(closing)
actuals = helper.RoundDigits(actuals, 2)
inputs[1] = helper.Skip(inputs[1], ind.IdlePeriod())
for data := range inputs[1] {
actual := <-actuals
if actual != data.Expected {
t.Fatalf("actual %v expected %v", actual, data.Expected)
}
}
}- Run all checks:
task - Format:
task fmt - Lint:
task lint - Test:
task test - Build CLI Tools:
task build-tools