This commit is contained in:
2026-08-24 20:53:21 +08:00
commit 6e78bba16a
287 changed files with 32443 additions and 0 deletions

121
rpc_fea/aifea.go Normal file
View File

@@ -0,0 +1,121 @@
package rpc_fea
import (
"github.com/banbox/banbot/biz"
"github.com/banbox/banbot/btime"
"github.com/banbox/banbot/core"
"github.com/banbox/banbot/orm"
"github.com/banbox/banexg"
"github.com/banbox/banexg/errs"
"github.com/banbox/banexg/log"
utils2 "github.com/banbox/banexg/utils"
"github.com/banbox/banta"
"go.uber.org/zap"
"gonum.org/v1/gonum/mat"
"math"
)
type AIFeaState struct {
cg *banta.CGraph
cgBig *banta.CGraph
matBig [3]*mat.Dense
feasBig *mat.Dense
rateBig float64
avgCostBigs []float64 //大周期的成本价
}
func pubAiFea(exsList []*orm.ExSymbol, req *biz.SubReq, rsp biz.FeaFeeder_SubFeaturesServer) error {
var sample = int(req.Sample)
if sample <= 0 {
sample = 10
}
startDt := btime.ToDateStr(req.Start, core.DefaultDateFmt)
log.Info("start ai fea2", zap.Int("pair", len(exsList)), zap.String("start", startDt),
zap.Int("sample", sample))
var seqNum = 150 // 特征序列长度
var penNum = 120 // 笔矩阵长度
var depth = 100 // 笔矩阵深度:100个深度
tfSml, tfBig := "5m", "1h"
tfMSecs := int64(utils2.TFToSecs(tfSml) * 1000)
tfMSecsBig := int64(utils2.TFToSecs(tfBig) * 1000)
states := make(map[string]*AIFeaState)
for _, exs := range exsList {
states[exs.Symbol] = &AIFeaState{
cg: &banta.CGraph{ParseLevel: banta.ParseLevelPen, MaxPen: penNum * 3 / 2},
cgBig: &banta.CGraph{ParseLevel: banta.ParseLevelPen, MaxPen: penNum * 3 / 2},
}
}
task := &GenFeaTask{
TfSml: tfSml,
TfBig: tfBig,
WarmSml: 3000,
WarmBig: 3000,
TfMSecs: tfMSecs,
TfMSecsBig: tfMSecsBig,
NextObsNum: 30,
}
batch := NewBatchMap(len(exsList))
task.OnBar = func(e *banta.BarEnv, b *banexg.Kline, futs []*banexg.Kline) *errs.Error {
//if !core.IsWarmUp {
// log.Info("OnBar", zap.Int64("time", e.TimeStop))
//}
state := states[e.Symbol]
state.cg.AddBar(e)
state.cg.Parse()
feas := onAiFeatures(e, seqNum)
if e.BarNum%sample > 0 || len(state.cg.Pens) < penNum || state.rateBig == 0 || len(futs) == 0 {
// 预热时futs为nil
return nil
}
// 收集特征,发送到监听端
minChgVal := banta.ATR(e.High, e.Low, e.Close, 30).Get(0) * 5
futProbs := genFutProbsV1(futs, minChgVal, 0.4)
mats, rate := GenFeaMats(state.cg, penNum, depth, false)
avgCosts := avgCostN(e, 10, 20, 40, 80, 160, 320)
info := make([]float64, 0, 25)
info = append(info, float64(b.Time/1000), b.Open, b.High, b.Low, b.Close, b.Volume,
math.Log1p(rate), math.Log1p(state.rateBig))
info = append(info, avgCosts...)
info = append(info, state.avgCostBigs...)
info = append(info, futProbs[0], futProbs[1], futProbs[2])
return batch.Add(rsp, e, b.Time, map[string]*biz.NumArr{
"time": matToNumArr(mats[0]),
"buy": matToNumArr(mats[1]),
"sell": matToNumArr(mats[2]),
"feas": matToNumArr(feas),
"time2": matToNumArr(state.matBig[0]),
"buy2": matToNumArr(state.matBig[1]),
"sell2": matToNumArr(state.matBig[2]),
"feas2": matToNumArr(state.feasBig),
"info": {Data: info, Shape: []int32{int32(len(info))}},
})
}
task.OnInfoBar = func(e *banta.BarEnv, b *banexg.Kline) *errs.Error {
//if !core.IsWarmUp {
// log.Info("OnInfoBar", zap.Int64("time", e.TimeStop))
//}
state := states[e.Symbol]
state.cgBig.AddBar(e)
state.cgBig.Parse()
state.feasBig = onAiFeatures(e, seqNum)
state.avgCostBigs = avgCostN(e, 10, 20, 40, 80, 160, 320)
if len(state.cgBig.Pens) >= penNum {
state.matBig, state.rateBig = GenFeaMats(state.cgBig, penNum, depth, false)
}
return nil
}
task.OnEnvEnd = func(bar *banexg.PairTFKline, adj *orm.AdjInfo) {
if bar == nil {
return
}
state := states[bar.Symbol]
state.cg = &banta.CGraph{ParseLevel: banta.ParseLevelPen, MaxPen: penNum * 3 / 2}
state.cgBig = &banta.CGraph{ParseLevel: banta.ParseLevelPen, MaxPen: penNum * 3 / 2}
state.rateBig = 0
}
return pubFeaBase(exsList, req, task)
}

107
rpc_fea/aifea2.go Normal file
View File

