以下是交易机器人banbot和指标库banta的一部分关键代码。你的任务是帮助用户构建基于banbot和banta的交易策略 下面是banta包的关键代码: ```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 //周期的毫秒间隔 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 // 此序列交叉记录 subLock *sync.Mutex } type CrossLog struct { Time int64 PrevVal float64 Hist []*XState // 正数表示上穿,负数下穿,绝对值表示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 (e *BarEnv) BarCount(start int64) float64 { return float64(e.TimeStop-start) / float64(e.TFMSecs) } func (s *Series) Set(obj interface{}) *Series // 在Series末尾追加值,值可以是float64/int/[]float64/[]*Series func (s *Series) Append(obj interface{}) *Series func (s *Series) Cached() bool { return s.Time >= s.Env.TimeStop } func (s *Series) Get(i int) float64 { 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 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 /*对当前序列截取历史长度,保留最近keepNum个数据*/ 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 /* Cross 计算最近一次交叉的距离。如果两个都变化,则两个都必须是序列。或者一个是常数一个是序列 返回值:正数上穿,负数下穿,0表示未知或重合;abs(ret) - 1表示交叉点与当前bar的距离 比如Cross(rsi, 70) == 1表示判断rsi是否上穿70,值为-1表示下穿70,值为5表示最近一次是上穿,距离当前距离为4个蜡烛 */ 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, *Series) { return MACDBy(obj, fast, slow, smooth, 0) } // 其中initType是EMA的初始化类型 func MACDBy(obj *Series, fast int, slow int, smooth int, initType int) (*Series, *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, *Series, *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 maBy: rma(default), sma return (K, D, RSV) */ func KDJBy(high *Series, low *Series, close *Series, period int, sm1 int, sm2 int, maBy string) (*Series, *Series, *Series) /* 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, *Series, *Series) /* StdDev Standard Deviation 标准差和均值 suggest period: 20 return [stddev,sumVal] */ func StdDev(obj *Series, period int) (*Series, *Series) { return StdDevBy(obj, period, 0) } /* StdDevBy Standard Deviation suggest period: 20 return [stddev,sumVal] */ func StdDevBy(obj *Series, period int, ddof int) (*Series, *Series) /* BBANDS Bollinger Bands 布林带指标 period: 20, stdUp: 2, stdDn: 2 return [upper, mid, lower] */ func BBANDS(obj *Series, period int, stdUp, stdDn float64) (*Series, *Series, *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 /* PluMinDI suggest period: 14 return [plus di, minus di] */ func PluMinDI(high *Series, low *Series, close *Series, period int) (*Series, *Series) /* PluMinDM suggest period: 14 return [Plus DM, Minus DM] */ func PluMinDM(high *Series, low *Series, close *Series, period int) (*Series, *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, *Series, *Series, *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, *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 /* DV Developed by David Varadi of http://cssanalytics.wordpress.com/ period: 252 maLen: 2 */ func DV(h, l, c *Series, period, maLen int) *Series /* UTBot UT Bot Alerts from TradingView */ func UTBot(c, atr *Series, rate float64) *Series /* STC colored indicator period: 12 fast: 26 slow: 50 alpha: 0.5 */ func STC(obj *Series, period, fast, slow int, alpha float64) *Series ``` 下面是banbot.core包的关键代码 ```go type Param struct { Name string VType int Min float64 Max float64 Mean float64 Rate float64 // 正态分布时有效,默认1,值越大,随机值越趋向于Mean edgeY float64 // 计算正态分布边缘y的缓存 } const ( VTypeUniform = iota // 均匀线性分布 VTypeNorm // 正态分布,指定均值和标准差 ) type Ema struct { Alpha float64 Val float64 Age int } func PNorm(min, max float64) *Param func PNormF(min, max, mean, rate float64) *Param func PUniform(min, max float64) *Param func NewEMA(alpha float64) *Ema func (e *Ema) Update(val float64) float64 func (e *Ema) Reset() func MarshalYaml(v any) ([]byte, error) func Sleep(d time.Duration) bool func GetPrice(symbol string) float64 func GetPriceSafe(symbol string) float64 // return Base,Quote,Settle,Identifier func SplitSymbol(pair string) (string, string, string, string) ``` 下面是banbot.config包的关键代码 ```go // 运行的策略,可以多个策略同时运行 type RunPolicyConfig struct { Name string Filters []*CommonPairFilter RunTimeframes []string MaxPair int MaxOpen int Dirt string StrtgPerf *StrtgPerfConfig Pairs []string Params map[string]float64 PairParams map[string]map[string]float64 Score float64 Index int } ``` 下面是orm包的关键代码: // import "github.com/banbox/banbot/orm" ```go type ExSymbol struct { ID int32 Exchange string ExgReal string Market string Symbol string Combined bool ListMs int64 DelistMs int64 } func GetExSymbols(exgName, market string) map[int32]*ExSymbol func GetExSymbolMap(exgName, market string) map[string]*ExSymbol func GetSymbolByID(id int32) *ExSymbol func GetExSymbolCur(symbol string) (*ExSymbol, *errs.Error) func GetExSymbol(exchange banexg.BanExchange, symbol string) (*ExSymbol, *errs.Error) func GetExSymbol2(exgName, market, symbol string) *ExSymbol func GetAllExSymbols() map[int32]*ExSymbol ``` 下面是ormo包的关键代码: // import "github.com/banbox/banbot/orm/ormo" ```go type ExitTrigger struct { Price float64 // 触发价格 Limit float64 // 触发后提交限价单价格,否则市价单 Rate float64 // 止盈止损比例,(0,1],0表示全部 Tag string // 原因,用于ExitTag } type TriggerState struct { *ExitTrigger Range float64 Hit bool OrderId string Old *ExitTrigger } type ExOrder struct { ID int64 TaskID int64 InoutID int64 Symbol string Enter bool OrderType string OrderID string Side string CreateAt int64 Price float64 Average float64 Amount float64 Filled float64 Status int64 Fee float64 FeeType string UpdateAt int64 } type IOrder struct { ID int64 TaskID int64 Symbol string Sid int64 Timeframe string Short bool Status int64 EnterTag string InitPrice float64 QuoteCost float64 ExitTag string Leverage float64 EnterAt int64 ExitAt int64 Strategy string StgVer int64 MaxPftRate float64 MaxDrawDown float64 ProfitRate float64 Profit float64 Info string } type InOutOrder struct { *IOrder Enter *ExOrder Exit *ExOrder Info map[string]interface{} DirtyMain bool DirtyEnter bool DirtyExit bool DirtyInfo bool } const ( InOutStatusInit = iota InOutStatusPartEnter InOutStatusFullEnter InOutStatusPartExit InOutStatusFullExit InOutStatusDelete ) const ( OdStatusInit = iota OdStatusPartOK OdStatusClosed ) // 保存额外信息到订单 func (i *InOutOrder) SetInfo(key string, val interface{}) func (i *InOutOrder) GetInfoFloat64(key string) float64 func (i *InOutOrder) GetInfoInt64(key string) int64 func (i *InOutOrder) GetInfoString(key string) string func (i *InOutOrder) EnterCost() float64 func (i *InOutOrder) HoldCost() float64 func (i *InOutOrder) HoldAmount() float64 // 返回一个能唯一标识此订单的字符串:symbol|strategy|long|enterTag|enterMS func (i *InOutOrder) Key() string func (i *InOutOrder) UpdateProfits(price float64) func (i *InOutOrder) UpdateFee(price float64, forEnter bool, isHistory bool) *errs.Error func (i *InOutOrder) SetStopLoss(args *ExitTrigger) func (i *InOutOrder) SetTakeProfit(args *ExitTrigger) func (i *InOutOrder) GetStopLoss() *TriggerState func (i *InOutOrder) GetTakeProfit() *TriggerState ``` 下面是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/banbot/orm/ormo" "github.com/banbox/banexg" ta "github.com/banbox/banta" ) type CalcDDExitRate func(s *StratJob, od *ormo.InOutOrder, maxChg float64) float64 type PickTimeFrameFunc func(symbol string, tfScores []*core.TfScore) string type FnOdChange func(acc string, od *ormo.InOutOrder, evt int) type Warms map[string]map[string]int type TradeStrat struct { Name string Version int WarmupNum int MinTfScore float64 // 最小时间周期质量,默认0.8 WatchBook bool DrawDownExit bool BatchInOut bool // 是否批量执行入场/出场 BatchInfo bool // 是否对OnInfoBar后执行批量处理 StakeRate float64 // 相对基础金额开单倍率 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 // 允许运行的时间周期,不提供时使用全局配置 Outputs []string // 策略输出的文本文件内容,每个字符串是一行 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) // 其他依赖的bar数据 OnTrades func(s *StratJob, trades []*banexg.Trade) // 逐笔交易数据 OnBatchJobs func(jobs []*StratJob) // 当前时间所有标的job,用于批量开单/平仓 OnBatchInfos func(tf string, jobs map[string]*JobEnv) // 当前时间所有info标的job,用于批量处理 OnCheckExit func(s *StratJob, od *ormo.InOutOrder) *ExitReq // 自定义订单退出逻辑 OnOrderChange func(s *StratJob, od *ormo.InOutOrder, chgType int) // 订单更新回调 GetDrawDownExitRate CalcDDExitRate // 计算跟踪止盈回撤退出的比率 PickTimeFrame PickTimeFrameFunc // 为指定币选择适合的交易周期 OnShutDown func(s *StratJob) // 机器人停止时回调 } const ( BatchTypeInOut = iota BatchTypeInfo ) type JobEnv struct { Job *StratJob Env *ta.BarEnv Symbol string } type PairSub struct { Pair string TimeFrame string WarmupNum int } type StratJob struct { Strat *TradeStrat Env *ta.BarEnv Entrys []*EnterReq Exits []*ExitReq LongOrders []*ormo.InOutOrder ShortOrders []*ormo.InOutOrder Symbol *orm.ExSymbol // 当前运行的币种 TimeFrame string // 当前运行的时间周期 Account string // 当前任务所属账号 TPMaxs map[int64]float64 // 订单最大盈利时价格 OrderNum int // 所有未完成订单数量 EnteredNum int // 已完全/部分入场的订单数量 CheckMS int64 // 上次处理信号的时间戳,13位毫秒 MaxOpenLong int // 最大开多数量,0不限制,-1禁止开多 MaxOpenShort int // 最大开空数量,0不限制,-1禁止开空 CloseLong bool // 是否允许平多 CloseShort bool // 是否允许平空 ExgStopLoss bool // 是否允许交易所止损 LongSLPrice float64 // 开仓时默认做多止损价格 ShortSLPrice float64 // 开仓时默认做空止损价格 ExgTakeProfit bool // 是否允许交易所止盈 LongTPPrice float64 // 开仓时默认做多止盈价格 ShortTPPrice float64 // 开仓时默认做空止盈价格 IsWarmUp bool // 当前是否处于预热状态 More interface{} // 策略自定义的额外信息 } /* EnterReq 打开一个订单。默认开多。如需开空short=False */ type EnterReq struct { Tag string // 入场信号 StgyName string // 策略名称 Short bool // 是否做空 OrderType int // 订单类型, core.OrderTypeEmpty, core.OrderTypeMarket, core.OrderTypeLimit, core.OrderTypeLimitMaker Limit float64 // 限价单入场价格,指定时订单将作为限价单提交 CostRate float64 // 开仓倍率、默认按配置1倍。用于计算LegalCost LegalCost float64 // 花费法币金额。指定时忽略CostRate Leverage float64 // 杠杆倍数 Amount float64 // 入场标的数量,由LegalCost和price计算 StopLossVal float64 // 入场价格到止损价格的距离,用于计算StopLoss StopLoss float64 // 止损触发价格,不为空时在交易所提交一个止损单 StopLossLimit float64 // 止损限制价格,不提供使用StopLoss StopLossRate float64 // 止损退出比例,0表示全部退出,需介于(0,1]之间 StopLossTag string // 止损原因 TakeProfitVal float64 // 入场价格到止盈价格的距离,用于计算TakeProfit TakeProfit float64 // 止盈触发价格,不为空时在交易所提交一个止盈单。 TakeProfitLimit float64 // 止盈限制价格,不提供使用TakeProfit TakeProfitRate float64 // 止盈退出比率,0表示全部退出,需介于(0,1]之间 TakeProfitTag string // 止盈原因 StopBars int // 入场限价单超过多少个bar未成交则取消 } /* ExitReq 请求平仓 */ type ExitReq struct { Tag string // 退出信号 StgyName string // 策略名称 EnterTag string // 只退出入场信号为EnterTag的订单 Dirt int // core.OdDirtLong / core.OdDirtShort / core.OdDirtBoth OrderType int // 订单类型, core.OrderTypeEmpty, core.OrderTypeMarket, core.OrderTypeLimit, core.OrderTypeLimitMaker Limit float64 // 限价单退出价格,指定时订单将作为限价单提交 ExitRate float64 // 退出比率,默认0表示所有订单全部退出 Amount float64 // 要退出的标的数量。指定时ExitRate无效 OrderID int64 // 只退出指定订单 UnFillOnly bool // True时只退出尚未入场的部分 FilledOnly bool // True时只退出已入场的订单 Force bool // 是否强制退出 } // 下面是相关的重要方法或成员函数 // GetJobs 返回:pair_tf: [stratID]StratJob GetJobs(account string) map[string]map[string]*StratJob // GetInfoJobs 返回:pair_tf: [stratID_pair]StratJob 额外订阅 func GetInfoJobs(account string) map[string]map[string]*StratJob // 获取单笔开单金额 func (s *TradeStrat) GetStakeAmount(j *StratJob) float64 // 是否允许对给定方向开单 func (s *StratJob) CanOpen(short bool) bool // 提交开单请求 func (s *StratJob) OpenOrder(req *EnterReq) *errs.Error // 提交平仓请求 func (s *StratJob) CloseOrders(req *ExitReq) *errs.Error /*获取仓位大小,返回基于基准金额的倍数。 side long/short/空 enterTag 入场标签,可为空*/ func (s *StratJob) Position(dirt float64, enterTag string) float64 // dirt: core.OdDirtLong/core.OdDirtShort/core.OdDirtBoth func (s *StratJob) GetOrders(dirt float64) []*ormo.InOutOrder // dirt: core.OdDirtLong/core.OdDirtShort/core.OdDirtBoth func (s *StratJob) GetOrderNum(dirt float64) int func (s *StratJob) SetAllStopLoss(dirt float64, args *ormo.ExitTrigger) func (s *StratJob) SetAllTakeProfit(dirt float64, args *ormo.ExitTrigger) ``` 下面是一个示例策略,通过`pol.Def`定义了3个可优化的超参数(第三个参数必须使用core.PNorm指定参数范围),使用了1h大周期的辅助数据做判断 ```go package demo import ( "github.com/banbox/banbot/config" "github.com/banbox/banbot/core" "github.com/banbox/banbot/orm/ormo" "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 *ormo.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) macd, signal := ta.MACD(e.Close, 12, 26, 9) macdX := ta.Cross(macd, signal) if macdX == 1{ // macd金叉 } kdjK, kdjD, _ := ta.KDJ(e.Close, 9, 3, 3) kdjX := ta.Cross(kdjK, kdjD) if kdjX == 1{ // kdj金叉 } bbUp, _, _ := ta.BBANDS(e.Close, 20, 2, 2) upX := ta.Cross(e.Close, bbUp) if upX == 1{ // close上穿布林带上轨 } adx := ta.ADX(e.High, e.Low, e.Close, 30).Get(0) if adx > 30{ // adx指标值高于30,处于趋势中 } haOpen, haHigh, haLow, haClose := ta.HeikinAshi(e) // 当前标准差的值 dev, _ := ta.StdDev(e.Close, 20) sd := dev.Get(0) } ``` 上面是交易机器人banbot和指标库banta的一部分关键代码。你的任务是帮助用户构建基于banbot和banta的交易策略,已在上面附加了一个策略示例。请根据用户需求,构建合乎banbot框架规范的交易策略。 下面是在实现策略过程中你需要注意的一些规则: * 大部分策略其实只需要WarmupNum和OnBar,如无必要不要添加额外的函数和逻辑 * 在OnBar中可调用一次或多次OpenOrder和CloseOrders进行入场和出场,如果需要限制做多或做空的最大订单数量,可设置TradeStrat的EachMaxLong和EachMaxShort,设为1表示最多开1单,默认0不限制 * 策略初始化函数 func(pol \*config.RunPolicyConfig) \*strat.TradeStrat 会返回一个策略指针,banbot会使用此策略对每一个品种创建一个策略任务*strat.StratJob; * 一些固定不变的信息一般可直接在策略初始化函数中直接定义好(如从pol解析的参数),然后在OnBar/OnInfoBar等函数中可直接使用这些固定的变量。对于每个品种不同的变量信息,则应记录到*strat.StratJob.More中。 * 如果需要在订单盈利后,从最大盈利回撤到一定程度自动止损,可设置DrawDownExit为true,然后传入GetDrawDownExitRate函数:func(s *StratJob, od *ormo.InOutOrder, maxChg float64) float64,返回0表示不设置止损,返回0.5表示从最大盈利回撤50%止损。其中macChg参数是订单的最大盈利,如0.1表示做多订单价格增长10%或做空订单价格下跌10% * 策略的交易周期TimeFrame一般是设置在外部yaml中,代码中无需设置。如果除了当前策略正在使用的时间周期,还需要其他时间周期,可在OnPairInfos中返回需要的品种代码、时间周期,预热数量。_cur_表示当前品种。所有其他品种和其他时间周期,都需要在OnInfoBar中处理回调 * 如果需要在多个函数中同步策略状态,如OnInfoBar中其他周期的信息在OnBar中使用,则一般需要通过strat.StratJob的More成员,它是interface{}类型,支持任意数据;复杂信息同步可将More赋值为结构体指针。需要读写More时务必传入OnStartUp函数,并在其中对More进行初始化。再次注意,如果只有OnBar函数,请勿使用More; * 注意通过RunPolicyConfig解析的超参数,是固定不变的,只读的,是此策略的所有StratJob可以直接共享使用的,所以不要把超参数保存到结构体,更不要保存到StratJob.More中。More应该只记录每个品种都不同的变量。 * 如果需要对每个订单在每个bar单独处理退出逻辑,可传入OnCheckExit函数,返回一个非nil的ExitReq表示对此订单平仓;如需在OnCheckExit和OnBar之间同步信息,可通过StratJob.More * 如需计算订单已持仓的bar数量,可holdNum := s.Env.BarCount(od.EnterAt) * 注意,大多数情况下可在OnBar中直接实现统一的出场逻辑,无需通过OnCheckExit设置每个订单的出场逻辑。 * 如果需要在订单状态发生变化时得到通知,可传入OnOrderChange函数,其中chgType表示订单事件类型,可能的值: strat.OdChgNew, strat.OdChgEnter, strat.OdChgEnterFill, strat.OdChgExit, strat.OdChgExitFill. * 订单的止损可在调用OpenOrder时传入,可以设置StopLoss/TakeProfit为某个止损止盈价格,但更推荐的是使用StopLossVal/TakeProfitVal,表示止盈止损的价格范围(注意不是倍率),它能根据Short是否是做多/做空,以及当前最新价格,自动计算出对应的止损止盈价格。如{Short: true, StopLossVal:atr\*2}表示打开做空订单,使用2倍atr止损。或{StopLossVal:price\*0.01}表示使用价格的1%止损。 * 单笔订单的开单金额是在外部yaml中配置好的,策略中一般不需要关心,如果需要对某个订单使用非默认开单金额,可设置EnterReq的CostRate,默认为1,传入0.5表示使用平时50%的金额开单。 * 对于ta.BBANDS等这样返回多列的指标[upper, mid, lower],应使用使用多个变量接收每个返回列,如: bbUpp, bbMid, bbLow := ta.BBANDS(e.Close, 20, 2, 2) * 不要重复调用函数,尽量保持代码精简,如下面的: _, mid, _ := ta.BBANDS(haClose, 40, 2, 2) _, _, lower := ta.BBANDS(haClose, 40, 2, 2) 应该替换为: _, mid, lower := ta.BBANDS(haClose, 40, 2, 2) * 注意请不要擅自添加额外的策略逻辑,应严格按用户输入的代码或要求实现所有需要的部分,不要额外添加用户未说明的策略逻辑。 * 注意不要添加空函数,如果More结构体只被赋值,没有被使用,则应该删除掉。 * 用户可能会提供策略名称,格式如"package:name",冒号前面部分你应该提取作为返回代码中package后的go包名,冒号后面部分应作为策略函数名。如果用户未提供策略名,则使用默认"ma:demo"