海龟交易法则的精髓部分是明确给出入市位置、开仓规模、加仓位置、止损位置、离市平仓位置的交易系统,可以说是最全面的交易系统,非常适合期货交易者学习。
建仓资金(头寸规模)选择 海龟交易法则将市场价格上下波动量化为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)