@@ -0,0 +1,107 @@
package rpc_fea
import (
"github.com/banbox/banbot/biz"
"github.com/banbox/banbot/btime"
"github.com/banbox/banbot/core"
"github.com/banbox/banbot/orm"
"github.com/banbox/banexg"
"github.com/banbox/banexg/errs"
"github.com/banbox/banexg/log"
utils2 "github.com/banbox/banexg/utils"
"github.com/banbox/banta"
"go.uber.org/zap"
"gonum.org/v1/gonum/mat"
)
type AiFea2State struct {
cg *banta.CGraph
feasBig *mat.Dense
avgCostBigs []float64 //大周期的成本价
}
func pubAiFea2(exsList []*orm.ExSymbol, req *biz.SubReq, rsp biz.FeaFeeder_SubFeaturesServer) error {
var sample = int(req.Sample)
if sample <= 0 {
sample = 10
}
startDt := btime.ToDateStr(req.Start, core.DefaultDateFmt)
endDt := btime.ToDateStr(req.End, core.DefaultDateFmt)
log.Info("start ai fea2", zap.Int("pair", len(exsList)), zap.String("start", startDt),
zap.String("end", endDt), zap.Int("sample", sample))
var seqNum = 150 // 特征序列长度
var penNum = 80 // 笔矩阵长度
var depth = 100 // 笔矩阵深度:100个深度
tfSml, tfBig := "5m", "1h"
tfMSecs := int64(utils2.TFToSecs(tfSml) * 1000)
tfMSecsBig := int64(utils2.TFToSecs(tfBig) * 1000)
task := &GenFeaTask{
TfSml: tfSml,
TfBig: tfBig,
WarmSml: 2000,
WarmBig: 300,
TfMSecs: tfMSecs,
TfMSecsBig: tfMSecsBig,
NextObsNum: 30,
}
states := make(map[string]*AiFea2State)
for _, exs := range exsList {
states[exs.Symbol] = &AiFea2State{
cg: &banta.CGraph{ParseLevel: banta.ParseLevelPen, MaxPen: penNum * 3 / 2},
}
}
batch := NewBatchMap(len(exsList))
task.OnBar = func(e *banta.BarEnv, b *banexg.Kline, futs []*banexg.Kline) *errs.Error {
state := states[e.Symbol]
state.cg.AddBar(e)
state.cg.Parse()
feas := onAiFeatures2(e, 30)
if e.BarNum%sample > 0 || len(state.cg.Pens) < penNum || len(futs) == 0 || state.feasBig == nil {
// 预热时futs为nil
return nil
}
// 收集特征,发送到监听端
minChgVal := banta.ATR(e.High, e.Low, e.Close, 30).Get(0) * 5
futProbs := genFutProbsV1(futs, minChgVal, 0.4)
mats, _ := GenPenMats2(state.cg, penNum, depth, false)
avgCosts := avgCostN(e, 10, 20, 40, 80, 160, 320)
info := make([]float64, 0, 25)
info = append(info, avgCosts...)
info = append(info, state.avgCostBigs...)
return batch.Add(rsp, e, b.Time, map[string]*biz.NumArr{
"one": matToNumArr(mats[0]),
"two": matToNumArr(mats[1]),
"feas": matToNumArr(feas),
"feas2": matToNumArr(state.feasBig),
"bar": {
Data: []float64{float64(b.Time / 1000), b.Open, b.High, b.Low, b.Close, b.Volume},
Shape: []int32{6},
},
"y": {
Data: []float64{futProbs[0], futProbs[1], futProbs[2]},
Shape: []int32{3},
},
"info": {
Data: info, Shape: []int32{int32(len(info))},
},
})
}
task.OnInfoBar = func(e *banta.BarEnv, b *banexg.Kline) *errs.Error {
state := states[e.Symbol]
state.feasBig = onAiFeatures2(e, seqNum)
state.avgCostBigs = avgCostN(e, 10, 20, 40, 80, 160, 320)
return nil
}
task.OnEnvEnd = func(bar *banexg.PairTFKline, adj *orm.AdjInfo) {
if bar == nil {
return
}
state := states[bar.Symbol]
state.cg = &banta.CGraph{ParseLevel: banta.ParseLevelPen, MaxPen: penNum * 3 / 2}
}
return pubFeaBase(exsList, req, task)
}

176
rpc_fea/aifea_test.go Normal file
View File

@@ -0,0 +1,176 @@
package rpc_fea
import (
"context"
"fmt"
"github.com/banbox/banbot/biz"
"github.com/banbox/banbot/btime"
"github.com/banbox/banbot/core"
"github.com/banbox/banbot/utils"
"github.com/banbox/banexg/log"
"go.uber.org/zap"
"gonum.org/v1/gonum/mat"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"math"
"slices"
"strings"
"testing"
)
func TestDataClient(t *testing.T) {
addr := "127.0.0.1:6789"
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Error("connect grpc fail", zap.Error(err))
return
}
defer conn.Close()
c := biz.NewFeaFeederClient(conn)
ctx := context.Background()
startMS := int64(1682899200000)
endMS := int64(1682985600000)
task := "aifea2"
codes := []string{"1000BONK/USDT:USDT", "1000FLOKI/USDT:USDT", "1000PEPE/USDT:USDT", "1000RATS/USDT:USDT",
"1000SATS/USDT:USDT", "ACE/USDT:USDT", "AGIX/USDT:USDT", "ALICE/USDT:USDT", "API3/USDT:USDT",
"ARKM/USDT:USDT", "ARK/USDT:USDT", "AR/USDT:USDT", "ATA/USDT:USDT", "ATOM/USDT:USDT",
"AVAX/USDT:USDT", "AXS/USDT:USDT", "BADGER/USDT:USDT", "BAKE/USDT:USDT", "BAT/USDT:USDT",
"BEAMX/USDT:USDT", "BICO/USDT:USDT", "BNB/USDT:USDT", "BNT/USDT:USDT", "BNX/USDT:USDT",
"BTC/USDT:USDT", "CKB/USDT:USDT", "COMBO/USDT:USDT", "COMP/USDT:USDT", "CTK/USDT:USDT",
"DEFI/USDT:USDT", "DODOX/USDT:USDT", "DOT/USDT:USDT", "DUSK/USDT:USDT", "ETHW/USDT:USDT",
"FET/USDT:USDT", "FLM/USDT:USDT", "FLOW/USDT:USDT", "FRONT/USDT:USDT", "GLMR/USDT:USDT",
"GRT/USDT:USDT", "GTC/USDT:USDT", "HFT/USDT:USDT", "HIFI/USDT:USDT", "HIGH/USDT:USDT", "ID/USDT:USDT",
"ILV/USDT:USDT", "JASMY/USDT:USDT", "JOE/USDT:USDT", "JUP/USDT:USDT", "KLAY/USDT:USDT",
"KNC/USDT:USDT", "KSM/USDT:USDT", "LDO/USDT:USDT", "LINA/USDT:USDT", "LIT/USDT:USDT",
"LOOM/USDT:USDT", "LPT/USDT:USDT", "LSK/USDT:USDT", "LTC/USDT:USDT", "LUNA2/USDT:USDT",
"MAGIC/USDT:USDT", "MANA/USDT:USDT", "NEO/USDT:USDT", "OGN/USDT:USDT", "ORDI/USDT:USDT",
"RVN/USDT:USDT", "SFP/USDT:USDT", "SSV/USDT:USDT", "STRAX/USDT:USDT", "STX/USDT:USDT",
"TRB/USDT:USDT", "XLM/USDT:USDT", "XRP/USDT:USDT", "ZIL/USDT:USDT"}
// codes = []string{"AGIX/USDT:USDT"} //, "API3/USDT:USDT", "ARKM/USDT:USDT"}
matKeys := map[string]bool{"time": true, "buy": true, "sell": true, "one": true, "two": true,
"feas": true, "feas2": true}
staMap := map[string][]*ColSta{}
logSta := func(key string, sta *ColSta) {
items, ok := staMap[key]
if !ok {
items = make([]*ColSta, 0, 30)
}
items = append(items, sta)
staMap[key] = items
}
res, err := c.SubFeatures(ctx, &biz.SubReq{
Exchange: "binance",
Market: "linear",
Codes: codes,
Start: startMS,
End: endMS,
Task: task,
Sample: 1,
})
fmt.Printf("\nlisten for %s\n", codes)
if err != nil {
log.Error("call SubFeatures fail", zap.Error(err))
return
}
for {
fea, err := res.Recv()
if err != nil {
if strings.Contains(err.Error(), "EOF") {
log.Info("done", zap.Strings("code", codes))
} else {
log.Error("receive feature fail", zap.Strings("code", codes), zap.Error(err))
}
break
}
for key, item := range fea.Mats {
if _, ok := matKeys[key]; ok {
row, col := int(item.Shape[0]), int(item.Shape[1])
arr := mat.NewDense(row, col, item.Data)
for j := 0; j < col; j++ {
column := make([]float64, row)
for i := 0; i < row; i++ {
column[i] = arr.At(i, j)
}
sta := sampleArr(column)
if sta.Nan > 0 || sta.Min < -1 || sta.Max > 1 {
fmt.Printf("\n%v col %v: %v", key, j, staToStr(sta))
}
logSta(fmt.Sprintf("%s_%v", key, j), sta)
}
} else {
sta := sampleArr(item.Data)
if sta.Nan > 0 || sta.Min < -1 || sta.Max > 1 {
fmt.Printf("\n%v: %v", key, staToStr(sta))
}
logSta(key, sta)
}
}
break
}
keys := utils.KeysOfMap(staMap)
slices.Sort(keys)
fmt.Printf("\n\n\n sta cross all codes:\n")
for _, key := range keys {
items, _ := staMap[key]
nan := 0
sum, minVal, maxVal := float64(0), math.MaxFloat64, -math.MaxFloat64
for _, it := range items {
sum += it.Mean
nan += it.Nan
minVal = min(minVal, it.Min)
maxVal = max(maxVal, it.Max)
}
sta := &ColSta{Nan: nan, Min: minVal, Max: maxVal, Mean: sum / float64(len(items))}
if sta.Nan > 0 || sta.Min < -1 || sta.Max > 1 {
fmt.Printf("\n%v: %v", key, staToStr(sta))
}
}
}
func TestAiFea2(t *testing.T) {
addr := "127.0.0.1:6789"
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Error("connect grpc fail", zap.Error(err))
return
}
defer conn.Close()
c := biz.NewFeaFeederClient(conn)
ctx := context.Background()
startMS := int64(1682899200000)
endMS := int64(1682985600000)
task := "aifea2"
code := "AGIX/USDT:USDT"
res, err := c.SubFeatures(ctx, &biz.SubReq{
Exchange: "binance",
Market: "linear",
Codes: []string{code},
Start: startMS,
End: endMS,
Task: task,
Sample: 1,
})
fmt.Printf("\nlisten for %s\n", code)
if err != nil {
log.Error("call SubFeatures fail", zap.Error(err))
return
}
for {
fea, err := res.Recv()
if err != nil {
if strings.Contains(err.Error(), "EOF") {
log.Info("done", zap.String("code", code))
} else {
log.Error("receive feature fail", zap.String("code", code), zap.Error(err))
}
break
}
a := staToStr(sampleArr(fea.Mats["one"].Data))
b := staToStr(sampleArr(fea.Mats["two"].Data))
cc := staToStr(sampleArr(fea.Mats["feas"].Data))
d := staToStr(sampleArr(fea.Mats["feas2"].Data))
bar := fea.Mats["bar"].Data
curDate := btime.ToDateStr(int64(bar[0]), core.DefaultDateFmt)
fmt.Printf("%s, %.5f, %s, %s, %s, %s\n", curDate, bar[4], a, b, cc, d)
}
}

