输入/搜索内容
3
关注
225
关注者
股票均线策略
交流分享
创建于 2020-12-14 13:51:41  更新于 2023-11-29 21:36:31
 2
 2882

img

股票均线策略

趋势策略中最简单的策略就是均线策略了,通过上一篇文章中我们探讨一个股票策略的几点特殊之处,并实现了一个经典策略。再来实现一个双均线策略就十分简单了。我们把之前的「股票DualThrust策略」中的策略逻辑等相关内容剔除,就拿到了一个股票策略的基本结构。这个程序结构可以复用于我们的双均线策略。其实就是把双均线的交易逻辑、数据处理等写进去就可以了,趋势策略基本都可以这样复用代码。

程序结构框架

剔除了之前策略交易逻辑后剩下的程序结构,这个程序还不能直接使用。

var Ids = [] // ["600519.SH"] var _Symbols = [] var STATE_IDLE = 0 var STATE_LONG = 1 var SlideTick = 2 var StatusMsg = "" var _Chart = null var _ArrChart = [] var Interval = 1000 var ArrStateStr = ["空闲", "多仓"] function newDate() { var timezone = 8 var offset_GMT = new Date().getTimezoneOffset() var nowDate = new Date().getTime() var targetDate = new Date(nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000) return targetDate } function GetPosition(e, contractTypeName) { var allAmount = 0 var allProfit = 0 var allFrozen = 0 var posMargin = 0 var price = 0 var direction = null positions = _C(e.GetPosition) for (var i = 0; i < positions.length; i++) { if (positions[i].ContractType != contractTypeName) { continue } if (positions[i].Type == PD_LONG) { posMargin = positions[i].MarginLevel allAmount += positions[i].Amount allProfit += positions[i].Profit allFrozen += positions[i].FrozenAmount price = positions[i].Price direction = positions[i].Type } } if (allAmount === 0) { return null } return { MarginLevel: posMargin, FrozenAmount: allFrozen, Price: price, Amount: allAmount, Profit: allProfit, Type: direction, ContractType: contractTypeName, CanCoverAmount: allAmount - allFrozen } } function Buy(e, contractType, opAmount, insDetail) { var initPosition = GetPosition(e, contractType) var isFirst = true var initAmount = initPosition ? initPosition.Amount : 0 var positionNow = initPosition if(opAmount % insDetail.LotSize != 0) { throw "每手数量不匹配" } while (true) { var needOpen = opAmount if (isFirst) { isFirst = false } else { Sleep(Interval*20) positionNow = GetPosition(e, contractType) if (positionNow) { needOpen = opAmount - (positionNow.Amount - initAmount) } Log("positionNow:", positionNow, "needOpen:", needOpen)// 测试 } if (needOpen < insDetail.LotSize || needOpen % insDetail.LotSize != 0) { break } var depth = _C(e.GetDepth) // 需要检测是否涨跌停 var amount = needOpen e.SetDirection("buy") var orderId = e.Buy(depth.Asks[0].Price + (insDetail.PriceSpread * SlideTick), amount, contractType, 'Ask', depth.Asks[0]) // CancelPendingOrders while (true) { Sleep(Interval*20) var orders = _C(e.GetOrders) if (orders.length === 0) { break } for (var j = 0; j < orders.length; j++) { e.CancelOrder(orders[j].Id) if (j < (orders.length - 1)) { Sleep(Interval*20) } } } } var ret = null if (!positionNow) { return ret } ret = positionNow return ret } function Sell(e, contractType, lots, insDetail) { var initAmount = 0 var firstLoop = true if(lots % insDetail.LotSize != 0) { throw "每手数量不匹配" } while (true) { var n = 0 var total = 0 var positions = _C(e.GetPosition) var nowAmount = 0 for (var i = 0; i < positions.length; i++) { if (positions[i].ContractType != contractType) { continue } nowAmount += positions[i].Amount } if (firstLoop) { initAmount = nowAmount firstLoop = false } var amountChange = initAmount - nowAmount if (typeof(lots) == 'number' && amountChange >= lots) { break } for (var i = 0; i < positions.length; i++) { if (positions[i].ContractType != contractType) { continue } var amount = positions[i].Amount var depth var opAmount = 0 var opPrice = 0 if (positions[i].Type == PD_LONG) { depth = _C(e.GetDepth) // 需要检测是否涨跌停 opAmount = amount opPrice = depth.Bids[0].Price - (insDetail.PriceSpread * SlideTick) } if (typeof(lots) === 'number') { opAmount = Math.min(opAmount, lots - (initAmount - nowAmount)) } if (opAmount > 0) { if (positions[i].Type == PD_LONG) { e.SetDirection("closebuy") e.Sell(opPrice, opAmount, contractType, "平仓", 'Bid', depth.Bids[0]) } n++ } // break to check always if (typeof(lots) === 'number') { break } } if (n === 0) { break } while (true) { Sleep(Interval*20) var orders = _C(e.GetOrders) if (orders.length === 0) { break } for (var j = 0; j < orders.length; j++) { e.CancelOrder(orders[j].Id) if (j < (orders.length - 1)) { Sleep(Interval*20) } } } } } function IsTrading() { // 使用 newDate() 代替 new Date() 因为服务器时区问题 var now = newDate() var day = now.getDay() var hour = now.getHours() var minute = now.getMinutes() StatusMsg = "非交易时段" if (day === 0 || day === 6) { return false } if((hour == 9 && minute >= 30) || (hour == 11 && minute < 30) || (hour > 9 && hour < 11)) { // 9:30-11:30 StatusMsg = "交易时段" return true } else if (hour >= 13 && hour < 15) { // 13:00-15:00 StatusMsg = "交易时段" return true } return false } function init () { Ids = JSON.parse(StrIds) for (var i = 0 ; i < Ids.length ; i++) { _Symbols[i] = {} _Symbols[i].ContractTypeName = Ids[i] _Symbols[i].State = STATE_IDLE _Symbols[i].ChartIndex = i _Symbols[i].Status = "" _Symbols[i].Pos = null _Symbols[i].ChartCfg = { // 图表对象 } _ArrChart.push(_Symbols[i].ChartCfg) } _Chart = Chart(_ArrChart) _Chart.reset() } function Process (symbols) { for (var i = 0 ; i < symbols.length ; i++) { var contractTypeName = symbols[i].ContractTypeName var insDetail = _C(exchange.SetContractType, contractTypeName) symbols[i].InstrumentName = insDetail.InstrumentName // 判断是不是交易状态 if (!insDetail.IsTrading || !IsTrading()) { continue } var depth = exchange.GetDepth() if (!depth || depth.Bids[0].Amount == 0 || depth.Asks[0].Amount == 0) { // 标记涨跌停 symbols[i].Status = "涨跌停" continue } symbols[i].Status = "正常交易" // 检测持仓 var pos = GetPosition(exchange, contractTypeName) symbols[i].Pos = pos var posAmount = pos ? pos.Amount : 0 // 同步持仓状态 if (symbols[i].State == STATE_IDLE && posAmount > 0) { symbols[i].State = STATE_LONG } else if (symbols[i].State == STATE_LONG && posAmount == 0) { symbols[i].State = STATE_IDLE } // 执行交易 // Buy(exchange, contractTypeName, AmountOP, ticker.Info) // Sell(exchange, contractTypeName, AmountOP, ticker.Info) } } function main(){ if(IsReset) { LogReset(1) } SetErrorFilter("market not ready") exchange.SetPrecision(3, 0) if((!IsVirtual() && exchange.GetCurrency() != "STOCK" && exchange.GetName() != "Futures_Futu") || (IsVirtual() && exchange.GetCurrency() != "STOCK_CNY" && exchange.GetName() != "Futures_XTP")) { Log("currency:", exchange.GetCurrency(), "name:", exchange.GetName()) throw "不支持" } while(true){ var tbl = { "type" : "table", "title": "信息", "cols": [], "rows": [], } for(var i = 0 ; i < _Symbols.length; i++) { tbl.rows.push([]) } var tblPos = { "type" : "table", "title" : "持仓", "cols" : ["名称", "价格", "数量", "盈亏", "类型", "冻结数量", "可平量"], "rows" : [], } for (var j = 0 ; j < _Symbols.length; j++) { if(_Symbols[j].Pos) { tblPos.rows.push([_Symbols[j].Pos.ContractType, _Symbols[j].Pos.Price, _Symbols[j].Pos.Amount, _Symbols[j].Pos.Profit, _Symbols[j].Pos.Type, _Symbols[j].Pos.FrozenAmount, _Symbols[j].Pos.CanCoverAmount]) } } LogStatus(_D(), StatusMsg, "\n`" + JSON.stringify([tbl, tblPos]) + "`") Process(_Symbols) Sleep(1000) } }

双均线策略

  • 首先调整一下界面参数
    img

    添加上双均线需要的策略参数。

  • 添加策略交易逻辑

    // 计算指标数据 var ma = TA.EMA(r, MAPeriod) // 周期小的均线指标数据(快线) var slowMa = TA.EMA(r, SlowMAPeriod) // 周期大的均线指标数据(慢线) ... // 交易信号检测 if(symbols[i].State == STATE_IDLE && ma[ma.length - 2] > slowMa[slowMa.length - 2] && ma[ma.length - 3] < slowMa[slowMa.length - 3]) { Log("快线上穿慢线", "#FF0000") Buy(exchange, contractTypeName, Amount, ticker.Info) symbols[i].State = STATE_LONG } else if (symbols[i].State == STATE_LONG && ma[ma.length - 2] < slowMa[slowMa.length - 2] && ma[ma.length - 3] > slowMa[slowMa.length - 3]) { Log("快线下穿慢线", "#FF0000") Sell(exchange, contractTypeName, Amount, ticker.Info) symbols[i].State = STATE_IDLE }

    交易信号检测其实就是判断快线上穿、下穿慢线,这里使用的是倒数第二、第三根K线柱上对应的指标判断,因为最后一个指标数据是实时的,所以会有反复交叉分开的情况。

  • 其它细节
    因为这次策略用到了2根均线指标,我们需要在K线图表上把均线画出来,所以对画图部分的代码做了小小的调整。
    对于策略状态栏显示的信息也需要略微调整,显示当前策略的均线指标、当前价格等数据。
    最近优宽平台支持了股票回测,所以我们也要针对回测系统做一些兼容,策略中一些用到IsVirtual()函数的地方就是为了回测系统做的兼容。

回测测试

回测仅仅是策略初步测试,所以不必太在意回测结果。所以我们随便选择几只股票,随便设置一些参数回测一下。

img

img

img

img

策略地址

富途模拟盘

同样也可以挂一个富途模拟盘测试,优宽平台后续也会支持更多券商。

结尾

策略仅仅用于学习交流,交流策略设计思路,互相学习。
感谢阅读,感谢提出建议及意见。

相关推荐
评论
全部评论 (0)
暂无数据
暂无数据
  • 1
iPhone 下载
社区
回测系统
© 2015 - ∞ YouQuant 豫ICP备19046564号