多品种海龟交易策略

Author: yuzy, Date: 2022-11-30 16:12:55
Tags:

海龟交易法则的精髓部分是明确给出入市位置、开仓规模、加仓位置、止损位置、离市平仓位置的交易系统,可以说是最全面的交易系统,非常适合期货交易者学习。

建仓资金(头寸规模)选择 海龟交易法则将市场价格上下波动量化为N值,每次建仓的头寸和加仓规模与波动量N相关。 波动量N(平均真实波动振幅ATR)ATR是日内价格最大波动的平均振幅,计算方法: TR=MAX(最高价-最低价,最高价-前日收盘价,前日收盘价-最低价) ATR=TR的N日简单移动平均价格波动 每次开仓(建仓)规模就是将总资产乘1%再除以DV(价值波动量) DV = N * (合约乘数) Unit(开仓数量)=(总资产×1%)/DV

入市位置选择 海龟交易法则的入市判断方式是以唐奇安的通道突破系统为基础。 海龟系统认为:短期价格突破20日、中长期突破55日唐奇安通道为入市信号(包含向上突破和向下突破,即做多和做空)。

加仓和止损 海龟交易法加仓位置为建仓后,价格向盈利方向突破1/2N时加仓,最多4个单位; 止损位置为亏损2N,即达到平均真实振幅值的两倍。

离市平仓位置 短期(20日唐奇安通道),多头头寸在突破过10日最低处平仓离场;空头头寸在突破过10日最高处平仓离场; 中长期(55日唐奇安通道),多头头寸突破过20日最低处平仓离场;空头头寸在突破20日最高处平仓离场。


'''backtest
start: 2022-11-01 00:00:00
end: 2022-11-30 23:59:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
'''