745
rpc_fea/common.go Normal file
View File

@@ -0,0 +1,745 @@
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
}

124
rpc_fea/fea_base.go Normal file
View File

@@ -0,0 +1,124 @@
package rpc_fea
import (
"github.com/banbox/banbot/biz"
"github.com/banbox/banbot/orm"
"github.com/banbox/banexg"
"github.com/banbox/banexg/errs"
"github.com/banbox/banta"
)
type GenFeaTask struct {
TfSml string
TfBig string
WarmSml int
WarmBig int
TfMSecs int64
TfMSecsBig int64
NextObsNum int
OnBar func(e *banta.BarEnv, b *banexg.Kline, futs []*banexg.Kline) *errs.Error
OnInfoBar func(e *banta.BarEnv, b *banexg.Kline) *errs.Error
OnEnvEnd func(bar *banexg.PairTFKline, adj *orm.AdjInfo)
}
type CodeState struct {
env *banta.BarEnv
envBig *banta.BarEnv
}
func pubFeaBase(exsList []*orm.ExSymbol, req *biz.SubReq, task *GenFeaTask) error {
var states = make(map[string]*CodeState)
for _, exs := range exsList {
states[exs.Symbol] = &CodeState{
env: &banta.BarEnv{
TimeFrame: task.TfSml,
TFMSecs: task.TfMSecs,
Exchange: exs.Exchange,
MarketType: exs.Market,
Symbol: exs.Symbol,
MaxCache: 1000,
},
envBig: &banta.BarEnv{
TimeFrame: task.TfBig,
TFMSecs: task.TfMSecsBig,
Exchange: exs.Exchange,
MarketType: exs.Market,
Symbol: exs.Symbol,
MaxCache: 500,
},
}
}
var outErr *errs.Error
verCh := make(chan int, 5)
stopWithErr := func(err error) {
outErr = errs.New(errs.CodeRunTime, err)
select {
case verCh <- -1:
default:
}
}
onData := func(evt *orm.DataSeries, nexts []*orm.DataSeries) {
bar, err_ := orm.AsKline(evt)
if err_ != nil {
stopWithErr(err_)
return
}
state := states[bar.Symbol]
var err *errs.Error
if bar.TimeFrame == task.TfSml {
futs := make([]*banexg.Kline, 0, len(nexts))
for _, n := range nexts {
nextBar, convErr := orm.AsKline(n)
if convErr != nil {
stopWithErr(convErr)
return
}
futs = append(futs, &nextBar.Kline)
}
state.env.OnBar(bar.Time, bar.Open, bar.High, bar.Low, bar.Close, bar.Volume,
bar.Quote, bar.BuyVolume, bar.TradeNum)
err = task.OnBar(state.env, &bar.Kline, futs)
} else {
state.envBig.OnBar(bar.Time, bar.Open, bar.High, bar.Low, bar.Close, bar.Volume,
bar.Quote, bar.BuyVolume, bar.TradeNum)
err = task.OnInfoBar(state.envBig, &bar.Kline)
}
if err != nil {
outErr = err
verCh <- -1
}
}
err := biz.RunHistSeries(&biz.RunHistSeriesArgs{
ExsList: exsList,
Start: req.Start,
End: req.End,
ViewNextNum: task.NextObsNum,
TfWarms: map[string]int{
task.TfSml: task.WarmSml,
task.TfBig: task.WarmBig,
},
OnEnvEnd: func(evt *orm.DataSeries) {
if evt == nil {
task.OnEnvEnd(nil, nil)
return
}
bar, err_ := orm.AsKline(evt)
if err_ != nil {
stopWithErr(err_)
return
}
if bar.PairTFKline != nil {
state := states[bar.Symbol]
state.env.Reset()
state.envBig.Reset()
}
task.OnEnvEnd(bar.PairTFKline, bar.Adj)
},
VerCh: verCh,
OnData: onData,
})
if err != nil {
return err
}
return outErr
}

