Skip to content

How to develop new indicators quickly? #1

@anyongjin

Description

@anyongjin

Implement indicator code

You can use GPT to help quickly implement the banta version of the indicator based on the existing logic !
For example, you can use the following prompt words (including go code):

type Kline struct {
	Time   int64
	Open   float64
	High   float64
	Low    float64
	Close  float64
	Volume float64
	Info   float64
}

type BarEnv struct {
	TimeStart  int64
	TimeStop   int64
	Exchange   string
	MarketType string
	Symbol     string
	TimeFrame  string
	TFMSecs    int64 //周期的毫秒间隔
	BarNum     int
	MaxCache   int
	VNum       int
	Open       *Series
	High       *Series
	Low        *Series
	Close      *Series
	Volume     *Series
	Info       *Series
	Data       map[string]interface{}
}

type Series struct {
	ID    int
	Env   *BarEnv
	Data  []float64
	Cols  []*Series
	Time  int64
	More  interface{}
	Subs  map[string]map[int]*Series // 由此序列派生的;function:hash:object
	XLogs map[int]*CrossLog          // 此序列交叉记录
}


func (e *BarEnv) OnBar(barMs int64, open, high, low, close, volume, info float64) {
	if e.TimeStop > barMs {
		panic(fmt.Errorf("%s/%s old Bar Receive: %d, Current: %d", e.Symbol, e.TimeFrame, barMs, e.TimeStop))
	}
	e.TimeStart = barMs
	e.TimeStop = barMs + e.TFMSecs
	e.BarNum += 1
	if e.Open == nil {
		e.Open = e.NewSeries([]float64{open})
		e.High = e.NewSeries([]float64{high})
		e.Low = e.NewSeries([]float64{low})
		e.Close = e.NewSeries([]float64{close})
		e.Volume = e.NewSeries([]float64{volume})
		e.Info = e.NewSeries([]float64{info})
		if e.MaxCache == 0 {
			// 默认保留1000个
			e.MaxCache = 1000
		}
	} else {
		e.Open.Time = barMs
		e.Open.Data = append(e.Open.Data, open)
		e.High.Time = barMs
		e.High.Data = append(e.High.Data, high)
		e.Low.Time = barMs
		e.Low.Data = append(e.Low.Data, low)
		e.Close.Time = barMs
		e.Close.Data = append(e.Close.Data, close)
		e.Volume.Time = barMs
		e.Volume.Data = append(e.Volume.Data, volume)
		e.Info.Time = barMs
		e.Info.Data = append(e.Info.Data, info)
	}
}

func (e *BarEnv) NewSeries(data []float64) *Series {
	subs := make(map[string]map[int]*Series)
	xlogs := make(map[int]*CrossLog)
	res := &Series{e.VNum, e, data, nil, e.TimeStart, nil, subs, xlogs}
	e.VNum += 1
	return res
}

func (s *Series) Set(obj interface{}) *Series {
	if !s.Cached() {
		return s.Append(obj)
	}
	return s
}

func (s *Series) Append(obj interface{}) *Series {
	if s.Time >= s.Env.TimeStop {
		panic(fmt.Sprintf("repeat append on Series, %s, %v -> %v",
			s.Env.Symbol, s.Time, s.Env.TimeStop))
	}
	s.Time = s.Env.TimeStop
	if val, ok := obj.(float64); ok {
		s.Data = append(s.Data, val)
	} else if val, ok := obj.(int); ok {
		s.Data = append(s.Data, float64(val))
	} else if arr, ok := obj.([]float64); ok {
		for i, v := range arr {
			if i >= len(s.Cols) {
				col := s.To("_", i)
				s.Cols = append(s.Cols, col)
				col.Append(v)
			} else {
				col := s.Cols[i]
				col.Append(v)
			}
		}
	} else if cols, ok := obj.([]*Series); ok {
		s.Cols = cols
	} else {
		fmt.Printf("invalid val for Series.Append: %t", obj)
		panic(ErrInvalidSeriesVal)
	}
	return s
}

func (s *Series) Cached() bool {
	return s.Time >= s.Env.TimeStop
}

