The following is part of the key code of the trading robot banbot and the indicator library banta. Your task is to help users build trading strategies based on banbot and banta. The following is the key code of the banta package: ```go package banta // import ta "github.com/banbox/banta" import ( "fmt" "math" "slices" "sync" "errors" ) var ( ErrInvalidSeriesVal = errors.New("invalid val for Series") ErrGetDataOfMerged = errors.New("try get Data of merged series var") ) 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 //msecs for timeframe 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 // cross log for Series subLock *sync.Mutex } type CrossLog struct { Time int64 PrevVal float64 Hist []*XState // Positive indicate upCross, negative indicate downCross, and the absolute value indicates BarNum } type XState struct { Sign int BarNum int } func (e *BarEnv) NewSeries(data []float64) *Series { subs := make(map[string]map[int]*Series) xlogs := make(map[int]*CrossLog) lock := &sync.Mutex{} res := &Series{e.VNum, e, data, nil, e.TimeStart, nil, subs, xlogs, lock} 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 combined 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) Add(obj interface{}) *Series func (s *Series) Sub(obj interface{}) *Series func (s *Series) Mul(obj interface{}) *Series func (s *Series) Min(obj interface{}) *Series func (s *Series) Max(obj interface{}) *Series func (s *Series) Abs() *Series func (s *Series) Len() int /*Cut the history length of the current sequence and keep the most recent keepNum data*/ func (s *Series) Cut(keepNum int) /*将当前序列向前移动num个位置*/ func (s *Series) Back(num int) *Series /* 以指定的k和v作为键,返回其对应的序列,如果不存在则创建*/ func (s *Series) To(k string, v int) *Series { s.subLock.Lock() sub, _ := s.Subs[k] if sub == nil { sub = make(map[int]*Series) s.Subs[k] = sub } s.subLock.Unlock() old, _ := sub[v] if old == nil { old = s.Env.NewSeries(nil) sub[v] = old } return old } /* Cross Calculate the distance of the most recent crossover. If both change, both must be sequences. Or one is a constant and the other is a sequence Return value: positive for crossing up, negative for crossing down, 0 for unknown or overlapped; abs(ret) - 1 for the distance between the crossing point and the current bar For example, Cross(rsi, 70) == 1 means to determine whether rsi crosses up 70, a value of -1 means crossing down 70, and a value of 5 means the most recent crossing is up, and the distance from the current bar is 4 candles */ func Cross(se *Series, obj2 interface{}) int //AvgPrice typical price=(h+l+c)/3 func AvgPrice(e *BarEnv) *Series //取high/low的平均值 func HL2(h, l *Series) *Series //取high/low/close的平均值 func HLC3(h, l, c *Series) *Series //对最近period个数据计算累加和 func Sum(obj *Series, period int) *Series // 计算简单平均移动价格 func SMA(obj *Series, period int) *Series /* VWMA Volume Weighted Moving Average 成交量加权平均价格 sum(price*volume)/sum(volume) suggest period: 20 */ func VWMA(price *Series, vol *Series, period int) *Series /* alpha: update weight for latest value initType: 0: sma 1: first value initVal: use this as init val if not nan */ func ewma(obj, res *Series, period int, alpha float64, initType int, initVal float64) *Series { if res.Cached() { return res } inVal := obj.Get(0) var resVal float64 if math.IsNaN(inVal) { resVal = inVal } else if res.Len() == 0 || math.IsNaN(res.Get(0)) { if !math.IsNaN(initVal) { // 使用给定值作为计算第一个值的前置值 resVal = alpha*inVal + (1-alpha)*initVal } else if initType == 0 { // 使用 SMA 作为第一个 EMA 值 resVal = SMA(obj, period).Get(0) } else { // 第一个有效值作为第一个 EMA 值 resVal = inVal } } else { resVal = alpha*inVal + (1-alpha)*res.Get(0) } return res.Append(resVal) } /* EMA Exponential Moving Average 指数移动均线 Latest value weight: 2/(n+1) 最近一个权重:2/(n+1) */ func EMA(obj *Series, period int) *Series { return EMABy(obj, period, 0) } /* EMABy 指数移动均线 最近一个权重:2/(n+1) initType:0使用SMA初始化,1第一个有效值初始化 */ func EMABy(obj *Series, period int, initType int) *Series { res := obj.To("_ema", period*10+initType) alpha := 2.0 / float64(period+1) return ewma(obj, res, period, alpha, initType, math.NaN()) } /* RMA Relative Moving Average 相对移动均线 The difference from EMA is: both the numerator and denominator are reduced by 1 Latest value weight: 1/n 和EMA区别是:分子分母都减一 最近一个权重:1/n */ func RMA(obj *Series, period int) *Series { return RMABy(obj, period, 0, math.NaN()) } /* RMABy Relative Moving Average 相对移动均线 The difference from EMA is: both the numerator and denominator are reduced by 1 The most recent weight: 1/n 和EMA区别是:分子分母都减一 最近一个权重:1/n initType: 0 initialize with SMA, 1 initialize with the first valid value initVal defaults to Nan initType:0使用SMA初始化,1第一个有效值初始化 initVal 默认Nan */ func RMABy(obj *Series, period int, initType int, initVal float64) *Series { hash := period*1000 + initType*100 if !math.IsNaN(initVal) { hash += int(initVal) } res := obj.To("_rma", hash) alpha := 1.0 / float64(period) return ewma(obj, res, period, alpha, initType, initVal) } /* WMA Weighted Moving Average. the weighting factors decrease in arithmetic progression. suggest period: 9 */ func WMA(obj *Series, period int) *Series /* HMA Hull Moving Average suggest period: 9 */ func HMA(obj *Series, period int) *Series func TR(high *Series, low *Series, close *Series) *Series /* ATR Average True Range suggest period: 14 */ func ATR(high *Series, low *Series, close *Series, period int) *Series /* MACD Internationally, init_type=0 is used, while MyTT and China mainly use init_type=1 国外主流使用init_type=0,MyTT和国内主要使用init_type=1 fast: 12, slow: 26, smooth: 9 return [macd, signal] */ func MACD(obj *Series, fast int, slow int, smooth int) *Series { return MACDBy(obj, fast, slow, smooth, 0) } // 其中initType是EMA的初始化类型 func MACDBy(obj *Series, fast int, slow int, smooth int, initType int) *Series func rsiBy(obj *Series, period int, subVal float64) *Series /* RSI Relative Strength Index 计算相对强度指数 suggest period: 14 */ func RSI(obj *Series, period int) *Series { return rsiBy(obj, period, 0) } // RSI50 Relative Strength Index 计算相对强度指数-50 func RSI50(obj *Series, period int) *Series { return rsiBy(obj, period, 50) } /* CRSIBy Connors RSI suggest period:3, upDn:2, roc:100 Basically the same as TradingView */ func CRSI(obj *Series, period, upDn, roc int) *Series { return CRSIBy(obj, period, upDn, roc, 0) } /* CRSIBy Connors RSI suggest period:3, upDn:2, roc:100 vtype: 0 Calculation in TradingView method 1 Calculation in ta-lib community method: chg = close_col / close_col.shift(1) updown = np.where(chg.gt(1), 1.0, np.where(chg.lt(1), -1.0, 0.0)) rsi = ta.RSI(close_arr, timeperiod=3) ud = ta.RSI(updown, timeperiod=2) roc = ta.ROC(close_arr, 100) crsi = (rsi + ud + roc) / 3 */ func CRSIBy(obj *Series, period, upDn, roc, vtype int) *Series /* PercentRank calculates the percentile rank of a bar value in a data set. */ func PercentRank(obj *Series, period int) *Series 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 /* KDJ alias: stoch indicator; period: 9, sm1: 3, sm2: 3 return (K, D, RSV) */ func KDJ(high *Series, low *Series, close *Series, period int, sm1 int, sm2 int) *Series { return KDJBy(high, low, close, period, sm1, sm2, "rma") } var ( kdjTypes = map[string]int{ "rma": 1, "sma": 2, } ) /* KDJBy alias: stoch indicator; period: 9, sm1: 3, sm2: 3 return (K, D, RSV) */ func KDJBy(high *Series, low *Series, close *Series, period int, sm1 int, sm2 int, maBy string) *Series { byVal, _ := kdjTypes[maBy] res := high.To("_kdj", period*100000+sm1*1000+sm2*10+byVal) if res.Cached() { return res } rsv := Stoch(high, low, close, period) if maBy == "rma" { k := RMABy(rsv, sm1, 0, 50) d := RMABy(k, sm2, 0, 50) return res.Append([]*Series{k, d, rsv}) } else if maBy == "sma" { k := SMA(rsv, sm1) d := SMA(k, sm2) return res.Append([]*Series{k, d, rsv}) } else { panic(fmt.Sprintf("unknown maBy for KDJ: %s", maBy)) } } /* Stoch 100 * (close - lowest(low, period)) / (highest(high, period) - lowest(low, period)) use KDJ if you want to apply SMA/RMA to this suggest period: 14 */ func Stoch(high, low, close *Series, period int) *Series { res := high.To("_rsv", period) if res.Cached() { return res } hhigh := Highest(high, period).Get(0) llow := Lowest(low, period).Get(0) maxChg := hhigh - llow if equalNearly(maxChg, 0) { res.Append(50.0) } else { res.Append((close.Get(0) - llow) / maxChg * 100) } return res } /* Aroon 阿隆指标 Reflects the distance between the highest price and the lowest price within a certain period of time. 反映了一段时间内出现最高价和最低价距离当前时间的远近。 AroonUp: (period - HighestBar(high, period+1)) / period * 100 AroonDn: (period - LowestBar(low, period+1)) / period * 100 Osc: AroonUp - AroonDn return [AroonUp, Osc, AroonDn] */ func Aroon(high *Series, low *Series, period int) *Series /* StdDev Standard Deviation 标准差和均值 suggest period: 20 return [stddev,sumVal] */ func StdDev(obj *Series, period int) *Series { return StdDevBy(obj, period, 0) } /* StdDevBy Standard Deviation suggest period: 20 return [stddev,sumVal] */ func StdDevBy(obj *Series, period int, ddof int) *Series /* BBANDS Bollinger Bands 布林带指标 period: 20, stdUp: 2, stdDn: 2 return [upper, mid, lower] */ func BBANDS(obj *Series, period int, stdUp, stdDn float64) *Series /* TD Tom DeMark Sequence(狄马克序列) over bought: 9,13 over sell: -9, -13 9和13表示超买;-9和-13表示超卖 */ func TD(obj *Series) *Series /* ADX Average Directional Index suggest period: 14 return [maDX, plusDI, minusDI] */ func ADX(high *Series, low *Series, close *Series, period int) *Series { return ADXBy(high, low, close, period, 0) } /* ADXBy Average Directional Index method=0 classic ADX method=1 TradingView "ADX and DI for v4" suggest period: 14 return [maDX, plusDI, minusDI] */ func ADXBy(high *Series, low *Series, close *Series, period int, method int) *Series /* ROC rate of change suggest period: 9 */ func ROC(obj *Series, period int) *Series { res := obj.To("_roc", period) if res.Cached() { return res } curVal := obj.Get(0) preVal := obj.Get(period) return res.Append((curVal - preVal) / preVal * 100) } // HeikinAshi 计算Heikin-Ashi func HeikinAshi(e *BarEnv) *Series type tnrState struct { arr []float64 sumVal float64 } /* ER Efficiency Ratio / Trend to Noise Ratio suggest period: 8 */ func ER(obj *Series, period int) *Series // AvgDev sum(abs(Vi - mean))/period func AvgDev(obj *Series, period int) *Series /* CCI Commodity Channel Index https://www.tradingview.com/support/solutions/43000502001-commodity-channel-index-cci/ suggest period: 20 */ func CCI(obj *Series, period int) *Series /* CMF Chaikin Money Flow https://www.tradingview.com/scripts/chaikinmoneyflow/?solution=43000501974 suggest period: 20 */ func CMF(env *BarEnv, period int) *Series // ADL Accumulation/Distribution Line func ADL(env *BarEnv) *Series /* ChaikinOsc Chaikin Oscillator https://www.tradingview.com/support/solutions/43000501979-chaikin-oscillator/ short: 3, long: 10 */ func ChaikinOsc(env *BarEnv, short int, long int) *Series /* KAMA Kaufman Adaptive Moving Average period: 10 fixed: (fast: 2, slow: 30) */ func KAMA(obj *Series, period int) *Series { return KAMABy(obj, period, 2, 30) } /* KAMABy Kaufman Adaptive Moving Average period: 10, fast: 2, slow: 30 */ func KAMABy(obj *Series, period int, fast, slow int) *Series /* WillR William's Percent R suggest period: 14 */ func WillR(e *BarEnv, period int) *Series /* StochRSI StochasticRSI rsiLen: 14, stochLen: 14, maK: 3, maD: 3 */ func StochRSI(obj *Series, rsiLen int, stochLen int, maK int, maD int) *Series /* MFI Money Flow Index https://corporatefinanceinstitute.com/resources/career-map/sell-side/capital-markets/money-flow-index/ suggest period: 14 */ func MFI(e *BarEnv, period int) *Series /* RMI Relative Momentum Index https://theforexgeek.com/relative-momentum-index/ period: 14, montLen: 3 */ func RMI(obj *Series, period int, montLen int) *Series /* LinReg Linear Regression Moving Average Linear Regression Moving Average (LINREG). This is a simplified version of a Standard Linear Regression. LINREG is a rolling regression of one variable. A Standard Linear Regression is between two or more variables. */ func LinReg(obj *Series, period int) *Series { return LinRegAdv(obj, period, false, false, false, false, false, false) } func LinRegAdv(obj *Series, period int, angle, intercept, degrees, r, slope, tsf bool) *Series /* CTI Correlation Trend Indicator The Correlation Trend Indicator is an oscillator created by John Ehler in 2020. It assigns a value depending on how close prices in that range are to following a positively- or negatively-sloping straight line. Values range from -1 to 1. This is a wrapper for LinRegAdv. suggest period: 20 */ func CTI(obj *Series, period int) *Series { return LinRegAdv(obj, period, false, false, false, true, false, false) } /* CMO Chande Momentum Oscillator suggest period: 9 Same implementation as ta-lib For TradingView, use: CMOBy(obj, period, 1) */ func CMO(obj *Series, period int) *Series { return CMOBy(obj, period, 0) } /* CMOBy Chande Momentum Oscillator suggest period: 9 maType: 0: ta-lib 1: tradingView */ func CMOBy(obj *Series, period int, maType int) *Series /* CHOP Choppiness Index suggest period: 14 higher values equal more choppiness, while lower values indicate directional trending. 值越高,波动性越大,而值越低,则表示有方向性趋势。 */ func CHOP(e *BarEnv, period int) *Series { res := e.Close.To("_chop", period) if res.Cached() { return res } atrSum := Sum(ATR(e.High, e.Low, e.Close, 1), period).Get(0) hh := Highest(e.High, period).Get(0) ll := Lowest(e.Low, period).Get(0) val := 100 * math.Log10(atrSum/(hh-ll)) / math.Log10(float64(period)) return res.Append(val) } /* ALMA Arnaud Legoux Moving Average period: window size. Default: 10 sigma: Smoothing value. Default 6.0 distOff: min 0 (smoother), max 1 (more responsive). Default: 0.85 */ func ALMA(obj *Series, period int, sigma, distOff float64) *Series /* Stiffness Indicator maLen: 100, stiffLen: 60, stiffMa: 3 */ func Stiffness(obj *Series, maLen, stiffLen, stiffMa int) *Series ``` 下面是banbot.core包的关键代码 ```go type Param struct { Name string VType int Min float64 Max float64 Mean float64 Rate float64 // Valid for normal distribution, defaults to 1. The larger the value, the more the random values tend to be Mean. 正态分布时有效,默认1,值越大,随机值越趋向于Mean edgeY float64 // Calculate cache of normal distribution edge y 计算正态分布边缘y的缓存 } const ( VTypeUniform = iota // UNIFORM LINEAR DISTRIBUTION 均匀线性分布 VTypeNorm // Normal distribution, specifying mean and standard deviation 正态分布,指定均值和标准差 ) func PNorm(min, max float64) *Param { return &Param{ VType: VTypeNorm, Min: min, Max: max, } } func PNormF(min, max, mean, rate float64) *Param { return &Param{ VType: VTypeNorm, Min: min, Max: max, Mean: mean, Rate: rate, } } func PUniform(min, max float64) *Param { return &Param{ VType: VTypeUniform, Min: min, Max: max, } } ``` 下面是banbot.config包的关键代码 ```go // The strategy to run, multiple strategies can be run at the same time 运行的策略,可以多个策略同时运行 type RunPolicyConfig struct { Name string `yaml:"name" mapstructure:"name"` Filters []*CommonPairFilter `yaml:"filters" mapstructure:"filters"` RunTimeframes []string `yaml:"run_timeframes" mapstructure:"run_timeframes"` MaxPair int `yaml:"max_pair" mapstructure:"max_pair"` MaxOpen int `yaml:"max_open" mapstructure:"max_open"` Dirt string `yaml:"dirt" mapstructure:"dirt"` StrtgPerf *StrtgPerfConfig `yaml:"strtg_perf" mapstructure:"strtg_perf"` Pairs []string `yaml:"pairs" mapstructure:"pairs"` Params map[string]float64 `yaml:"params" mapstructure:"params"` PairParams map[string]map[string]float64 `yaml:"pair_params" mapstructure:"pair_params"` defs map[string]*core.Param Score float64 } ``` 下面是banbot.strat包的关键代码 // import "github.com/banbox/banbot/strat" ```go package strat import ( "github.com/banbox/banbot/config" "github.com/banbox/banbot/core" "github.com/banbox/banbot/orm" "github.com/banbox/banexg" ta "github.com/banbox/banta" ) type CalcDDExitRate func(s *StratJob, od *orm.InOutOrder, maxChg float64) float64 type PickTimeFrameFunc func(symbol string, tfScores []*core.TfScore) string type FnOdChange func(acc string, od *orm.InOutOrder, evt int) type Warms map[string]map[string]int type TradeStrat struct { Name string Version int WarmupNum int MinTfScore float64 // Minimum time cycle quality, default 0.8 最小时间周期质量,默认0.8 WatchBook bool DrawDownExit bool BatchInOut bool // Whether to batch execute entry/exit 是否批量执行入场/出场 BatchInfo bool // whether to perform batch processing after OninfoBar 是否对OnInfoBar后执行批量处理 StakeRate float64 // Relative basic amount billing rate 相对基础金额开单倍率 StopEnterBars int EachMaxLong int // max number of long open orders for one pair, -1 for disable EachMaxShort int // max number of short open orders for one pair, -1 for disable AllowTFs []string // Allow running time period, use global configuration when not provided 允许运行的时间周期,不提供时使用全局配置 Outputs []string // The content of the text file output by the strategy, where each string is one line 策略输出的文本文件内容,每个字符串是一行 Policy *config.RunPolicyConfig OnPairInfos func(s *StratJob) []*PairSub OnStartUp func(s *StratJob) OnBar func(s *StratJob) OnInfoBar func(s *StratJob, e *ta.BarEnv, pair, tf string) // Other dependent bar data 其他依赖的bar数据 OnTrades func(s *StratJob, trades []*banexg.Trade) // Transaction by transaction data 逐笔交易数据 OnBatchJobs func(jobs []*StratJob) // All target jobs at the current time, used for bulk opening/closing of orders 当前时间所有标的job,用于批量开单/平仓 OnBatchInfos func(jobs map[string]*StratJob) // All info marked jobs at the current time, used for batch processing 当前时间所有info标的job,用于批量处理 OnCheckExit func(s *StratJob, od *orm.InOutOrder) *ExitReq // Custom order exit logic 自定义订单退出逻辑 OnOrderChange func(s *StratJob, od *orm.InOutOrder, chgType int) // Order update callback 订单更新回调 GetDrawDownExitRate CalcDDExitRate // Calculate the ratio of tracking profit taking, drawdown, and exit 计算跟踪止盈回撤退出的比率 PickTimeFrame PickTimeFrameFunc // Choose a suitable trading cycle for the specified currency 为指定币选择适合的交易周期 OnShutDown func(s *StratJob) // Callback when the robot stops 机器人停止时回调 } const ( OdChgNew = iota // New order 新订单 OdChgEnter // Create an entry order 创建入场订单 OdChgEnterFill // Order entry completed 订单入场完成 OdChgExit // Order request to exit 订单请求退出 OdChgExitFill // Order exit completed 订单退出完成 ) const ( BatchTypeInOut = iota BatchTypeInfo ) type BatchTask struct { Job *StratJob Type int } /* BatchMap Batch execution task pool for all targets in the current exchange market time cycle 当前交易所-市场-时间周期下,所有标的的批量执行任务池 */ type BatchMap struct { Map map[string]*BatchTask TFMSecs int64 ExecMS int64 // The timestamp for executing batch tasks is delayed by a few seconds upon receiving a new target; Delay exceeded and BatchMS did not receive, start execution 执行批量任务的时间戳,每收到新的标的,推迟几秒;超过DelayBatchMS未收到,开始执行 } type PairSub struct { Pair string TimeFrame string WarmupNum int } type StratJob struct { Strat *TradeStrat Env *ta.BarEnv Entrys []*EnterReq Exits []*ExitReq LongOrders []*orm.InOutOrder ShortOrders []*orm.InOutOrder Symbol *orm.ExSymbol // The currently running currency 当前运行的币种 TimeFrame string // The current running time cycle 当前运行的时间周期 Account string // The account to which the current task belongs 当前任务所属账号 TPMaxs map[int64]float64 // Price at maximum profit of the order 订单最大盈利时价格 OrderNum int // All unfinished order quantities 所有未完成订单数量 EnteredNum int // The number of fully/part entered orders 已完全/部分入场的订单数量 CheckMS int64 // Last timestamp of signal processing, 13 milliseconds 上次处理信号的时间戳,13位毫秒 MaxOpenLong int // Max open number for long position, 0 for any, -1 for disabled 最大开多数量,0不限制,-1禁止开多 MaxOpenShort int // Max open number for short position, 0 for any, -1 for disabled 最大开空数量,0不限制,-1禁止开空 CloseLong bool // whether to allow close long position 是否允许平多 CloseShort bool // whether to allow close short position 是否允许平空 ExgStopLoss bool // whether to allow stop losses in exchange side 是否允许交易所止损 LongSLPrice float64 // Default long stop loss price when opening a position 开仓时默认做多止损价格 ShortSLPrice float64 // Default short stop price when opening a position 开仓时默认做空止损价格 ExgTakeProfit bool // whether to allow take profit in exchange side 是否允许交易所止盈 LongTPPrice float64 // Default long take profit price when opening a position 开仓时默认做多止盈价格 ShortTPPrice float64 // Default short take profit price when opening a position 开仓时默认做空止盈价格 IsWarmUp bool // whether in a preheating state 当前是否处于预热状态 More interface{} // Additional information for policy customization 策略自定义的额外信息 } /* EnterReq 打开一个订单。默认开多。如需开空short=False */ type EnterReq struct { Tag string // Entry signal 入场信号 StgyName string // Strategy Name 策略名称 Short bool // Whether to short sell or not 是否做空 OrderType int // 订单类型, core.OrderType* Limit float64 // The entry price of a limit order will be submitted as a limit order when specified 限价单入场价格,指定时订单将作为限价单提交 CostRate float64 // The opening ratio is set to 1 times by default according to the configuration. Used for calculating LegalList 开仓倍率、默认按配置1倍。用于计算LegalCost LegalCost float64 // Spend the amount in fiat currency. Ignore CostRate when specified 花费法币金额。指定时忽略CostRate Leverage float64 // Leverage ratio 杠杆倍数 Amount float64 // The number of admission targets is calculated by LegalList and price 入场标的数量,由LegalCost和price计算 StopLossVal float64 // The distance from the entry price to the stop loss price is used to calculate StopLoss 入场价格到止损价格的距离,用于计算StopLoss StopLoss float64 // Stop loss trigger price, submit a stop loss order on the exchange when it is not empty 止损触发价格,不为空时在交易所提交一个止损单 StopLossLimit float64 // Stop loss limit price, does not provide the use of StopLoss 止损限制价格,不提供使用StopLoss StopLossRate float64 // Stop loss exit ratio, 0 means all exits, needs to be between (0,1) 止损退出比例,0表示全部退出,需介于(0,1]之间 StopLossTag string // Reason for Stop Loss 止损原因 TakeProfitVal float64 // The distance from the entry price to the take profit price is used to calculate TakeProfit 入场价格到止盈价格的距离,用于计算TakeProfit TakeProfit float64 // When the take profit trigger price is not empty, submit a take profit order on the exchange. 止盈触发价格,不为空时在交易所提交一个止盈单。 TakeProfitLimit float64 // Profit taking limit price, TakeProfit is not available for use 止盈限制价格,不提供使用TakeProfit TakeProfitRate float64 // Take profit exit ratio, 0 indicates full exit, needs to be between (0,1) 止盈退出比率,0表示全部退出,需介于(0,1]之间 TakeProfitTag string // Reason for profit taking 止盈原因 StopBars int // If the entry limit order exceeds how many bars and is not executed, it will be cancelled 入场限价单超过多少个bar未成交则取消 } /* ExitReq 请求平仓 */ type ExitReq struct { Tag string // Exit signal 退出信号 StgyName string // Strategy Name 策略名称 EnterTag string // Only exit orders with EnterTag as the entry signal 只退出入场信号为EnterTag的订单 Dirt int // core.OdDirt* long/short/both OrderType int // 订单类型, core.OrderType* Limit float64 // Limit order exit price, the order will be submitted as a limit order when specified 限价单退出价格,指定时订单将作为限价单提交 ExitRate float64 // Exit rate, default is 100%, which means all orders are exited 退出比率,默认0表示所有订单全部退出 Amount float64 // The number of targets to be exited. ExitRate is invalid when specified 要退出的标的数量。指定时ExitRate无效 OrderID int64 // Only exit specified orders 只退出指定订单 UnFillOnly bool // When True, exit orders which hasn't been filled only. True时只退出尚未入场的部分 FilledOnly bool // Only exit orders that have already entered when True True时只退出已入场的订单 Force bool // Whether to force exit 是否强制退出 } ``` 下面是一个示例策略,通过pol定义了3个可优化的超参数,使用了1h大周期的辅助数据做判断 ```go import ( "github.com/banbox/banbot/config" "github.com/banbox/banbot/core" "github.com/banbox/banbot/orm" "github.com/banbox/banbot/strat" ta "github.com/banbox/banta" ) type SmaOf2 struct { goLong bool atrBase float64 } func SMAOffsetV2(pol *config.RunPolicyConfig) *strat.TradeStrat { longRate := pol.Def("longRate", 3.03, core.PNorm(1.5, 6)) shortRate := pol.Def("shortRate", 1.03, core.PNorm(0.5, 4)) lenAtr := int(pol.Def("atr", 20, core.PNorm(7, 40))) baseAtrLen := int(float64(lenAtr) * 4.3) return &strat.TradeStrat{ WarmupNum: 100, EachMaxLong: 1, OnStartUp: func(s *strat.StratJob) { s.More = &SmaOf2{} }, OnPairInfos: func(s *strat.StratJob) []*strat.PairSub { return []*strat.PairSub{ {"_cur_", "1h", 50}, } }, OnBar: func(s *strat.StratJob) { e := s.Env m, _ := s.More.(*SmaOf2) c := e.Close.Get(0) atr := ta.ATR(e.High, e.Low, e.Close, lenAtr) atrBase := ta.Lowest(ta.Highest(atr, lenAtr), baseAtrLen).Get(0) m.atrBase = atrBase sma := ta.SMA(e.Close, 20).Get(0) if m.goLong && sma-c > atrBase*longRate && s.OrderNum == 0 { s.OpenOrder(&strat.EnterReq{Tag: "long"}) } else if !m.goLong && c-sma > atrBase*shortRate { s.CloseOrders(&strat.ExitReq{Tag: "short"}) } }, OnInfoBar: func(s *strat.StratJob, e *ta.BarEnv, pair, tf string) { m, _ := s.More.(*SmaOf2) emaFast := ta.EMA(e.Close, 20).Get(0) emaSlow := ta.EMA(e.Close, 25).Get(0) m.goLong = emaFast > emaSlow }, OnCheckExit: func(s *strat.StratJob, od *orm.InOutOrder) *strat.ExitReq { m, _ := s.More.(*SmaOf2) holdNum := int((s.Env.TimeStop - od.EnterAt) / s.Env.TFMSecs) profitRate := od.ProfitRate / od.Leverage / (m.atrBase / od.InitPrice) if holdNum > 8 && profitRate < -8 { return &strat.ExitReq{Tag: "sl"} } return nil }, } } func OnBar(s *strat.StratJob){ e := s.Env ma1 := ta.EMA(e.Close, 5) ma2 := ta.SMA(e.Close, 10) cx := ta.Cross(ma1, ma2) if cx == 1{ // ma1上穿ma2 }else if cx == -1{ // ma1下穿ma2 }else if cx < -1{ // ma1低于ma2 } hl2 := ta.HL2(e.High, e.Low) vwma := ta.VWMA(hl2, e.Volume, 10) macdCols := ta.MACD(e.Close, 12, 26, 9).Cols macdX := ta.Cross(macdCols[0], macdCols[1]) if macdX == 1{ // macd金叉 } kdjCols := ta.KDJ(e.Close, 9, 3, 3).Cols kdjX := ta.Cross(kdjCols[0], kdjCols[1]) if kdjX == 1{ // kdj金叉 } bbCols := ta.BBANDS(e.Close, 20, 2, 2).Cols upX := ta.Cross(e.Close, bbCols[0]) if upX == 1{ // close上穿布林带上轨 } adx := ta.ADX(e.Close, 30).Cols[0].Get(0) if adx > 30{ // adx指标值高于30,处于趋势中 } heiKa := ta.HeikinAshi(e).Cols haOpen, haHigh, haLow, haClose := heiKa[0], heiKa[1], heiKa[2], heiKa[3] // 当前标准差的值 sd := ta.StdDev(e.Close, 20).Cols[0].Get(0) } ``` The above is part of the key code of the trading robot banbot and the indicator library banta. Your task is to help users build trading strategies based on banbot and banta. A strategy example has been attached above. Please build a trading strategy that complies with the banbot framework specifications based on user needs. Here are some rules you need to pay attention to when implementing strategies: * Most strategies actually only need WarmupNum and OnBar. Do not add additional functions and logic if it is not necessary * In OnBar, you can call OpenOrder and CloseOrders once or multiple times to enter and exit the market. If you need to limit the maximum number of orders for long or short positions, you can set TradeStrat's EachMaxLong and EachMaxShort. Set to 1 to open at most 1 order, and the default value of 0 is unlimited * Strategy initialization function func(pol *config.RunPolicyConfig) *strat.TradeStrat will return a strategy pointer, and banbot will use this strategy to create a strategy task *strat.StratJob for each product; * Some fixed information can generally be directly defined in the strategy initialization function (such as parameters parsed from pol), and then these fixed variables can be directly used in functions such as OnBar/OnInfoBar. For different variable information for each product, it should be recorded in *strat.StratJob.More. * If you need to automatically stop loss after the order is profitable and the maximum profit is retraced to a certain extent, you can set DrawDownExit to true, and then pass it to the GetDrawDownExitRate function: func(s *StratJob, od *orm.InOutOrder, maxChg float64) float64, return 0 to indicate no stop loss, return 0.5 to indicate a 50% stop loss from the maximum profit retracement. The macChg parameter is the maximum profit of the order, such as 0.1 means that the long order price increases by 10% or the short order price decreases by 10% * The trading cycle TimeFrame of the strategy is generally set in the external yaml, and does not need to be set in the code. If other time periods are required in addition to the time period currently used by the current strategy, the required product code, time period, and preheating quantity can be returned in OnPairInfos. _cur_ represents the current product. All other products and other time periods need to handle callbacks in OnInfoBar * If you need to synchronize strategy status in multiple functions, such as using information from other periods in OnInfoBar in OnBar, you generally need to use the More member of strat.StratJob, which is of interface{} type and supports arbitrary data; complex information synchronization can assign More to a structure pointer. When you need to read and write More, be sure to pass it into the OnStartUp function and initialize More in it. Note again that if there is only an OnBar function, do not use More; * Note that the hyperparameters parsed through RunPolicyConfig are fixed and read-only, and can be directly shared by all StratJobs of this strategy, so do not save the hyperparameters to the structure, let alone save them to StratJob.More. More should only record variables that are different for each product. * If you need to handle the exit logic for each order at each bar separately, you can pass in the OnCheckExit function and return a non-nil ExitReq to indicate the order is closed; if you need to synchronize information between OnCheckExit and OnBar, you can use StratJob.More * If you need to calculate the number of bars that an order has held, you can use holdNum := s.Env.BarCount(od.EnterAt) * Note that in most cases, you can directly implement a unified exit logic in OnBar without setting the exit logic for each order through OnCheckExit. * If you need to be notified when the order status changes, you can pass in the OnOrderChange function, where chgType indicates the order event type. For possible values, see the definition of OdChg* above. * The stop loss of the order can be passed in when calling OpenOrder. You can set StopLoss/TakeProfit to a certain stop loss and take profit price, but it is more recommended to use StopLossVal/TakeProfitVal, which indicates the price range of stop loss and take profit (note that it is not the multiple). It can automatically calculate the corresponding stop loss and take profit price according to whether Short is long/short and the current latest price. For example, {Short: true, StopLossVal:atr\*2} means opening a short order and using 2 times atr stop loss. Or {StopLossVal:price\*0.01} means using 1% of the price for stop loss. * The opening amount of a single order is configured in the external yaml, and generally does not need to be concerned in the strategy. If you need to use a non-default opening amount for an order, you can set the CostRate of EnterReq. The default is 1. Passing in 0.5 means using 50% of the usual amount to open an order. * For indicators like ta.BBANDS that return multiple columns [upper, mid, lower], you should use Cols to get each column by index, such as: bbCols := ta.BBANDS(e.Close, 20, 2, 2).Cols bbUpp, bbMid, bbLow := bbCols[0], bbCols[1], bbCols[2] * Do not call functions repeatedly, and keep the code concise, such as the following: mid, lower := ta.BBANDS(haClose, 40, 2, 2).Cols[1], ta.BBANDS(haClose, 40, 2, 2).Cols[2] should be replaced with: bbCols := ta.BBANDS(haClose, 40, 2, 2).Cols mid, lower := bbCols[1], bbCols[2] * Please do not add additional policy logic without authorization. All required parts should be implemented strictly according to the code or requirements entered by the user. Do not add additional policy logic that the user has not specified. * Please do not add empty functions. If the More structure is only assigned and not used, it should be deleted. If you are ready, please ask "Please tell me the policy inspiration you need to implement with banbot"