107
rpc_fea/fea_times.go Normal file
View File

@@ -0,0 +1,107 @@
package rpc_fea
import (
"github.com/banbox/banbot/biz"
"github.com/banbox/banbot/btime"
"github.com/banbox/banbot/core"
"github.com/banbox/banbot/orm"
"github.com/banbox/banexg"
"github.com/banbox/banexg/errs"
"github.com/banbox/banexg/log"
utils2 "github.com/banbox/banexg/utils"
"github.com/banbox/banta"
"go.uber.org/zap"
"gonum.org/v1/gonum/mat"
)
func pubFeaTimes(exsList []*orm.ExSymbol, req *biz.SubReq, rsp biz.FeaFeeder_SubFeaturesServer) error {
var sample = int(req.Sample)
if sample <= 0 {
sample = 10
}
startDt := btime.ToDateStr(req.Start, core.DefaultDateFmt)
log.Info("start fea_times", zap.Int("pair", len(exsList)), zap.String("start", startDt),
zap.Int("sample", sample))
tfSml, tfBig := "5m", "1h"
tfMSecs := int64(utils2.TFToSecs(tfSml) * 1000)
tfMSecsBig := int64(utils2.TFToSecs(tfBig) * 1000)
seqNum := 150 // 特征的历史长度
var feasBig *mat.Dense
var avgCostBigs []float64 //大周期的成本价
task := &GenFeaTask{
TfSml: tfSml,
TfBig: tfBig,
WarmSml: 500,
WarmBig: 500,
TfMSecs: tfMSecs,
TfMSecsBig: tfMSecsBig,
NextObsNum: 30, // 预测后n个bar的行情
}
batch := NewBatchMap(len(exsList))
//taskId := orm.GetTaskID("")
//outDir := fmt.Sprintf("%s/backtest/task_%d", config.GetDataDir(), taskId)
//var outputs []string
task.OnBar = func(e *banta.BarEnv, b *banexg.Kline, futs []*banexg.Kline) *errs.Error {
feas := onAiFeatures(e, seqNum)
if e.BarNum%sample > 0 || feasBig == nil {
return nil
}
atr30 := banta.ATR(e.High, e.Low, e.Close, 30).Get(0)
minChgVal := atr30 * 5
futProbs := genFutProbsV1(futs, minChgVal, 0.4)
//if !core.IsWarmUp {
// curDate := btime.ToDateStr(e.TimeStart, core.DefaultDateFmt)
// curCloseVal := e.Close.Get(0)
// n30Close := futs[len(futs)-1].Close
// row := fmt.Sprintf("%s, %.5f, %.5f, %.4f, %.4f, %.4f, %.7f", curDate, curCloseVal, n30Close,
// futProbs[0], futProbs[1], futProbs[2], atr30)
// outputs = append(outputs, row)
//}
avgCosts := avgCostN(e, 10, 20, 40, 80, 160, 320)
info := make([]float64, 0, 12)
info = append(info, avgCosts...)
info = append(info, avgCostBigs...)
return batch.Add(rsp, e, b.Time, map[string]*biz.NumArr{
"feas": matToNumArr(feas),
"feas2": matToNumArr(feasBig),
"bar": {
Data: []float64{float64(b.Time / 1000), b.Open, b.High, b.Low, b.Close, b.Volume},
Shape: []int32{6},
},
"y": {
Data: []float64{futProbs[0], futProbs[1], futProbs[2]},
Shape: []int32{3},
},
"info": {Data: info, Shape: []int32{int32(len(info))}},
})
}
task.OnInfoBar = func(e *banta.BarEnv, b *banexg.Kline) *errs.Error {
feasBig = onAiFeatures(e, seqNum)
avgCostBigs = avgCostN(e, 10, 20, 40, 80, 160, 320)
return nil
}
task.OnEnvEnd = func(bar *banexg.PairTFKline, adj *orm.AdjInfo) {
//name := "fea_times"
//outPath := fmt.Sprintf("%s/%s.log", outDir, name)
//file, err := os.Create(outPath)
//if err != nil {
// log.Error("create strategy output file fail", zap.String("name", name), zap.Error(err))
// return
//}
//_, err = file.WriteString(strings.Join(outputs, "\n"))
//if err != nil {
// log.Error("write strategy output fail", zap.String("name", name), zap.Error(err))
//}
//err = file.Close()
//if err != nil {
// log.Error("close strategy output fail", zap.String("name", name), zap.Error(err))
//}
}
return pubFeaBase(exsList, req, task)
}

383
rpc_fea/fea_trade1.go Normal file
View File