func (s *Series) Get(i int) float64 {
	if len(s.Cols) > 0 {
		panic(fmt.Errorf("Get Val on Merged Series!"))
	}
	allLen := len(s.Data)
	if i < 0 || i >= allLen {
		return math.NaN()
	}
	return s.Data[allLen-i-1]
}

/*
Range 获取范围内的值。
start 起始位置,0是最近的
stop 结束位置,不含
*/
func (s *Series) Range(start, stop int) []float64 {
	allLen := len(s.Data)
	_start := max(allLen-stop, 0)
	_stop := min(allLen-start, allLen)
	if _start >= _stop {
		return []float64{}
	}
	res := s.Data[_start:_stop]
	tmp := make([]float64, len(res))
	copy(tmp, res)
	slices.Reverse(tmp)
	return tmp
}

func (s *Series) Len() int {
	if len(s.Cols) > 0 {
		return s.Cols[0].Len()
	}
	return len(s.Data)
}

func (s *Series) Cut(keepNum int) {
	for _, dv := range s.Subs {
		for _, v := range dv {
			v.Cut(keepNum)
		}
	}
	if len(s.Cols) > 0 {
		for _, col := range s.Cols {
			col.Cut(keepNum)
		}
		return
	}
	curLen := len(s.Data)
	if curLen <= keepNum {
		return
	}
	s.Data = s.Data[curLen-keepNum:]
}

func (s *Series) Back(num int) *Series {
	res := s.To("_back", num)
	if !res.Cached() {
		endPos := len(s.Data) - num
		if endPos > 0 {
			res.Data = s.Data[:endPos]
		} else {
			res.Data = nil
		}
		res.Time = s.Env.TimeStop
	}
	return res
}

func (s *Series) objVal(rel string, obj interface{}) (*Series, float64) {
	if ser, ok := obj.(*Series); ok {
		return s.To(rel, ser.ID), ser.Get(0)
	} else if intVal, ok := obj.(int); ok {
		return s.To(rel, intVal), float64(intVal)
	} else if flt32Val, ok := obj.(float32); ok {
		return s.To(rel, int(flt32Val*10)), float64(flt32Val)
	} else if fltVal, ok := obj.(float64); ok {
		return s.To(rel, int(fltVal*10)), fltVal
	} else {
		fmt.Printf("invalid val for Series.objVal: %t", obj)
		panic(ErrInvalidSeriesVal)
	}
}

func (s *Series) To(k string, v int) *Series {
	sub, _ := s.Subs[k]
	if sub == nil {
		sub = make(map[int]*Series)
		s.Subs[k] = sub
	}
	old, _ := sub[v]
	if old == nil {
		old = s.Env.NewSeries(nil)
		sub[v] = old
	}
	return old
}


type tnrState struct {
	arr    []float64
	sumVal float64
}

// TNR Trend to Noise Ratio
func TNR(obj *Series, period int) *Series {
	res := obj.To("_tnr", period)
	if res.Cached() {
		return res
	}
	curVal := math.Abs(obj.Get(0) - obj.Get(1))
	sta, _ := res.More.(*tnrState)
	if sta == nil {
		sta = &tnrState{}
		res.More = sta
	}
	var resVal = math.NaN()
	if math.IsNaN(curVal) {
		sta.arr = make([]float64, 0)
		sta.sumVal = 0
	} else {
		sta.sumVal += curVal
		if len(sta.arr) < period {
			sta.arr = append(sta.arr, curVal)
		} else {
			sta.sumVal -= sta.arr[0]
			sta.arr = append(sta.arr[1:], curVal)
			if sta.sumVal > 0 {
				diffVal := math.Abs(obj.Get(0) - obj.Get(period))
				resVal = diffVal / sta.sumVal
			}
		}
	}
	return res.Append(resVal)
}


// KAMABy Kaufman Adaptive Moving Average
func KAMABy(obj *Series, period int, fast, slow float64) *Series {
	res := obj.To("_kama", period)
	if res.Cached() {
		return res
	}

	var prevMa = math.NaN()
	if cacheVal, ok := res.More.(*float64); ok {
		prevMa = *cacheVal
	}

	effRatio := TNR(obj, period).Get(0)
	smoothing := math.Pow(effRatio*(fast-slow)+slow, 2)

	curVal := obj.Get(0)
	if math.IsNaN(prevMa) {
		prevMa = curVal // Initialize with the first price value
	} else {
		prevMa += smoothing * (curVal - prevMa)
	}
	res.More = &prevMa

	return res.Append(prevMa)
}