import re 
import json
import time 
_bot = ext.NewPositionManager()
class Manager:
    ACT_IDIE = 0 
    ACT_LONG = 1 
    ACT_SHORT = 2 
    ACT_COVER = 3 
    
    ERR_SUCCESS = 0 
    ERR_SET_SYMBOL = 1 
    ERR_GET_ORDERS = 2 
    ERR_GET_POS = 3 
    ERR_TRADE = 4 
    ERR_GET_DEPTH = 5 
    ERR_NOT_TRADING = 6 
    errMsg = ["成功","切换合约失败","获取订单失败","获取持仓失败","交易下单失败","获取深度失败","不在交易时间"]
    
    def __init__(self,needRestore,symbol,keepBalance,riskRatio,atrLen,enterPeriodA,leavePeriodA,enterPeriodB,leavePeriodB,useFilter,multiplierN,multiplierS,maxLots):
        symbolDetail = _C(exchange.SetContractType,symbol)
        if symbolDetail["VolumeMultiple"] == 0 or symbolDetail["MaxLimitOrderVolume"] == 0 or symbolDetail["MinLimitOrderVolume"] == 0 or symbolDetail["LongMarginRatio"] == 0 or symbolDetail["ShortMarginRatio"] == 0 :
            Log(symbolDetail)
            raise Exception("合约信息异常")
        else:
            Log("合约:",symbolDetail["InstrumentName"],"一手",symbolDetail["VolumeMultiple"],"份,最大下单量:",symbolDetail["MaxLimitOrderVolume"],"保证金比率:",symbolDetail["LongMarginRatio"],symbolDetail["ShortMarginRatio"],"交割日期:",symbolDetail["StartDelivDate"])
        
        self.symbol = symbol
        self.keepBalance = keepBalance
        self.riskRatio = riskRatio
        self.atrLen = atrLen
        self.enterPeriodA = enterPeriodA
        self.leavePeriodA = leavePeriodA
        self.enterPeriodB = enterPeriodB
        self.leavePeriodB = leavePeriodB
        self.useFilter = useFilter
        self.multiplierN = multiplierN
        self.multiplierS = multiplierS
        self.maxLots = maxLots
        self.symbolDetail = symbolDetail
        self.lastPrice = 0 
        
        self.task = {
            "action" : Manager.ACT_IDIE ,
            "amount" : 0 ,
            "dealAmount" : 0 ,
            "avgPrice" : 0 ,
            "preCost" : 0 ,
            "preAmount" : 0 ,
            "init" :False ,
            "retry" : 0 ,
            "desc" : "空闲" ,
            "onFinish" : None
        }
        
        self.status = {
            "symbol" : symbol ,
            "recordsLen" : 0 ,
            "vm" : [] ,
            "open" : 0 ,
            "cover" : 0 ,
            "st" : 0,
            "marketPosition" : 0 ,
            "lastPrice" : 0,
            "holdPrice" : 0 ,
            "holdAmount" : 0 ,
            "holdProfit" : 0,
            "N" : 0,
            "upLine" : 0,
            "downLine" : 0,
            "symbolDetail" : symbolDetail,
            "lastErr" : "",
            "lastErrTime" : "",
            "stopPrice" : "",
            "leavePrice" : "",
            "isTrading" : False
        }
        vm = None
        if RMode == 0 :
            vm = _G(self.symbol)
        else:
            vm = json.loads(VMStatus)[self.symbol]
        if vm :
            Log("准备恢复进度,当前合约状态为",vm)
            self.reset(vm[0],vm[1],vm[2],vm[3],vm[4])
        else:
            if needRestore :
                Log("没有找到" + self.symbol +"的进度恢复信息")
            self.reset()
    def setLastError(self,err = None):
        if err is None :
            self.status["lastErr"] = ""
            self.status["lastErrTime"] = ""
            return
        t = _D()
        self.status["lastErr"] = err 
        self.status["lastErrTime"] = t 
    def reset(self,marketPosition = None,openPrice = None,N = None,leavePeriod = None,preBreakoutFailure = None):
        if marketPosition is not None:
            self.marketPosition = marketPosition
            self.openPrice = openPrice
            self.N = N 
            self.leavePeriod = leavePeriod
            self.preBreakoutFailure = preBreakoutFailure
            pos = _bot.GetPosition(self.symbol,PD_LONG if marketPosition > 0 else PD_SHORT)
            if pos is not None:
                self.holdPrice = pos["Price"]
                self.holdAmount = pos["Amount"]
                Log(self.symbol,"仓位:",pos)
            else:
                raise Exception("恢复" + self.symbol + "的持仓状态出错,没有找到仓位信息")
            Log("恢复",self.symbol,"加仓次数:",self.marketPosition,"持仓均价:",self.holdPrice,"持仓数量:",self.holdAmount,"最后一次加仓价:",self.openPrice,"N值:",self.N,"离市周期:",self.leavePeriod,"上次突破:","失败" if self.preBreakoutFailure else "成功")
            self.status["open"] = 1 
            self.status["vm"] = [self.marketPosition,self.openPrice,self.N,self.leavePeriod,self.preBreakoutFailure]
        else:
            self.marketPosition = 0 
            self.holdPrice = 0 
            self.openPrice = 0 
            self.holdAmount = 0 
            self.holdProfit = 0 
            self.preBreakoutFailure = True
            self.N = 0 
            self.leavePeriod = self.leavePeriodA
        self.holdProfit = 0 
        self.lastErr = ""
        self.lastErrTime = ""
    def Status(self):
        self.status["N"] = self.N 
        self.status["marketPosition"] = self.marketPosition 
        self.status["holdProfit"] = self.holdProfit
        self.status["holdAmount"] = self.holdAmount
        self.status["lastPrice"] = self.lastPrice
        if self.lastPrice > 0 and self.holdAmount > 0 and self.marketPosition != 0 :
            self.status["holdProfit"] = _N((self.lastPrice - self.holdPrice)*self.holdAmount * self.symbolDetail["VolumeMultiple"], 4) *(1 if self.marketPosition > 0 else -1)
        else:
            self.status["holdProfit"] = 0 
        return self.status
    def setTask(self,action,amount = None,onFinish = None):
        self.task["init"] = False
        self.task["retry"] = 0 
        self.task["action"] = action
        self.task["preAmount"] = 0 
        self.task["preCost"] = 0 
        self.task["amount"] = 0 if amount is None else amount
        self.task["onFinish"] = onFinish
        if action == Manager.ACT_IDIE :
            self.task["desc"] = "空闲"
            self.task["onFinish"] = None 
        else:
            if action != Manager.ACT_COVER:
                self.task["desc"] = ("加多仓" if action == Manager.ACT_LONG else "加空仓") + "(" + str(amount) + ")"
            else:
                self.task["desc"] = "平仓"
            Log("接收到任务",self.symbol,self.task["desc"])
            self.Poll(True)
    def processTask(self):
        insDetail = exchange.SetContractType(self.symbol)
        if not insDetail:
            return Manager.ERR_SET_SYMBOL
        SlideTick = 1 
        ret = False
        if self.task["action"] == Manager.ACT_COVER:
            hasPosition = False
            while True:
                if not ext.IsTrading(self.symbol):
                    return Manager.ERR_NOT_TRADING
                hasPosition = False
                positions = exchange.GetPosition()
                if positions is None:
                    return Manager.ERR_GET_POS
                depth = exchange.GetDepth()
                if depth is None:
                    return Manager.ERR_GET_DEPTH
                orderID = None
                for i in range(len(positions)):
                    if positions[i]["ContractType"] != self.symbol:
                        continue
                    amount = min(insDetail["MaxLimitOrderVolume"], positions[i]["Amount"])
                    if positions[i]["Type"] == PD_LONG or positions[i]["Type"] == PD_LONG_YD :
                        exchange.SetDirection("closebuy_today" if positions[i]["Type"] == PD_LONG else "closebuy")
                        orderID = exchange.Sell(_N(depth["Bids"][0]["Price"] - (insDetail["PriceTick"] * SlideTick), 2),min(amount, depth["Bids"][0]["Amount"]), self.symbol,"平今" if positions[i]["Type"] == PD_LONG else "平昨", "Bid",depth["Bids"][0])
                        hasPosition = True
                    elif positions[i]["Type"] == PD_SHORT or positions[i]["Type"] == PD_SHORT_YD :
                        exchange.SetDirection("closesell_today" if positions[i]["Type"] == PD_SHORT else "closesell")
                        orderID = exchange.Buy(_N(depth["Asks"][0]["Price"] + (insDetail["PriceTick"] * SlideTick), 2),min(amount, depth["Asks"][0]["Amount"]), self.symbol,"平今" if positions[i]["Type"] == PD_SHORT else "平昨","Ask",depth["Asks"][0])
                        hasPosition = True
                    if hasPosition:
                        if not orderID:
                            return Manager.ERR_TRADE
                        Sleep(1000)
                        while True:
                            orders = exchange.GetOrders()
                            if orders is None:
                                return Manager.ERR_GET_ORDERS
                            if len(orders) == 0:
                                break
                            for i in range(len(orders)):
                                exchange.CancelOrder(orders[i]["Id"])
                                Sleep(500)
                if not hasPosition:
                    break
            ret = True
        elif self.task["action"] == Manager.ACT_LONG or self.task["action"] == Manager.ACT_SHORT:
            while True:
                if not ext.IsTrading(self.symbol):
                    return Manager.ERR_NOT_TRADING
                Sleep(1000)
                while True:
                    orders = exchange.GetOrders()
                    if orders is None:
                        return Manager.ERR_GET_ORDERS
                    if len(orders) == 0:
                        break
                    for i in range(len(orders)):
                        exchange.CancelOrder(orders[i]["Id"])
                        Sleep(500)
                positions = exchange.GetPosition()
                if positions is None:
                    return Manager.ERR_GET_POS
                pos = None
                for i in range(len(positions)):
                    if positions[i]["ContractType"] == self.symbol and (((positions[i]["Type"] == PD_LONG or positions[i]["Type"] == PD_LONG_YD) and self.task["action"] == Manager.ACT_LONG) or ((positions[i]["Type"] == PD_SHORT) or positions[i]["Type"] == PD_SHORT_YD) and self.task["action"] == Manager.ACT_SHORT):
                        if not pos:
                            pos = positions[i]
                            pos["Cost"] = positions[i]["Price"] * positions[i]["Amount"]
                        else:
                            pos["Amount"] += positions[i]["Amount"]
                            pos["Profit"] += positions[i]["Profit"]
                            pos["Cost"] += positions[i]["Price"] * positions[i]["Amount"]
                if not self.task["init"]:
                    self.task["init"] = True
                    if pos:
                        self.task["preAmount"] = pos["Amount"]
                        self.task["preCost"] = pos["Cost"]
                    else:
                        self.task["preAmount"] = 0 
                        self.task["preCost"] = 0 
                remain = self.task["amount"]
                if pos:
                    self.task["dealAmount"] = pos["Amount"] - self.task["preAmount"]
                    remain = self.task["amount"] - self.task["dealAmount"]
                    if remain <= 0 or self.task["retry"] >= MaxTaskRetry:
                        ret = {
                            "price" : (pos["Cost"] - self.task["preCost"]) / (pos["Amount"] - self.task["preAmount"]),
                            "amount" : (pos["Amount"] - self.task["preAmount"]),
                            "position" : pos
                        }
                        break
                elif self.task["retry"] >= MaxTaskRetry:
                    ret = None
                    break
                depth = exchange.GetDepth()
                if depth is None:
                    return Manager.ERR_GET_DEPTH
                orderID = None 
                if self.task["action"] == Manager.ACT_LONG:
                    exchange.SetDirection("buy")
                    orderID = exchange.Buy(_N(depth["Asks"][0]["Price"] + (insDetail["PriceTick"] * SlideTick), 2), min(remain,depth["Asks"][0]["Amount"]),self.symbol,"Ask",depth["Asks"][0])
                else:
                    exchange.SetDirection("sell")
                    orderID = exchange.Sell(_N(depth["Bids"][0]["Price"] - (insDetail["PriceTick"] * SlideTick), 2), min(remain,depth["Bids"][0]["Amount"]),self.symbol,"Bid",depth["Bids"][0])
                if orderID is None:
                    self.task["retry"] += 1 
                    return Manager.ERR_TRADE
        if self.task["onFinish"]:
            self.task["onFinish"](ret)
        self.setTask(Manager.ACT_IDIE)
        return Manager.ERR_SUCCESS
    def Poll(self,subroutine = False):
        self.status["isTrading"] = ext.IsTrading(self.symbol)
        if not self.status["isTrading"]:
            return
        if self.task["action"] != Manager.ACT_IDIE:
            retCode = self.processTask()
            if self.task["action"] != Manager.ACT_IDIE:
                self.setLastError("任务没有处理成功:" + Manager.errMsg[retCode] + ", " + self.task["desc"] + ", 重试:" + str(self.task["retry"]))
            else:
                self.setLastError()
            return
        if subroutine:
            return
        suffix = "@" if Push else ""
        _C(exchange.SetContractType,self.symbol)
        records = exchange.GetRecords()
        if records is None:
            self.setLastError("获取K线数据失败")
            return
        self.status["recordsLen"] = len(records)
        if len(records) < self.atrLen:
            self.setLastError("K线数据长度小于ATR:" + str(self.atrLen))
            return
        opCode = 0 
        lastPrice = records[-1]["Close"]
        self.lastPrice = lastPrice
        if self.marketPosition == 0 :
            self.status["stopPrice"] = "--"
            self.status["leavePrice"] = "--"
            self.status["upLine"] = 0 
            self.status["downLine"] = 0 
            for i in range(2):
                if i == 0 and self.useFilter and not self.preBreakoutFailure:
                    continue
                enterPeriod = self.enterPeriodA if i == 0 else self.enterPeriodB
                if len(records) < (enterPeriod+1):
                    continue
                highest = TA.Highest(records, enterPeriod, "High")
                lowest = TA.Lowest(records, enterPeriod, "Low")
                self.status["upLine"] = highest if self.status["upLine"] == 0 else min(self.status["upLine"], highest)
                self.status["downLine"] = lowest if self.status["downLine"] == 0 else max(self.status["upLine"], lowest)
                if lastPrice > highest:
                    opCode = 1 
                elif lastPrice < lowest:
                    opCode = 2 
                self.leavePeriod = self.leavePeriodA if (enterPeriod == self.enterPeriodA) else self.leavePeriodB
        else:
            spread = (self.openPrice - lastPrice) if self.marketPosition > 0 else (lastPrice - self.openPrice)
            self.status["stopPrice"] = _N(self.openPrice + (self.N * StopLossRatio * (-1 if self.marketPosition > 0 else 1)))
            if spread > (self.N * StopLossRatio):
                opCode = 3 
                self.preBreakoutFailure = True
                Log(self.symbolDetail["InstrumentName"], "止损平仓", suffix)
                self.status["st"] += 1 
            elif -spread > (IncSpace * self.N):
                opCode = 1 if self.marketPosition > 0 else 2 
            elif len(records) > self.leavePeriod :
                self.status["leavePrice"] = TA.Lowest(records,self.leavePeriod,"Low") if self.marketPosition > 0 else TA.Highest(records,self.leavePeriod,"High")
                if (self.marketPosition > 0 and lastPrice < self.status["leavePrice"]) or (self.marketPosition < 0 and lastPrice > self.status["leavePrice"]):
                    self.preBreakoutFailure = True
                    Log(self.symbolDetail["InstrumentName"] , "正常平仓", suffix)
                    opCode = 3
                    self.status["cover"] += 1 
        if opCode == 0:
            return
        if opCode == 3:
            def coverCallBack(ret):
                self.reset()
                _G(self.symbol,None)
            self.setTask(Manager.ACT_COVER,0,coverCallBack)
            return
        if abs(self.marketPosition) >= self.maxLots:
            self.setLastError("禁止开仓,超过最大开仓次数" + str(self.maxLots))
            return
        atrs = TA.ATR(records,self.atrLen)
        N = _N(atrs[len(atrs) - 1],4)
        account = _bot.GetAccount()
        currMargin = json.loads(exchange.GetRawJSON())["CurrMargin"]
        unit = int((account["Balance"] + currMargin - self.keepBalance) * (self.riskRatio / 100) / N / self.symbolDetail["VolumeMultiple"])
        canOpen = int((account["Balance"] - self.keepBalance) / (self.symbolDetail["LongMarginRatio"] if opCode == 1 else self.symbolDetail["ShortMarginRatio"]) / (lastPrice*1.2) / self.symbolDetail["VolumeMultiple"])
        unit = min(unit,canOpen)
        if unit < self.symbolDetail["MinLimitOrderVolume"]:
            self.setLastError(str(unit) + "手,无法开仓")
            return
        def setTaskCallBack(ret):
            if not ret :
                self.setLastError("下单失败")
                return
            Log(self.symbolDetail["InstrumentName"], "开仓" if self.marketPosition == 0 else "加仓", "离市周期", self.leavePeriod, suffix)
            self.N = N 
            self.openPrice = ret["price"]
            self.holdPrice = ret["position"]["Price"]
            self.holdAmount = ret["position"]["Amount"]
            if self.marketPosition == 0 :
                self.status["open"] += 1 
            self.marketPosition += (1 if opCode == 1 else -1)
            self.status["vm"] = [self.marketPosition, self.openPrice, self.N, self.leavePeriod, self.preBreakoutFailure]
            _G(self.symbol,self.status["vm"])
        self.setTask(Manager.ACT_LONG if opCode == 1 else Manager.ACT_SHORT, unit, setTaskCallBack)