@@ -0,0 +1,383 @@
package rpc_fea
import (
"context"
"fmt"
"github.com/banbox/banbot/biz"
"github.com/banbox/banbot/config"
"github.com/banbox/banbot/core"
"github.com/banbox/banbot/orm/ormo"
"github.com/banbox/banbot/strat"
"github.com/banbox/banexg"
"github.com/banbox/banexg/log"
ta "github.com/banbox/banta"
"go.uber.org/zap"
"gonum.org/v1/gonum/mat"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"math"
)
type AIMore struct {
cg *ta.CGraph
cgBig *ta.CGraph
matBig [3]*mat.Dense // 大周期行情
feasBig *mat.Dense // 大周期特征
mat [3]*mat.Dense // 小周期行情
feas *mat.Dense // 小周期特征
rateBig float64 // 大周期边界倍率
rate float64 // 边界倍率
costBigs []float64 // 大周期成本价
costs []float64 // 小周期成本价
info []float64
hisProbs []float64 // 历史行情概率
diff []float64 // 历史行情概率与真实的差
pftDoneLong float64 // 已平仓的做多利润
pftDoneShort float64 // 已平仓做空利润
pftUPolLong float64 // 未平仓做多利润
pftUPolShort float64 // 未平仓做空利润
atr float64
trues []float64 // 记录最近probs是否相同
age int // 已持仓bar数
}
func AITrade(pol *config.RunPolicyConfig) *strat.TradeStrat {
var seqNum = 150 // 特征序列长度
var penNum = 120 // 笔矩阵长度
var depth = 100 // 笔矩阵深度
var hisProbLen = 100 // 历史行情置信度长度
var maxOdNum = 1.0 // 单币种单方向最多开1单
var nextObsNum = 30 // 预测后n个bar的收益
var diffLen = hisProbLen - nextObsNum
var indsDepth = 30*2 + 14
const maxMsgSize = 100 * 1024 * 1024
const maxHoldAge = 300 // 持仓age超过此配置,强制平仓
addr := "192.168.7.253:8080"
creds := grpc.WithTransportCredentials(insecure.NewCredentials())
conn, err_ := grpc.NewClient(addr, creds, grpc.WithDefaultCallOptions(
grpc.MaxCallSendMsgSize(maxMsgSize),
grpc.MaxCallRecvMsgSize(maxMsgSize),
))
if err_ != nil {
panic(err_)
}
client := biz.NewAInferClient(conn)
return &strat.TradeStrat{
BatchInOut: true,
//DrawDownExit: true,
WarmupNum: 3000,
OnStartUp: func(s *strat.StratJob) {
s.More = &AIMore{
cg: &ta.CGraph{ParseLevel: ta.ParseLevelPen},
cgBig: &ta.CGraph{ParseLevel: ta.ParseLevelPen},
}
},
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
return []*strat.PairSub{
{"_cur_", "1h", 3000},
}
},
OnBar: func(s *strat.StratJob) {
m, _ := s.More.(*AIMore)
e := s.Env
//if !core.IsWarmUp {
// log.Info("OnBar", zap.Int64("time", e.TimeStop))
// defer log.Info("OnBarEnd", zap.Int64("time", e.TimeStop))
//}
m.cg.AddBar(e)
m.cg.Parse()
m.feas = onAiFeatures(e, seqNum)
if len(m.cg.Pens) < 10 {
return
}
m.mat, m.rate = GenFeaMats(m.cg, penNum, depth, false)
m.costs = avgCostN(e, 10, 20, 40, 80, 160, 320)
o, h, l, c, v := e.Open.Get(0), e.High.Get(0), e.Low.Get(0), e.Close.Get(0), e.Volume.Get(0)
info := []float64{float64(e.TimeStart / 1000), o, h, l, c, v,
math.Log1p(m.rate), math.Log1p(m.rateBig)}
info = append(info, m.costs...)
info = append(info, m.costBigs...)
info = append(info, 0, 0, 0)
m.info = info
m.atr = ta.ATR(e.High, e.Low, e.Close, 30).Get(0)
m.pftUPolShort = 0
m.pftUPolLong = 0
if len(s.LongOrders) == 0 && len(s.ShortOrders) == 0 {
m.pftDoneLong = 0
m.pftDoneShort = 0
} else {
for _, od := range s.LongOrders {
if od.Status >= ormo.InOutStatusFullEnter {
m.pftUPolLong += od.ProfitRate
}
}
for _, od := range s.ShortOrders {
if od.Status >= ormo.InOutStatusFullEnter {
m.pftUPolShort += od.ProfitRate
}
}
}
},
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
//if !core.IsWarmUp {
// log.Info("OnInfoBar", zap.Int64("time", e.TimeStop))
// defer log.Info("OnInfoBarEnd", zap.Int64("time", e.TimeStop))
//}
m, _ := s.More.(*AIMore)
m.cgBig.AddBar(e)
m.cgBig.Parse()
m.feasBig = onAiFeatures(e, seqNum)
m.costBigs = avgCostN(e, 10, 20, 40, 80, 160, 320)
if len(m.cgBig.Pens) >= penNum {
m.matBig, m.rateBig = GenFeaMats(m.cgBig, penNum, depth, false)
}
},
OnBatchJobs: func(jobs []*strat.StratJob) {
var valids []*strat.StratJob
var time1, buy1, sell1, feas1, time2, buy2, sell2, feas2, info []float64
var graphLen, graphDepth, feasLen, feasDepth, infoDepth int
for _, j := range jobs {
m, _ := j.More.(*AIMore)
if m.matBig[0] == nil || m.mat[0] == nil || len(m.info) != 23 {
continue
}
if len(valids) == 0 {
graphLen, graphDepth = m.mat[0].Dims()
feasLen, feasDepth = m.feas.Dims()
infoDepth = len(m.info)
}
valids = append(valids, j)
time1 = append(time1, m.mat[0].RawMatrix().Data...)
buy1 = append(buy1, m.mat[1].RawMatrix().Data...)
sell1 = append(sell1, m.mat[2].RawMatrix().Data...)
feas1 = append(feas1, m.feas.RawMatrix().Data...)
time2 = append(time2, m.matBig[0].RawMatrix().Data...)
buy2 = append(buy2, m.matBig[1].RawMatrix().Data...)
sell2 = append(sell2, m.matBig[2].RawMatrix().Data...)
feas2 = append(feas2, m.feasBig.RawMatrix().Data...)
info = append(info, m.info...)
}
//if !core.IsWarmUp {
// log.Info("OnBatchJobs", zap.Int64("time", timeStop))
// defer log.Info("OnBatchJobs", zap.Int64("time", timeStop))
//}
if len(valids) == 0 {
return
}
bSize := len(valids)
klineShape := []int32{int32(bSize), int32(graphLen), int32(graphDepth)}
feaShape := []int32{int32(bSize), int32(feasLen), int32(feasDepth)}
ctx := context.Background()
trend, err_ := client.Trend(ctx, &biz.ArrMap{
Mats: map[string]*biz.NumArr{
"time": {Data: time1, Shape: klineShape},
"buy": {Data: buy1, Shape: klineShape},
"sell": {Data: sell1, Shape: klineShape},
"feas": {Data: feas1, Shape: feaShape},
"time2": {Data: time2, Shape: klineShape},
"buy2": {Data: buy2, Shape: klineShape},
"sell2": {Data: sell2, Shape: klineShape},
"feas2": {Data: feas2, Shape: feaShape},
"info": {Data: info, Shape: []int32{int32(bSize), int32(infoDepth)}},
},
})
if err_ != nil {
log.Panic("call ai trend fail", zap.Int("bSize", bSize), zap.Error(err_))
}
probs := trend.Mats["probs"].Data
if len(probs) != bSize*3 {
panic(fmt.Sprintf("probs length error, expect: %v, got: %v", len(probs), bSize*3))
}
infoMat := trend.Mats["info"]
rawInfo := infoMat.Data
rawInfoDepth := int(infoMat.Shape[1])
hisMin := hisProbLen * 3
var his, diff, inds []float64
info = nil
var items []*strat.StratJob
for i, j := range valids {
m, _ := j.More.(*AIMore)
curProbs := probs[i*3 : (i+1)*3]
m.hisProbs = append(m.hisProbs, curProbs...)
if len(m.hisProbs) > nextObsNum*3 {
start := len(m.hisProbs) - (nextObsNum+1)*3
p30Preds := m.hisProbs[start : start+3]
p30Trues := genCurFutProbs(j.Env)
m.diff = append(m.diff, math.Abs(p30Trues[0]-p30Preds[0]),
math.Abs(p30Trues[1]-p30Preds[1]),
math.Abs(p30Trues[2]-p30Preds[2]))
m.logTrue([3]float64{p30Trues[0], p30Trues[1], p30Trues[2]}, p30Preds)
}
if len(m.hisProbs) >= hisMin {
m.hisProbs = m.hisProbs[len(m.hisProbs)-hisMin:]
items = append(items, j)
his = append(his, m.hisProbs...)
m.diff = m.diff[len(m.diff)-diffLen*3:]
diff = append(diff, m.diff...)
m.trues = m.trues[len(m.trues)-diffLen:]
inds = append(inds, m.feas.RawRowView(feasLen-1)...)
inds = append(inds, m.feasBig.RawRowView(feasLen-1)...)
inds = append(inds, rawInfo[i*rawInfoDepth:(i+1)*rawInfoDepth]...)
info = append(info, m.pftDoneLong/maxOdNum, m.pftDoneShort/maxOdNum,
m.pftUPolLong/maxOdNum, m.pftUPolShort/maxOdNum,
float64(len(j.LongOrders))/maxOdNum, float64(len(j.ShortOrders))/maxOdNum,
avgArr(m.trues),
)
if m.age > 0 && len(j.LongOrders) == 0 && len(j.ShortOrders) == 0 {
m.age = 0
}
}
}
bSize = len(items)
if bSize == 0 {
return
}
trade, err_ := client.Trade(ctx, &biz.ArrMap{
Mats: map[string]*biz.NumArr{
"his": {Data: his, Shape: []int32{int32(bSize), int32(hisProbLen), 3}},
"diff": {Data: diff, Shape: []int32{int32(bSize), int32(diffLen), 3}},
"inds": {Data: inds, Shape: []int32{int32(bSize), int32(indsDepth)}},
"info": {Data: info, Shape: []int32{int32(bSize), 7}},
},
})
if err_ != nil {
panic(err_)
}
actions := trade.Mats["actions"].Data
for i, j := range items {
m, _ := j.More.(*AIMore)
act := int(math.Round(actions[i]))
truncated := false
if len(j.LongOrders) > 0 || len(j.ShortOrders) > 0 {
m.age += 1
if m.age >= maxHoldAge {
truncated = true
}
}
if (act == 3 || truncated) && len(j.LongOrders) > 0 {
_ = j.CloseOrders(&strat.ExitReq{
Tag: "exit_long",
Dirt: core.OdDirtLong,
})
}
if truncated || act == 4 && len(j.ShortOrders) > 0 {
_ = j.CloseOrders(&strat.ExitReq{
Tag: "exit_short",
Dirt: core.OdDirtShort,
})
} else if act == 1 && len(j.LongOrders) < int(maxOdNum) {
//m, _ := j.More.(*AIMore)
_ = j.OpenOrder(&strat.EnterReq{
Tag: "long",
//StopLossVal: m.atr * 10,
})
} else if act == 2 && len(j.ShortOrders) < int(maxOdNum) {
//m, _ := j.More.(*AIMore)
_ = j.OpenOrder(&strat.EnterReq{
Tag: "short",
Short: true,
//StopLossVal: m.atr * 10,
})
}
}
},
//OnCheckExit: func(s *strategy.StratJob, od *ormo.InOutOrder) *strategy.ExitReq {
// slVal := od.GetInfoFloat64(orm.OdInfoStopLossVal)
// holdNum := int((s.Env.TimeStop - od.EnterAt) / s.Env.TFMSecs)
// rate := math.Abs(s.Env.Close.Get(0)-od.InitPrice) / slVal
// if holdNum > nextObsNum && (od.ProfitRate < 0 || rate < 0.3) {
// // 止损:持仓30个bar && (亏损状态 or 盈利不足最小预期30%)
// return &strategy.ExitReq{
// Tag: "exit_bad",
// OrderID: od.ID,
// }
// }
// return nil
//},
GetDrawDownExitRate: func(s *strat.StratJob, od *ormo.InOutOrder, maxChg float64) float64 {
sl := od.GetStopLoss()
if sl == nil || sl.Range == 0 {
return 0
}
bestRate := maxChg * od.InitPrice / sl.Range
if bestRate > 1.2 {
// 最大盈利是止损区间2倍,以30%回撤止盈
return 0.3
}
return 0
},
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
m, _ := s.More.(*AIMore)
if chgType == strat.OdChgExitFill {
if od.Short {
m.pftDoneShort += od.ProfitRate
} else {
m.pftDoneLong += od.ProfitRate
}
}
},
}
}
func compareProb(trues [3]float64, preds []float64) float64 {
var mx, maxv = 0, trues[0]
for i := 1; i < 3; i++ {
if trues[i] > maxv {
maxv = trues[i]
mx = i
}
}
var argMaxA = mx
mx, maxv = 0, preds[0]
for i := 1; i < 3; i++ {
if preds[i] > maxv {
maxv = preds[i]
mx = i
}
}
if argMaxA == mx {
return 1
} else {
return 0
}
}
func (m *AIMore) logTrue(trues [3]float64, preds []float64) {
val := compareProb(trues, preds)
m.trues = append(m.trues, val)
}
func genCurFutProbs(e *ta.BarEnv) [3]float64 {
var nextObsNum = 30 // 预测后n个bar的收益
var bars = make([]*banexg.Kline, 0, nextObsNum)
// opens等都是逆序的,第0个是最新的
opens := e.Open.Range(0, nextObsNum)
highs := e.High.Range(0, nextObsNum)
lows := e.Low.Range(0, nextObsNum)
closes := e.Close.Range(0, nextObsNum)
vols := e.Volume.Range(0, nextObsNum)
for i := len(opens) - 1; i >= 0; i-- {
// 从后往前,即按时间顺序
bars = append(bars, &banexg.Kline{
Open: opens[i],
High: highs[i],
Low: lows[i],
Close: closes[i],
Volume: vols[i],
})
}
minChgVal := ta.ATR(e.High, e.Low, e.Close, 30).Get(nextObsNum) * 5
return genFutProbsV1(bars, minChgVal, 0.4)
}
func avgArr(data []float64) float64 {
if len(data) == 0 {
return 0
}
var sum = float64(0)
for _, v := range data {
sum += v
}
return sum / float64(len(data))
}

