package rpc_fea import ( "fmt" "github.com/banbox/banbot/biz" "github.com/banbox/banbot/utils" "github.com/banbox/banexg" "github.com/banbox/banexg/errs" ta "github.com/banbox/banta" "gonum.org/v1/gonum/floats" "gonum.org/v1/gonum/mat" "math" "slices" "sync" ) func genFutProbsV1(arr []*banexg.Kline, minChgVal float64, maxRevRate float64) [3]float64 { first, upper, lower, chgVal := getFutBounds(arr) total := upper - lower chgRate := chgVal / first upRate := (upper - first) / total dnRate := (first - lower) / total revRate := min(upRate, dnRate) / max(upRate, dnRate) var zeroRate float64 if revRate > maxRevRate { // 反向波动过大,视为不确定 zeroRate = min(1.0, max(0.55, revRate+0.2)) } else if chgRate < 0.005 || chgVal < minChgVal { // 波动太小,视为不确定 var rateScore, valScore float64 if chgRate < 0.01 { rateScore = 1 - chgRate/0.01 } if chgVal < minChgVal*2 { valScore = 1 - chgVal/minChgVal/2 } zeroRate = max(0.55, rateScore*0.5+valScore*0.5) } else { // 有方向的行情,将反向波动50%作为不确定的概率 zeroRate = revRate * 0.5 } dirtRate := 1 - zeroRate upRate *= dirtRate dnRate *= dirtRate return [3]float64{zeroRate, upRate, dnRate} } /* genFutProbs 生成未来行情概率:[波动幅度,上升,下降,类别] */ func genFutProbs(arr []*banexg.Kline, minChgVal float64) [4]float64 { first, upper, lower, chgVal := getFutBounds(arr) total := upper - lower chgRate := chgVal / first upRate := (upper - first) / total dnRate := (first - lower) / total var cate float64 if min(upRate, dnRate)/max(upRate, dnRate) < 0.4 { if upRate > dnRate { cate = 1 } else { cate = 2 } } if chgVal < minChgVal { // 低于最低限度,置为高低相同 upRate, dnRate = 0.5, 0.5 cate = 0 } else if chgVal < minChgVal*2 { // 不够高,逐渐调整 overRate := (chgVal - minChgVal) / minChgVal // 这是保持概率 sameRate := (1 - overRate) / 2 // 这是拉平的概率 upRate = upRate*overRate + sameRate dnRate = dnRate*overRate + sameRate if cate != 0 && min(upRate, dnRate)/max(upRate, dnRate) > 0.6 { cate = 0 } } // chgRate的30个5m变化率平均是1%,则乘以100 return [4]float64{math.Log1p(chgRate*100) / 2, upRate, dnRate, cate} } func getFutBounds(arr []*banexg.Kline) (float64, float64, float64, float64) { var first = arr[0].Open var lower, upper = first, first var lowAt, uppAt = 0, 0 for i, b := range arr { if b.Low < lower { lowAt = i lower = b.Low } if b.High > upper { uppAt = i upper = b.High } } var chgVal float64 if upper-first > first-lower { // 属于上升趋势 arr = arr[:uppAt+1] lower = first for _, b := range arr { lower = min(lower, b.Low) } chgVal = upper - first } else { // 下降趋势 arr = arr[:lowAt+1] upper = first for _, b := range arr { upper = max(upper, b.High) } chgVal = first - lower } return first, upper, lower, chgVal } /* * 计算给定周期内的成本价 */ func avgCostN(e *ta.BarEnv, periods ...int) []float64 { maxPeriod := slices.Max(periods) opens := e.Open.Range(0, maxPeriod) highs := e.High.Range(0, maxPeriod) lows := e.Low.Range(0, maxPeriod) closes := e.Close.Range(0, maxPeriod) vols := e.Volume.Range(0, maxPeriod) slices.Sort(periods) price := closes[0] // 累计流入资金,累计流入成交量 sumCost, sumVol := float64(0), float64(0) results := make([]float64, 0, len(periods)) hitIdx := 0 for i := 0; i < len(opens); i++ { if i >= periods[hitIdx] { results = append(results, sumCost/sumVol/price-1) hitIdx += 1 } o, h, l, c, v := opens[i], highs[i], lows[i], closes[i], vols[i] barUp, barDn := max(o, c), min(o, c) total := h - l + (h - barUp) + (barDn - l) if total <= 0 { continue } // 净买入或净卖出的比例 sideRate := (barUp - barDn) * 0.33 / total dirt := -1.0 if o < c { dirt = 1.0 } curVol := sideRate * v * dirt sumVol += curVol sumCost += curVol * (barUp*0.5 + barDn*0.5) } if len(results) < len(periods) && sumVol != 0 { results = append(results, sumCost/sumVol/price-1) } for len(results) < len(periods) { results = append(results, 0) } for i, v := range results { results[i] = cutRange(v, -1, 1, 0) } return results } func matToNumArr(m *mat.Dense) *biz.NumArr { data := m.RawMatrix().Data row, col := m.Dims() return &biz.NumArr{Data: data, Shape: []int32{int32(row), int32(col)}} } func onAiFeatures(e *ta.BarEnv, num int) *mat.Dense { volMa5 := ta.SMA(e.Volume, 5) volMa20 := ta.SMA(e.Volume, 20) volMa70 := ta.SMA(e.Volume, 70) vwma5 := ta.VWMA(e.Close, e.Volume, 5) vwma20 := ta.VWMA(e.Close, e.Volume, 20) vwma70 := ta.VWMA(e.Close, e.Volume, 70) rsi14 := ta.RSI(e.Close, 14) rsi50 := ta.RSI(e.Close, 50) atr14 := ta.ATR(e.High, e.Low, e.Close, 14) atr50 := ta.ATR(e.High, e.Low, e.Close, 30) sma5 := ta.SMA(e.Close, 5) sma20 := ta.SMA(e.Close, 20) sma70 := ta.SMA(e.Close, 70) macd, macdS := ta.MACD(e.Close, 12, 26, 9) macd2, macdS2 := ta.MACD(e.Close, 40, 90, 30) kdjK, kdjD, _ := ta.KDJ(e.High, e.Low, e.Close, 9, 3, 3) kdjK2, kdjD2, _ := ta.KDJ(e.High, e.Low, e.Close, 48, 16, 12) stdDev := ta.StdDev(e.Close, 10) adx := ta.ADXBy(e.High, e.Low, e.Close, 14, 0, 1) roc := ta.ROC(e.Close, 30) if num == 0 { return nil } // 这里的特征列数最好是4或10的倍数,否则模型端采样会有问题 feaNum := 30 res := mat.NewDense(num, feaNum, nil) for i := 0; i < num; i++ { closeVal := e.Close.Get(i) volVal := e.Volume.Get(i) volMa5Val := volMa5.Get(i) volMa20Val := volMa20.Get(i) sma5Val := sma5.Get(i) sma20Val := sma20.Get(i) vwma5Val := vwma5.Get(i) vwma20Val := vwma20.Get(i) row := []float64{ e.High.Get(i), e.Low.Get(i), closeVal, volVal, math.Log1p(volVal/volMa5Val) / 3, math.Log1p(volMa5Val/volMa20Val) / 3, // 5 math.Log1p(volMa20Val/volMa70.Get(i)) / 3, (closeVal - e.Close.Get(i+1)) / closeVal, (closeVal - e.Close.Get(i+7)) / closeVal, // 8 此行超出(-1,1),调用方需截断 rsi14.Get(i) / 100, rsi50.Get(i) / 100, // 10 atr14.Get(i) / closeVal, atr50.Get(i) / closeVal, sma5Val/closeVal - 1, sma20Val/sma5Val - 1, sma70.Get(i)/sma20Val - 1, // 15 vwma5Val/closeVal - 1, // 16 此行超出(-1,1),调用方需截断 vwma20Val/vwma5Val - 1, // 17 此行超出(-1,1),调用方需截断 vwma70.Get(i)/vwma20Val - 1, macd.Get(i) / closeVal, (macd.Get(i) - macdS.Get(i)) / closeVal, // 20 macd2.Get(i) / closeVal, (macd2.Get(i) - macdS2.Get(i)) / closeVal, kdjK.Get(i) / 100, (kdjK.Get(i) - kdjD.Get(i)) / 100, kdjK2.Get(i) / 100, // 25 (kdjK2.Get(i) - kdjD2.Get(i)) / 100, stdDev.Get(i) / closeVal, adx.Get(i) / 100, roc.Get(i) / 100, // 29 此行超出(-1,1),调用方需截断 } for j, v := range row { if math.IsNaN(v) { v = 0 } res.Set(i, j, v) } } // 对前四列hlcv进行归一化 for i := 0; i < 4; i++ { col := res.ColView(i) minVal := mat.Min(col) maxVal := mat.Max(col) if minVal == maxVal { continue } for row := 0; row < num; row++ { oldValue := res.At(row, i) newValue := (oldValue - minVal) / (maxVal - minVal) res.Set(row, i, newValue) } } return res } func onAiFeatures2(e *ta.BarEnv, num int) *mat.Dense { if num <= 10 { panic("onAiFeatures2.num must greate than 10") } volMa5 := ta.SMA(e.Volume, 5) volMa20 := ta.SMA(e.Volume, 20) volMa70 := ta.SMA(e.Volume, 70) vwma5 := ta.VWMA(e.Close, e.Volume, 5) vwma20 := ta.VWMA(e.Close, e.Volume, 20) vwma70 := ta.VWMA(e.Close, e.Volume, 70) rsi14 := ta.RSI(e.Close, 14) rsiMa := ta.SMA(rsi14, 5) rsi50 := ta.RSI(e.Close, 50) atr14 := ta.ATR(e.High, e.Low, e.Close, 14) atr50 := ta.ATR(e.High, e.Low, e.Close, 30) sma5 := ta.SMA(e.Close, 5) sma20 := ta.SMA(e.Close, 20) sma70 := ta.SMA(e.Close, 70) macd, macdS := ta.MACD(e.Close, 12, 26, 9) macd2, macdS2 := ta.MACD(e.Close, 40, 90, 30) kdjK, kdjD, _ := ta.KDJ(e.High, e.Low, e.Close, 9, 3, 3) kdjK2, kdjD2, _ := ta.KDJ(e.High, e.Low, e.Close, 48, 16, 12) stdDev := ta.StdDev(e.Close, 10) bbUp, _, bbDn := ta.BBANDS(e.Close, 15, 1.6, 1.6) bb2Up, _, bb2Dn := ta.BBANDS(e.Close, 30, 2.5, 2.5) aroonUp, aroonOsc, aroonDn := ta.Aroon(e.High, e.Low, 14) aroon2Up, aroon2Osc, aroon2Dn := ta.Aroon(e.High, e.Low, 30) adx := ta.ADX(e.High, e.Low, e.Close, 14) adxPlus, _ := ta.PluMinDI(e.High, e.Low, e.Close, 14) adx2 := ta.ADX(e.High, e.Low, e.Close, 50) adxPlus2, _ := ta.PluMinDI(e.High, e.Low, e.Close, 50) roc := ta.ROC(e.Close, 9) roc2 := ta.ROC(e.Close, 30) tnr := ta.ER(e.Close, 15) tnr2 := ta.ER(e.Close, 50) cci := ta.CCI(e.Close, 15) cci50 := ta.CCI(e.Close, 50) cci100 := ta.CCI(e.Close, 100) cmf := ta.CMF(e, 15) cmf50 := ta.CMF(e, 50) kama := ta.KAMA(e.Close, 15) kama30 := ta.KAMA(e.Close, 30) willr := ta.WillR(e, 15) willr30 := ta.WillR(e, 30) willr30_5 := ta.SMA(willr30, 5) willr50 := ta.WillR(e, 50) srsiK, srsiD := ta.StochRSI(e.Close, 14, 14, 3, 3) srsi2K, srsi2D := ta.StochRSI(e.Close, 30, 30, 5, 5) mfi := ta.MFI(e, 14) mfi30 := ta.MFI(e, 30) rmi := ta.RMI(e.Close, 14, 3) rmi30 := ta.RMI(e.Close, 30, 5) hh14 := ta.Highest(e.Close, 14) hh50 := ta.Highest(e.Close, 50) ll14 := ta.Lowest(e.Close, 14) ll50 := ta.Lowest(e.Close, 50) td := ta.TD(e.Close) // 这里的特征列数最好是4或10的倍数,否则模型端采样会有问题 feaNum := 72 arrData := make([]float64, 0, num*feaNum) offset := 0 for i := 0; i < num; i++ { closeVal := e.Close.Get(i) volMa5Val := volMa5.Get(i) volMa20Val := volMa20.Get(i) sma20Val := sma20.Get(i) vwma5Val := vwma5.Get(i) vwma20Val := vwma20.Get(i) ll14Val := ll14.Get(i) hh14Val := hh14.Get(i) kdjKVal := kdjK.Get(i) kdjK2Val := kdjK2.Get(i) arrData = append(arrData, e.High.Get(i), e.Low.Get(i), closeVal, e.Volume.Get(i), math.Log1p(e.Volume.Get(i)/volMa5Val)/3, math.Log1p(volMa5Val/volMa20Val)/3, // 5 math.Log1p(volMa20Val/volMa70.Get(i))/3, (closeVal-e.Close.Get(i+1))/closeVal, (closeVal-e.Close.Get(i+7))/closeVal, // 8 此行超出(-1,1),调用方需截断 rsi14.Get(i)/100, rsi50.Get(i)/100, // 10 (rsiMa.Get(i)-rsi14.Get(i))/100, atr14.Get(i)/closeVal, atr50.Get(i)/closeVal, sma5.Get(i)/closeVal-1, sma20Val/sma5.Get(i)-1, // 15 sma70.Get(i)/sma20Val-1, vwma5Val/closeVal-1, // 17 此行超出(-1,1),调用方需截断 vwma20Val/vwma5Val-1, // 18 此行超出(-1,1),调用方需截断 vwma70.Get(i)/vwma20Val-1, macd.Get(i)/closeVal, // 20 (macd.Get(i)-macdS.Get(i))/closeVal, macd2.Get(i)/closeVal, (macd2.Get(i)-macdS2.Get(i))/closeVal, kdjKVal/100, (kdjKVal-kdjD.Get(i))/100, // 25 kdjK2Val/100, (kdjK2Val-kdjD2.Get(i))/100, stdDev.Get(i)/closeVal, adx.Get(i)/100, adxPlus.Get(i)/100, // 30 adx2.Get(i)/100, adxPlus2.Get(i)/100, roc.Get(i)/100, // 33 此行超出(-1,1),调用方需截断 roc2.Get(i)/100, // 34 此行超出(-1,1),调用方需截断 (closeVal-bbUp.Get(i))/closeVal, (closeVal-bbDn.Get(i))/closeVal, (closeVal-bb2Up.Get(i))/closeVal, (closeVal-bb2Dn.Get(i))/closeVal, aroonUp.Get(i)/100, aroonOsc.Get(i)/100, aroonDn.Get(i)/100, aroon2Up.Get(i)/100, aroon2Osc.Get(i)/100, aroon2Dn.Get(i)/100, tnr.Get(i), tnr2.Get(i), cci.Get(i)/400, // 47 超出1,需截断 cci50.Get(i)/400, // 48 超出1,需截断 cci100.Get(i)/400, // 49 超出1,需截断 cmf.Get(i), cmf50.Get(i), kama.Get(i)/closeVal-1, kama30.Get(i)/closeVal-1, willr.Get(i)/100, willr30.Get(i)/100, (willr30.Get(i)-willr30_5.Get(i))/100, willr50.Get(i)/100, srsiK.Get(i)/100, // 58 超出1,需截断 (srsiK.Get(i)-srsiD.Get(i))/100, srsi2K.Get(i)/100, (srsi2K.Get(i)-srsi2D.Get(i))/100, mfi.Get(i)/100, mfi30.Get(i)/100, rmi.Get(i)/100, rmi30.Get(i)/100, closeVal/hh14Val, hh14Val/hh50.Get(i), ll14Val/closeVal, ll50.Get(i)/ll14Val, (hh14Val-ll14Val)/closeVal, td.Get(i)/30, ) end := len(arrData) for j := offset + 4; j < end; j++ { arrData[j] = cutRange(arrData[j], -1, 1, 0) } offset = end } res := mat.NewDense(num, feaNum, arrData) // 对前四列hlcv进行归一化 for i := 0; i < 4; i++ { col := res.ColView(i) minVal := mat.Min(col) maxVal := mat.Max(col) if minVal == maxVal { continue } for row := 0; row < num; row++ { oldValue := res.At(row, i) newValue := (oldValue - minVal) / (maxVal - minVal) res.Set(row, i, newValue) } } return res } /* GenFeaMats 生成时间、买、卖的热图 */ func GenFeaMats(graph *ta.CGraph, num, depth int, restGap bool) ([3]*mat.Dense, float64) { times := mat.NewDense(num, depth, nil) buy := mat.NewDense(num, depth, nil) sell := mat.NewDense(num, depth, nil) pens := make([]*ta.CPen, 0, num) bar := graph.Bar(graph.ParseTo) price := bar.Close pen := graph.Pens[len(graph.Pens)-1] pt := pen.End if graph.ParseTo > pt.BarId { if pen.Dirt*(price-pt.Price) > 0 { // 未合并数据和最后一笔方向相同 pen = pen.Clone() pen.End = pt.Clone().Move(graph.ParseTo, price) pens = append(pens, pen) } else { end := graph.NewPoint(-pt.Dirt, price, graph.ParseTo) newPen := graph.NewPen(pt, end) pens = append(pens, newPen, pen) } } else { pens = append(pens, pen) } for len(pens) < num && pen.Prev != nil { pens = append(pens, pen.Prev) pen = pen.Prev } minIdx := len(graph.Bars) - (graph.BarNum - pens[len(pens)-1].Start.BarId) - 1 maxEnd := len(graph.Bars) - (graph.BarNum - graph.ParseTo) bars := graph.Bars[minIdx:maxEnd] upper, lower, sumVol := price, price, float64(0) for _, b := range bars { lower = min(lower, b.Low) upper = max(upper, b.High) sumVol += b.Volume } barNum := len(bars) avgVol := sumVol / float64(barNum) sub := max(upper-price, price-lower) width := sub * 2 / float64(depth) // 每一格的价格宽度 lower = price - sub tfMSecs := min(bars[1].Time-bars[0].Time, bars[barNum-1].Time-bars[barNum-2].Time) barId, col := graph.ParseTo, num prevMS := int64(0) for _, p := range pens { penId := p.Start.BarId if barId > penId { col -= 1 if col < 0 { break } } for barId > penId { bar = graph.Bar(barId) o, h, l, c := bar.Open-lower, bar.High-lower, bar.Low-lower, bar.Close-lower if restGap && prevMS > 0 && prevMS-bar.Time > tfMSecs*3/2 { // 休市期间添加空白时间记录 lackNum := math.Round(float64(prevMS-bar.Time) / float64(tfMSecs)) for i := 0; i < depth; i++ { times.Set(col, i, lackNum) } col -= 1 if col < 0 { break } } barId -= 1 prevMS = bar.Time barUp, barDn := max(o, c), min(o, c) total := h - l + (h - barUp) + (barDn - l) if total > 0 { upRate, dnRate := (h-barUp)*2/total, (barDn-l)*2/total solidRate := (barUp - barDn) / total addToArr(times, col, barUp, h, upRate*0.5, width) addToArr(times, col, barDn, barUp, solidRate*0.5, width) addToArr(times, col, l, barDn, dnRate*0.5, width) volRate := math.Log(bar.Volume/avgVol + 1) buyRate, sellRate := 0.7, 0.3 if o > c { buyRate, sellRate = 0.3, 0.7 } addToArr(buy, col, barUp, h, volRate*upRate*0.5, width) addToArr(buy, col, barDn, barUp, volRate*solidRate*buyRate, width) addToArr(buy, col, l, barDn, volRate*dnRate*0.5, width) addToArr(sell, col, barUp, h, volRate*upRate*0.5, width) addToArr(sell, col, barDn, barUp, volRate*solidRate*sellRate, width) addToArr(sell, col, l, barDn, volRate*dnRate*0.5, width) } } } return [3]*mat.Dense{times, buy, sell}, sub / price } /* GenPenMats2 经测试,80个pen,平均需要1k根K线,最少700根,最多3k根 */ func GenPenMats2(graph *ta.CGraph, num, depth int, restGap bool) ([2]*mat.Dense, float64) { mats, rate := GenFeaMats(graph, num, depth, restGap) times, buy, sell := mats[0], mats[1], mats[2] one := mat.NewDense(num, depth, nil) two := mat.NewDense(num, depth, nil) times.Apply(func(i, j int, v float64) float64 { return revPow(v, 2, 0, 1, 0) }, times) one.Apply(func(i, j int, v float64) float64 { mid := (buy.At(i, j) + sell.At(i, j)) / 2 * (1 - v) return revPow(mid, 1.5, 0, 1, 0) }, times) two.Apply(func(i, j int, v float64) float64 { return revPow(v-sell.At(i, j), 1.5, -1, 1, 0) }, buy) return [2]*mat.Dense{one, two}, rate } func revPow(v, cutRate, minVal, maxVal, nanTo float64) float64 { if math.IsNaN(v) || math.IsInf(nanTo, 0) { if nanTo < 0 { return v } return nanTo } powered := math.Pow(math.Abs(v), 0.33) / cutRate if v >= 0 { return min(maxVal, powered) } return max(minVal, -powered) } func cutRange(v, minVal, maxVal, nanTo float64) float64 { if math.IsNaN(v) || math.IsInf(nanTo, 0) { if nanTo < 0 { return v } return nanTo } if v >= 0 { return min(maxVal, v) } return max(minVal, -v) } /* addToArr 将总值按low~high切分为格子加到arr上 */ func addToArr(m *mat.Dense, col int, low, high, total, width float64) { if high <= low || low < 0 { return } low /= width high /= width all := high - low idx, down := int(low), low _, dimCol := m.Dims() if high > float64(dimCol) { high = float64(dimCol) } for float64(idx) < high { upp := min(high, float64(idx+1)) m.Set(col, idx, (upp-down)/all*total) down = float64(idx) idx += 1 } } func getMatColSum(m *mat.Dense, col int) float64 { if col < 0 { _, colNum := m.Dims() col += colNum } arr := m.ColView(col) num := arr.Len() // res := make([]float64, 0, num) res := float64(0) for i := 0; i < num; i++ { // res = append(res, arr.AtVec(i)) res += arr.AtVec(i) } return res } func getMatRowSum(m *mat.Dense, row int) float64 { if row < 0 { rowNum, _ := m.Dims() row += rowNum } res := float64(0) arr := m.RawRowView(row) for _, v := range arr { res += v } return res } type ColSta struct { Nan int // nan数量 Min float64 Max float64 Mean float64 } func staToStr(sta *ColSta) string { return fmt.Sprintf("nan:%v, mean: %.5f, [%.5f, %.5f]", sta.Nan, sta.Mean, sta.Min, sta.Max) } func sampleArr(arr []float64) *ColSta { nanNum := 0 sum := float64(0) valid := 0 minVal, maxVal := math.MaxFloat64, -math.MaxFloat64 for _, v := range arr { if math.IsNaN(v) || math.IsInf(v, 0) { nanNum += 1 continue } sum += v valid += 1 if v < minVal { minVal = v } if v > maxVal { maxVal = v } } if valid > 0 { sum /= float64(valid) } return &ColSta{Nan: nanNum, Min: minVal, Max: maxVal, Mean: sum} } type BatchMap struct { codes []string envs []*ta.BarEnv mux sync.Mutex curBarMS int64 // 当前bar的13位毫秒时间戳 data map[string]*biz.NumArr maxSize int } func NewBatchMap(size int) *BatchMap { return &BatchMap{ data: make(map[string]*biz.NumArr), maxSize: size, } } func (b *BatchMap) Add(rsp biz.FeaFeeder_SubFeaturesServer, e *ta.BarEnv, barMs int64, items map[string]*biz.NumArr) *errs.Error { b.mux.Lock() defer b.mux.Unlock() if barMs > b.curBarMS { oldLen := len(b.codes) if oldLen > 0 { b.maxSize = max(b.maxSize, oldLen) corrs, err_ := getEnvsCorr(b.envs, 100) if err_ != nil { return errs.New(errs.CodeRunTime, err_) } b.data["corr"] = &biz.NumArr{ Data: corrs, Shape: []int32{int32(oldLen), 2}, } err_ = rsp.Send(&biz.ArrMap{ Codes: b.codes, Mats: b.data, }) if err_ != nil { return errs.New(errs.CodeRunTime, err_) } b.codes = make([]string, 0, b.maxSize) b.data = make(map[string]*biz.NumArr) b.envs = make([]*ta.BarEnv, 0, b.maxSize) } b.curBarMS = barMs } b.codes = append(b.codes, e.Symbol) b.envs = append(b.envs, e) for name, arr := range items { old, ok := b.data[name] if !ok { arr.Shape = append([]int32{1}, arr.Shape...) b.data[name] = arr } else { old.Data = append(old.Data, arr.Data...) old.Shape = append([]int32{old.Shape[0] + 1}, old.Shape[1:]...) } } return nil } func getEnvsCorr(envs []*ta.BarEnv, hisNum int) ([]float64, error) { if len(envs) < 2 { res := make([]float64, 0, len(envs)*2) for range envs { res = append(res, 0, 0) } return res, nil } _, avgs, err := utils.CalcEnvsCorr(envs, hisNum) res := make([]float64, 0, len(avgs)*2) avg := floats.Sum(avgs) / float64(len(avgs)) for _, v := range avgs { res = append(res, avg, v) } return res, err }