def onexit():
    Log("已退出策略....")
                
def main():
    while not exchange.IO("status"):
        Sleep(3000)
        LogStatus("正在等待与交易服务器连接")
    positions = _C(exchange.GetPosition)
    if len(positions) > 0 :
        Log("检测到当前持有仓位,系统开始恢复进度....")
        Log("持仓信息:",positions)
    Log("风险参数:",RiskRatio, "N值周期:",ATRlength, "系统1:入市周期",EnterPeriodA, "离市周期",LeavePeriodA, "系统2:入市周期",EnterPeriodB, "离市周期",LeavePeriodB,"加仓系数:",IncSpace,"止损系数:",StopLossRatio,"单品种最多开仓",MaxLots,"次")
    initAccount = _bot.GetAccount()
    initMargin = json.loads(exchange.GetRawJSON())["CurrMargin"]
    keepBalance = _N((initAccount["Balance"] + initMargin) * (keepRatio / 100), 3)
    Log("资产信息:",initAccount, "保留资金:",keepBalance)
    tts = []
    symbolFilter = {}
    arr = Instruments.split(",")
    for i in range(len(arr)):
        symbol = re.sub(r'/\s+$/g', "", re.sub(r'/^\s+/g', "", arr[i]))
        if symbol in symbolFilter.keys():
            raise Exception(symbol + "已经存在,请检查合约参数")
        symbolFilter[symbol] = True
        hasPosition = False
        for j in range(len(positions)):
            if positions[j]["ContractType"] == symbol :
                hasPosition = True 
                break
        obj = Manager(hasPosition,symbol,keepBalance,RiskRatio,ATRlength,EnterPeriodA,LeavePeriodA,EnterPeriodB,LeavePeriodB,UseEnterFilter,IncSpace,StopLossRatio,MaxLots)
        tts.append(obj)
    while True:
        while not exchange.IO("status"):
            Sleep(1000)
            LogStatus("正在等待与交易服务器连接")
        tblStatus = {
            "type" : "table",
            "title" : "持仓信息",
            "cols" : ["合约名称","持仓方向","持仓均价","持仓数量","持仓盈亏","加仓次数","开仓次数","止损次数","成功次数","当前价格","N"],
            "rows" : []
        }
        tblMarket = {
            "type" : "table",
            "title" : "运行状态",
            "cols" : ["合约名称","合约乘数","保证金比率","交易时间","柱线长度","上线","下线","止损价","离市价","异常描述","发生时间"],
            "rows" : []
        }
        totalHold = 0 
        vmStatus = {}
        holdSymbol = 0 
        for i in range(len(tts)):
            tts[i].Poll()
            d = tts[i].Status()
            if d["holdAmount"] > 0 :
                vmStatus[d["symbol"]] = d["vm"]
                holdSymbol += 1 
            tblStatus["rows"].append([d["symbolDetail"]["InstrumentName"], "--" if d["holdAmount"] == 0 else ("多" if d["marketPosition"] > 0 else "空"), d["holdPrice"], d["holdAmount"], d["holdProfit"], d["marketPosition"], d["open"], d["st"], d["cover"], d["lastPrice"], d["N"]])
            tblMarket["rows"].append([d["symbolDetail"]["InstrumentName"], d["symbolDetail"]["VolumeMultiple"], str(d["symbolDetail"]["LongMarginRatio"]) + "/"+ str(d["symbolDetail"]["ShortMarginRatio"]), "是#0000ff" if d["isTrading"] else "否#0000ff", d["recordsLen"],d["upLine"], d["downLine"], d["stopPrice"],d["leavePrice"],d["lastErr"],d["lastErrTime"]])
            totalHold += abs(d["holdAmount"])
        lastStatus = "`" + json.dumps([tblStatus, tblMarket]) + "`\n" + "当前时间:"+_D() + ",持有品种个数:" + str(holdSymbol)
        if totalHold > 0 :
            lastStatus += "\n手动恢复字符串:" + json.dumps(vmStatus)
        LogStatus(lastStatus)
        Sleep(LoopInterval * 1000)
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

更多内容