- 策略广场
- 股票网格策略
股票网格策略
Author:
雨幕(youquant), Date: 2020-12-22 10:21:00
Tags:
网格
/*backtest
start: 2019-04-01 09:00:00
end: 2020-12-13 15:00:00
period: 1d
basePeriod: 1d
exchanges: [{"eid":"Futures_XTP","currency":"STOCK","minFee":0}]
args: [["StrIds","[\"600519.SH\", \"600690.SH\", \"600006.SH\", \"601328.SH\", \"600887.SH\"]"],["BeginPrices","[-1, -1, -1, -1, -1]"]]
*/
var Ids = []
var _Symbols = []
var STATE_IDLE = 0
var STATE_LONG = 1
var SlideTick = 10
var StatusMsg = ""
var _Chart = null
var _ArrChart = []
var Interval = 1000
var ArrStateStr = ["空闲", "多仓"]
var TradeAmount = 100
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(!IsVirtual() && 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)
}
}
if (needOpen < insDetail.LotSize || (needOpen % insDetail.LotSize != 0 && !IsVirtual())) {
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(!IsVirtual() && 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)
BPs = JSON.parse(BeginPrices)
if (Ids.length != BPs.length) {
throw "起始价格数组与股票代码数组数量不匹配!请检查参数设置。"
}
/* 回测系统已经修改下单量为股票股数
if (IsVirtual()) {
TradeAmount = 1
}
*/
for (var i = 0 ; i < Ids.length ; i++) {
_Symbols[i] = {}
_Symbols[i].ContractTypeName = Ids[i]
_Symbols[i].LastBarTime = 0
_Symbols[i].State = STATE_IDLE
_Symbols[i].ChartIndex = i
_Symbols[i].Status = ""
_Symbols[i].Pos = null
_Symbols[i].InsDetail = null
_Symbols[i].BeginPrice = BPs[i]
_Symbols[i].CurrPrice = null
_Symbols[i].ChartCfg = {
__isStock: true,
title: {
text: Ids[i]
},
yAxis: {
plotLines: []
},
series: [{
type: 'candlestick',
name: '当前周期',
id: 'primary',
data: []
}]
}
_ArrChart.push(_Symbols[i].ChartCfg)
}
_Chart = Chart(_ArrChart)
if (IsReset) {
_Chart.reset()
} else {
_Chart.reset(1000)
}
}
function Process(symbols) {
for (var i = 0 ; i < symbols.length ; i++) {
var contractTypeName = symbols[i].ContractTypeName
var insDetail = _C(exchange.SetContractType, contractTypeName)
symbols[i].InsDetail = insDetail
symbols[i].InstrumentName = insDetail.InstrumentName
// 判断是不是交易状态
if ((!insDetail.IsTrading && !IsVirtual()) || !IsTrading()) {
continue
}
Sleep(2000)
var r = exchange.GetRecords()
if (!r) {
continue
}
Sleep(2000)
var ticker = exchange.GetTicker()
if (!ticker) {
continue
}
if (IsVirtual()) {
ticker.Info = {}
ticker.Info.LotSize = 1
ticker.Info.PriceSpread = 0.01
}
Sleep(2000)
var depth = exchange.GetDepth()
if (!depth || depth.Bids[0].Amount == 0 || depth.Asks[0].Amount == 0) {
// 标记涨跌停
symbols[i].Status = "涨跌停"
continue
}
symbols[i].Status = "正常交易"
// 检测持仓
Sleep(2000)
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
}
// 更新行情数据
symbols[i].CurrPrice = ticker.Last
if (symbols[i].BeginPrice == -1) {
symbols[i].BeginPrice = ticker.Last
}
var Bar = r[r.length - 1]
var index = symbols[i].ChartIndex
if (symbols[i].LastBarTime !== Bar.Time) {
if (symbols[i].LastBarTime > 0) {
var PreBar = r[r.length - 2]
_Chart.add(index, [PreBar.Time, PreBar.Open, PreBar.High, PreBar.Low, PreBar.Close], -1)
} else {
for (var j = r.length; j > 1; j--) {
var b = r[r.length - j]
_Chart.add(index, [b.Time, b.Open, b.High, b.Low, b.Close])
}
}
_Chart.add(index, [Bar.Time, Bar.Open, Bar.High, Bar.Low, Bar.Close])
// symbols[i].ChartCfg.yAxis.plotLines[0].value = symbols[i].BeginPrice
// _Chart.update(_ArrChart)
symbols[i].LastBarTime = Bar.Time
} else {
_Chart.add(index, [Bar.Time, Bar.Open, Bar.High, Bar.Low, Bar.Close], -1)
}
// 交易逻辑
var hasPosAmount = posAmount
var beginPrice = symbols[i].BeginPrice
var count = 0
symbols[i].ChartCfg.yAxis.plotLines = []
while (true) {
if (count == 0) {
symbols[i].ChartCfg.yAxis.plotLines.push({
value: beginPrice,
color: 'red',
width: 2,
label: {
text: '起始价格',
align: 'center'
},
})
count++
continue
}
beginPrice = beginPrice - beginPrice * DiffPricePercent
hasPosAmount = hasPosAmount - TradeAmount
symbols[i].ChartCfg.yAxis.plotLines.push({
value: beginPrice,
color: 'blue',
width: 2,
label: {
text: '节点:' + count,
align: 'center'
},
})
if (hasPosAmount < 0) {
_Chart.update(_ArrChart)
break
}
count++
}
if (ticker.Last < beginPrice) {
// 买入
Log(contractTypeName, "开多仓", "beginPrice:", beginPrice, "posAmount:", posAmount)
Buy(exchange, contractTypeName, TradeAmount, ticker.Info)
symbols[i].State = STATE_LONG
}
if (posAmount > 0 && pos.CanCoverAmount > 0 && ticker.Last > pos.Price + pos.Price * DiffPricePercent * 2) {
// 平仓
Log(contractTypeName, "平多仓")
Sell(exchange, contractTypeName, Math.min(pos.CanCoverAmount, posAmount), ticker.Info)
symbols[i].State = STATE_IDLE
}
}
}
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([_Symbols[i].ContractTypeName, _Symbols[i].InsDetail ? _Symbols[i].InsDetail.InstrumentName : "--", _Symbols[i].CurrPrice, _Symbols[i].Status])
}
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)
}
}
template: strategy.tpl:40:21: executing "strategy.tpl" at <.api.GetStrategyListByName>: wrong number of args for GetStrategyListByName: want 7 got 6