//其他已实现的go指标如下,为减少上下文长度,只列出函数签名,实现其他方法时,尽可能引用已有方法减少代码冗余
func AvgPrice(e *BarEnv) *Series // AvgPrice 平均价格=(h+l+c)/3
func Sum(obj *Series, period int) *Series // Sum of period n 
func SMA(obj *Series, period int) *Series // simple moving average of period n
func VWMA(price *Series, vol *Series, period int) *Series // VWMA 成交量加权平均价格  公式:sum(price*volume)/sum(volume)
func EMA(obj *Series, period int) *Series // EMA 指数移动均线 最近一个权重:2/(n+1)

/* EMABy 指数移动均线
最近一个权重:2/(n+1)
initType:0使用SMA初始化,1第一个有效值初始化
*/
func EMABy(obj *Series, period int, initType int) *Series 

/*
RMA 相对移动均线
	和EMA区别是:分子分母都减一
	最近一个权重:1/n
*/
func RMA(obj *Series, period int) *Series

/*RMABy 相对移动均线
	和EMA区别是:分子分母都减一
	最近一个权重:1/n
initType:0使用SMA初始化,1第一个有效值初始化
initVal 默认Nan
*/
func RMABy(obj *Series, period int, initType int, initVal float64) *Series
func TR(high *Series, low *Series, close *Series) *Series
func ATR(high *Series, low *Series, close *Series, period int) *Series
/* MACD 计算MACD指标。
国外主流使用init_type=0,MyTT和国内主要使用init_type=1*/
func MACD(obj *Series, fast int, slow int, smooth int) *Series
func MACDBy(obj *Series, fast int, slow int, smooth int, initType int) *Series
func RSI(obj *Series, period int) *Series // RSI 计算相对强度指数
func Highest(obj *Series, period int) *Series
func HighestBar(obj *Series, period int) *Series
func Lowest(obj *Series, period int) *Series
func LowestBar(obj *Series, period int) *Series
func KDJ(high *Series, low *Series, close *Series, period int, sm1 int, sm2 int) *Series // KDJ 也称为:Stoch随机指标。返回k, d
func KDJBy(high *Series, low *Series, close *Series, period int, sm1 int, sm2 int, maBy string) *Series
/* Aroon 阿隆指标  反映了一段时间内出现最高价和最低价距离当前时间的远近。
AroonUp: (period - HighestBar(high, period+1)) / period * 100
AroonDn: (period - LowestBar(low, period+1)) / period * 100
Osc: AroonUp - AroonDn
返回:AroonUp, Osc, AroonDn*/
func Aroon(high *Series, low *Series, period int) *Series
/* StdDev 计算标准差和均值
返回:stddev,mean*/
func StdDev(obj *Series, period int) *Series
func BBANDS(obj *Series, period int, stdUp, stdDn float64) *Series // BBANDS 布林带指标。返回:upper, mean, lower
/* TD 计算Tom DeMark Sequence(狄马克序列)
9和13表示超买;-9和-13表示超卖*/
func TD(obj *Series) *Series
func ROC(obj *Series, period int) *Series // ROC rate of change
func AvgDev(obj *Series, period int) *Series // AvgDev sum(abs(Vi - mean))/period
func CCI(obj *Series, period int) *Series // CCI Commodity Channel Index
func CMF(env *BarEnv, period int) *Series // CMF Chaikin Money Flow
func MFV(env *BarEnv) (float64, float64)
func ADL(env *BarEnv) *Series // ADL Chaikin Accumulation/Distribution
func ChaikinOsc(env *BarEnv, short int, long int) *Series

As mentioned above, some indicators in Go are implemented, as well as the calculation logic of ADX (pinescript code of ADX). Please help me implement the ADX indicator in Go.

Test indicator correctness

You can add a CaseItem to the end of sta_inds_test.go:TestCommon to observe the output of the new indicator.
Then you can add calls to other libraries of this indicator in the scripts/test_inds.py script and compare the outputs after execution.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions