输入/搜索内容
0
关注
224
关注者
股票多品种平衡策略设计教学
创建于 2022-03-07 13:50:52  更新于 2024-11-26 20:40:58
 0
 1106

img

股票多品种平衡策略设计教学

鉴于股票策略在youquant.com上相对较少,喜欢研究股票程序化、量化交易的同学们借鉴、参考的内容不多。为了更加方便程序化、量化交易入门的同学学习、研究。本篇我们一起来实现一个简单的股票多品种策略设计。OK!撸起袖子加油~宽客们。

策略思路

策略思路很简单,借鉴于youquant.com站的一个策略。同类型的策略在不可描述的区块市场上也有所作为。

策略逻辑简单说就是:
以账户初期资产为基数,根据策略参数设置一个百分比,计算出每只股票的持有价值。设置的股票池参数中每只股票按持有价值持仓。当价格上涨,使持仓的价值超过既定的持仓价值时,卖出一部分股票平衡到最初的持仓价值。当价格下跌,使持仓的价值低于既定的持仓价值时,买入一部分股票平衡到最初的持仓价值。

策略是不是非常简单。

实现策略

参数设计:
img

策略代码:

javascript
/*backtest start: 2016-05-01 09:00:00 end: 2022-03-06 15:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Futures_XTP","currency":"STOCK","balance":100000,"minFee":0}] args: [["contractTypes","600519.SH,600690.SH,600006.SH,601328.SH,600887.SH,600121.SH,601633.SH"]] */ // 全局变量 var StatusMsg = "" function newDate() { var timezone = 8 //目标时区时间,东八区 var offset_GMT = new Date().getTimezoneOffset() // 本地时间和格林威治的时间差,单位为分钟 var nowDate = new Date().getTime() // 本地时间距 1970 年 1 月 1 日午夜(GMT 时间)之间的毫秒数 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) } } } } } /* 1、9:15-9:25为开盘集合竞价; 2、9:30-11:30,13:00-14:57为连续竞价阶段; 3、14:57-15:00为收盘集合竞价。 */ function IsTrading() { var now = newDate() // 使用 newDate() 代替 new Date() 因为服务器时区问题 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 main(){ var cts = contractTypes.split(",") var acc = _C(exchange.GetAccount) var value = _N(acc.Balance * ratio, 0) while (true) { var rowsData = [] _.each(cts, function(ct) { if (!IsTrading()) { return } var info = exchange.SetContractType(ct) if (!info) { return } var ticker = exchange.GetTicker() if (!ticker) { return } if (IsVirtual()) { ticker.Info = {} ticker.Info.LotSize = info.VolumeMultiple ticker.Info.PriceSpread = 0.01 } var pos = GetPosition(exchange, ct) if (!pos) { // 没有持仓,按照价值建仓 var amount = parseInt(value / ticker.Last / info.VolumeMultiple) if (amount < 1) { return } amount = amount * info.VolumeMultiple Buy(exchange, ct, amount, ticker.Info) } else { // 计算价值偏离,平衡仓位 var nowValue = ticker.Last * pos.Amount var diffValue = nowValue - value if (diffValue > 0) { // 持有价值增加超过阈值 var amount = parseInt(diffValue / ticker.Last / info.VolumeMultiple) if (amount >= 1) { // 平衡 amount = amount * info.VolumeMultiple Sell(exchange, ct, amount, ticker.Info) var newPos = GetPosition(exchange, ct) Log("当前持仓价值:", pos.Amount + " * " + ticker.Last + " = " + nowValue, ",初期价值:", value, ",本次平衡价值:", amount * ticker.Last, ",平衡后的持仓价值:", newPos.Amount * ticker.Last) } } else if (diffValue < 0) { // 持有价值减少超过阈值 var amount = parseInt(-diffValue / ticker.Last / info.VolumeMultiple) if (amount >= 1) { // 平衡 amount = amount * info.VolumeMultiple Buy(exchange, ct, amount, ticker.Info) var newPos = GetPosition(exchange, ct) Log("当前持仓价值:", pos.Amount + " * " + ticker.Last + " = " + nowValue, ",初期价值:", value, ",本次平衡价值:", amount * ticker.Last, ",平衡后的持仓价值:", newPos.Amount * ticker.Last) } } } rowsData.push([info.InstrumentID, info.InstrumentName, info.VolumeMultiple, ticker.Last, pos ? pos.Price : "--", pos ? pos.Amount : "--", value, pos ? ticker.Last * pos.Amount : "--"]) }) var tbl = { "type" : "table", "title" : "行情、持仓数据", "cols" : ["股票代码", "名称", "一手乘数", "当前价格", "持仓价格", "持仓数量", "初期价值", "当前价值"], "rows" : rowsData } LogStatus("日期时间:", _D(), ", 状态信息:", StatusMsg, "\n`" + JSON.stringify(tbl) + "`") Sleep(1000) } }

策略主要逻辑就在于对于持仓价值的计算。计算偏移出的价值是否足够至少一手的交易量。

pine
if (!pos) { // 没有持仓,按照价值建仓 var amount = parseInt(value / ticker.Last / info.VolumeMultiple) if (amount < 1) { return } amount = amount * info.VolumeMultiple Buy(exchange, ct, amount, ticker.Info) } else { // 计算价值偏离,平衡仓位 var nowValue = ticker.Last * pos.Amount var diffValue = nowValue - value if (diffValue > 0) { // 持有价值增加超过阈值 var amount = parseInt(diffValue / ticker.Last / info.VolumeMultiple) if (amount >= 1) { // 平衡 amount = amount * info.VolumeMultiple Sell(exchange, ct, amount, ticker.Info) var newPos = GetPosition(exchange, ct) Log("当前持仓价值:", pos.Amount + " * " + ticker.Last + " = " + nowValue, ",初期价值:", value, ",本次平衡价值:", amount * ticker.Last, ",平衡后的持仓价值:", newPos.Amount * ticker.Last) } } else if (diffValue < 0) { // 持有价值减少超过阈值 var amount = parseInt(-diffValue / ticker.Last / info.VolumeMultiple) if (amount >= 1) { // 平衡 amount = amount * info.VolumeMultiple Buy(exchange, ct, amount, ticker.Info) var newPos = GetPosition(exchange, ct) Log("当前持仓价值:", pos.Amount + " * " + ticker.Last + " = " + nowValue, ",初期价值:", value, ",本次平衡价值:", amount * ticker.Last, ",平衡后的持仓价值:", newPos.Amount * ticker.Last) } } }

策略回测

img

img

img

平衡策略用于操作多只股票组成的股票池,可以分散投资。回测中每只股票投入资金只有5000元,并且根据价格变动不断调整持仓。由于策略开始需要持有底仓,等于开始做多。所以最好还是选择一些潜力较好的股票。

策略地址:https://www.youquant.com/strategy/349108

当然,策略回测仅供于参考。策略设计仅仅为了学习、研究,实盘还需进一步的优化、升级。

对于0基础的小伙伴来说以上的策略代码可能有些难懂。所以,对于不会编程、交易的小伙伴们推荐一本非常实用的书籍,无缝贴合http://youquant.com量化交易平台的量化交易实战,会非常方便入门量化交易领域。

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