股票网格策略

Author: 雨幕(youquant), Date: 2020-12-22 10:21:00
Tags: 网格

股票网格策略

相关文章:https://www.fmz.cn/bbs-topic/6364


/*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)
    }
}

相关内容

更多内容