324
rpc_fea/fea_trade2.go Normal file
View File

@@ -0,0 +1,324 @@
package rpc_fea
import (
"context"
"fmt"
"github.com/banbox/banbot/biz"
"github.com/banbox/banbot/config"
"github.com/banbox/banbot/core"
"github.com/banbox/banbot/orm/ormo"
"github.com/banbox/banbot/strat"
"github.com/banbox/banexg/log"
ta "github.com/banbox/banta"
"go.uber.org/zap"
"gonum.org/v1/gonum/mat"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"math"
)
type AIMore2 struct {
cg *ta.CGraph
feasBig *mat.Dense // 大周期特征
mat [2]*mat.Dense // 小周期行情
feas *mat.Dense // 小周期特征
costBigs []float64 // 大周期成本价
costs []float64 // 小周期成本价
info []float64
hisProbs []float64 // 历史行情概率
diff []float64 // 历史行情概率与真实的差
pftDoneLong float64 // 已平仓的做多利润
pftDoneShort float64 // 已平仓做空利润
pftUPolLong float64 // 未平仓做多利润
pftUPolShort float64 // 未平仓做空利润
atr float64
trues []float64 // 记录最近probs是否相同
age int // 已持仓bar数
}
func AITrade2(pol *config.RunPolicyConfig) *strat.TradeStrat {
var seqNum = 150 // 特征序列长度
var penNum = 120 // 笔矩阵长度
var depth = 100 // 笔矩阵深度
var hisProbLen = 100 // 历史行情置信度长度
var maxOdNum = 1.0 // 单币种单方向最多开1单
var nextObsNum = 30 // 预测后n个bar的收益
var diffLen = hisProbLen - nextObsNum
var indsDepth = 72 + 12
const maxMsgSize = 100 * 1024 * 1024
const maxHoldAge = 100 // 持仓age超过此配置,强制平仓
addr := "192.168.7.253:8080"
creds := grpc.WithTransportCredentials(insecure.NewCredentials())
conn, err_ := grpc.NewClient(addr, creds, grpc.WithDefaultCallOptions(
grpc.MaxCallSendMsgSize(maxMsgSize),
grpc.MaxCallRecvMsgSize(maxMsgSize),
))
if err_ != nil {
panic(err_)
}
client := biz.NewAInferClient(conn)
return &strat.TradeStrat{
BatchInOut: true,
DrawDownExit: true,
WarmupNum: 3000,
OnStartUp: func(s *strat.StratJob) {
s.More = &AIMore2{
cg: &ta.CGraph{ParseLevel: ta.ParseLevelPen, MaxPen: penNum * 3 / 2},
}
},
OnPairInfos: func(s *strat.StratJob) []*strat.PairSub {
return []*strat.PairSub{
{"_cur_", "1h", 300},
}
},
OnBar: func(s *strat.StratJob) {
m, _ := s.More.(*AIMore2)
e := s.Env
m.cg.AddBar(e)
m.cg.Parse()
m.feas = onAiFeatures2(e, 30)
if len(m.cg.Pens) < 10 {
return
}
m.mat, _ = GenPenMats2(m.cg, penNum, depth, false)
m.costs = avgCostN(e, 10, 20, 40, 80, 160, 320)
o, h, l, c, v := e.Open.Get(0), e.High.Get(0), e.Low.Get(0), e.Close.Get(0), e.Volume.Get(0)
info := make([]float64, 0, 20)
info = append(info, float64(e.TimeStart/1000), o, h, l, c, v)
info = append(info, m.costs...)
info = append(info, m.costBigs...)
m.info = info
m.atr = ta.ATR(e.High, e.Low, e.Close, 30).Get(0)
m.pftUPolShort = 0
m.pftUPolLong = 0
if len(s.LongOrders) == 0 && len(s.ShortOrders) == 0 {
m.pftDoneLong = 0
m.pftDoneShort = 0
} else {
for _, od := range s.LongOrders {
if od.Status >= ormo.InOutStatusFullEnter {
m.pftUPolLong += od.ProfitRate
}
}
for _, od := range s.ShortOrders {
if od.Status >= ormo.InOutStatusFullEnter {
m.pftUPolShort += od.ProfitRate
}
}
}
//if !s.IsWarmUp {
// curDate := btime.ToDateStr(e.TimeStart, core.DefaultDateFmt)
// curCloseVal := e.Close.Get(0)
// a := staToStr(sampleArr(m.mat[0].RawMatrix().Data))
// b := staToStr(sampleArr(m.mat[1].RawMatrix().Data))
// cc := staToStr(sampleArr(m.feas.RawMatrix().Data))
// d := staToStr(sampleArr(m.feasBig.RawMatrix().Data))
// row := fmt.Sprintf("%s, %.5f, %s, %s, %s, %s", curDate, curCloseVal, a, b, cc, d)
// s.Strat.Outputs = append(s.Strat.Outputs, row)
//}
},
OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) {
m, _ := s.More.(*AIMore2)
m.feasBig = onAiFeatures2(e, seqNum)
m.costBigs = avgCostN(e, 10, 20, 40, 80, 160, 320)
},
OnBatchJobs: func(jobs []*strat.StratJob) {
var valids []*strat.StratJob
var one, two, feas1, feas2, bars, info []float64
var graphLen, graphDepth, feasLen, feasLen2, feasDepth, infoDepth int
for _, j := range jobs {
m, _ := j.More.(*AIMore2)
if m.mat[0] == nil || len(m.info) != 18 {
continue
}
if len(valids) == 0 {
graphLen, graphDepth = m.mat[0].Dims()
feasLen, feasDepth = m.feas.Dims()
feasLen2, _ = m.feasBig.Dims()
infoDepth = len(m.info) - 6
}
valids = append(valids, j)
one = append(one, m.mat[0].RawMatrix().Data...)
two = append(two, m.mat[1].RawMatrix().Data...)
feas1 = append(feas1, m.feas.RawMatrix().Data...)
feas2 = append(feas2, m.feasBig.RawMatrix().Data...)
bars = append(bars, m.info[:6]...)
info = append(info, m.info[6:]...)
}
if len(valids) == 0 {
return
}
bSize := len(valids)
klineShape := []int32{int32(bSize), int32(graphLen), int32(graphDepth)}
feaShape1 := []int32{int32(bSize), int32(feasLen), int32(feasDepth)}
feaShape2 := []int32{int32(bSize), int32(feasLen2), int32(feasDepth)}
ctx := context.Background()
trend, err_ := client.Trend(ctx, &biz.ArrMap{
Mats: map[string]*biz.NumArr{
"one": {Data: one, Shape: klineShape},
"two": {Data: two, Shape: klineShape},
"feas": {Data: feas1, Shape: feaShape1},
"feas2": {Data: feas2, Shape: feaShape2},
"bar": {Data: bars, Shape: []int32{int32(bSize), 6}},
"info": {Data: info, Shape: []int32{int32(bSize), int32(infoDepth)}},
},
})
if err_ != nil {
log.Panic("call ai trend fail", zap.Int("bSize", bSize), zap.Error(err_))
}
probs := trend.Mats["probs"].Data
outDim := 12
if len(probs) != bSize*outDim {
panic(fmt.Sprintf("probs length error, expect: %v, got: %v", bSize*outDim, len(probs)))
}
infoMat := trend.Mats["info"]
rawInfo := infoMat.Data
rawInfoDepth := int(infoMat.Shape[1])
hisMin := hisProbLen * outDim
var his, diff, inds []float64
info = nil
var items []*strat.StratJob
for i, j := range valids {
m, _ := j.More.(*AIMore2)
curProbs := probs[i*outDim : (i+1)*outDim]
m.hisProbs = append(m.hisProbs, curProbs...)
if len(m.hisProbs) > nextObsNum*outDim {
start := len(m.hisProbs) - (nextObsNum+1)*outDim
p30Preds := m.hisProbs[start : start+outDim]
p30Trues := genCurFutProbs(j.Env)
m.diff = append(m.diff, math.Abs(p30Trues[0]-p30Preds[0]),
math.Abs(p30Trues[1]-p30Preds[1]),
math.Abs(p30Trues[2]-p30Preds[2]),
math.Abs(p30Trues[0]-p30Preds[3]),
math.Abs(p30Trues[1]-p30Preds[4]),
math.Abs(p30Trues[2]-p30Preds[5]),
math.Abs(p30Trues[0]-p30Preds[6]),
math.Abs(p30Trues[1]-p30Preds[7]),
math.Abs(p30Trues[2]-p30Preds[8]),
math.Abs(p30Trues[0]-p30Preds[9]),
math.Abs(p30Trues[1]-p30Preds[10]),
math.Abs(p30Trues[2]-p30Preds[11]))
m.logTrue([3]float64{p30Trues[0], p30Trues[1], p30Trues[2]}, p30Preds)
}
if len(m.hisProbs) >= hisMin {
m.hisProbs = m.hisProbs[len(m.hisProbs)-hisMin:]
items = append(items, j)
his = append(his, m.hisProbs...)
m.diff = m.diff[len(m.diff)-diffLen*outDim:]
diff = append(diff, m.diff...)
m.trues = m.trues[len(m.trues)-diffLen:]
inds = append(inds, m.feasBig.RawRowView(feasLen-1)...)
inds = append(inds, rawInfo[i*rawInfoDepth:(i+1)*rawInfoDepth]...)
info = append(info, (m.pftDoneLong+m.pftUPolLong)/maxOdNum,
(m.pftDoneShort+m.pftUPolShort)/maxOdNum,
float64(len(j.LongOrders))/maxOdNum, float64(len(j.ShortOrders))/maxOdNum,
avgArr(m.trues),
)
if m.age > 0 && len(j.LongOrders) == 0 && len(j.ShortOrders) == 0 {
m.age = 0
}
}
}
bSize = len(items)
if bSize == 0 {
return
}
trade, err_ := client.Trade(ctx, &biz.ArrMap{
Mats: map[string]*biz.NumArr{
"his": {Data: his, Shape: []int32{int32(bSize), int32(hisProbLen), int32(outDim)}},
"diff": {Data: diff, Shape: []int32{int32(bSize), int32(diffLen), int32(outDim)}},
"inds": {Data: inds, Shape: []int32{int32(bSize), int32(indsDepth)}},
"info": {Data: info, Shape: []int32{int32(bSize), 5}},
},
})
if err_ != nil {
panic(err_)
}
actions := trade.Mats["actions"].Data
for i, j := range items {
m, _ := j.More.(*AIMore2)
act := int(math.Round(actions[i]))
truncated := false
if len(j.LongOrders) > 0 || len(j.ShortOrders) > 0 {
m.age += 1
if m.age >= maxHoldAge {
truncated = true
}
}
if (truncated || act == 3 || act == 2) && len(j.LongOrders) > 0 {
_ = j.CloseOrders(&strat.ExitReq{
Tag: "exit_long",
Dirt: core.OdDirtLong,
})
}
if (truncated || act == 4 || act == 1) && len(j.ShortOrders) > 0 {
_ = j.CloseOrders(&strat.ExitReq{
Tag: "exit_short",
Dirt: core.OdDirtShort,
})
}
if !truncated {
if act == 1 && len(j.LongOrders) < int(maxOdNum) {
m, _ := j.More.(*AIMore2)
_ = j.OpenOrder(&strat.EnterReq{
Tag: "long",
StopLossVal: m.atr * 5,
})
} else if act == 2 && len(j.ShortOrders) < int(maxOdNum) {
m, _ := j.More.(*AIMore2)
_ = j.OpenOrder(&strat.EnterReq{
Tag: "short",
Short: true,
StopLossVal: m.atr * 5,
})
}
}
}
},
OnCheckExit: func(s *strat.StratJob, od *ormo.InOutOrder) *strat.ExitReq {
sl := od.GetStopLoss()
if sl == nil || sl.Range == 0 {
return nil
}
holdNum := int((s.Env.TimeStop - od.RealEnterMS()) / s.Env.TFMSecs)
rate := math.Abs(s.Env.Close.Get(0)-od.InitPrice) / sl.Range
if holdNum > nextObsNum && (od.ProfitRate < 0 || rate < 0.3) {
// 止损:持仓30个bar && (亏损状态 or 盈利不足最小预期30%)
return &strat.ExitReq{
Tag: "exit_bad",
OrderID: od.ID,
}
}
return nil
},
GetDrawDownExitRate: func(s *strat.StratJob, od *ormo.InOutOrder, maxChg float64) float64 {
sl := od.GetStopLoss()
if sl == nil || sl.Range == 0 {
return 0
}
bestRate := maxChg * od.InitPrice / sl.Range
if bestRate > 1.2 {
// 最大盈利是止损区间2倍,以30%回撤止盈
return 0.3
}
return 0
},
OnOrderChange: func(s *strat.StratJob, od *ormo.InOutOrder, chgType int) {
m, _ := s.More.(*AIMore2)
if chgType == strat.OdChgExitFill {
if od.Short {
m.pftDoneShort += od.ProfitRate
} else {
m.pftDoneLong += od.ProfitRate
}
}
},
}
}
func (m *AIMore2) logTrue(trues [3]float64, preds []float64) {
val := compareProb(trues, preds)
m.trues = append(m.trues, val)
}

14
rpc_fea/main.go Normal file
View File

@@ -0,0 +1,14 @@
package rpc_fea
import (
"github.com/banbox/banbot/biz"
"github.com/banbox/banbot/strat"
)
func init() {
biz.FeaGenerators["aifea"] = pubAiFea
biz.FeaGenerators["aifea2"] = pubAiFea2
biz.FeaGenerators["fea_times"] = pubFeaTimes
strat.StratMake["rpc_fea:ai"] = AITrade
strat.StratMake["rpc_fea:ai2"] = AITrade2
}