Nó được cấy ghép hoàn toàn từ
Là chiến lược đơn giản nhất, chiến lược trung bình động rất dễ học, bởi vì chiến lược trung bình động không có bất kỳ thuật toán tiên tiến và logic phức tạp nào.
Bài viết liên quan đến phiên bản JavaScript:https://www.fmz.com/bbs-topic/5235.
'''backtest
start: 2019-07-01 09:00:00
end: 2020-03-25 15:00:00
period: 1d
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
'''
import json
import re
import time
_bot = ext.NewPositionManager()
class Manager:
'Strategy logic control'
ACT_IDLE = 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 = ["Success", "Failed to switch contract", "Failed to get order info", "Failed to get position info", "Placing Order failed", "Failed to get order depth info", "Not in trading hours"]
def __init__(self, needRestore, symbol, keepBalance, fastPeriod, slowPeriod):
# Get symbolDetail
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("Abnormal contract information")
else :
Log("contract", symbolDetail["InstrumentName"], "1 lot", symbolDetail["VolumeMultiple"], "lot, Maximum placing order quantity", symbolDetail["MaxLimitOrderVolume"], "Margin rate: ", _N(symbolDetail["LongMarginRatio"]), _N(symbolDetail["ShortMarginRatio"]), "Delivery date", symbolDetail["StartDelivDate"])
# Initialization
self.symbol = symbol
self.keepBalance = keepBalance
self.fastPeriod = fastPeriod
self.slowPeriod = slowPeriod
self.marketPosition = None
self.holdPrice = None
self.holdAmount = None
self.holdProfit = None
self.task = {
"action" : Manager.ACT_IDLE,
"amount" : 0,
"dealAmount" : 0,
"avgPrice" : 0,
"preCost" : 0,
"preAmount" : 0,
"init" : False,
"retry" : 0,
"desc" : "idle",
"onFinish" : None
}
self.lastPrice = 0
self.symbolDetail = symbolDetail
# Position status information
self.status = {
"symbol" : symbol,
"recordsLen" : 0,
"vm" : [],
"open" : 0,
"cover" : 0,
"st" : 0,
"marketPosition" : 0,
"lastPrice" : 0,
"holdPrice" : 0,
"holdAmount" : 0,
"holdProfit" : 0,
"symbolDetail" : symbolDetail,
"lastErr" : "",
"lastErrTime" : "",
"isTrading" : False
}
# Other processing work during object construction
vm = None
if RMode == 0:
vm = _G(self.symbol)
else:
vm = json.loads(VMStatus)[self.symbol]
if vm:
Log("Ready to resume progress, current contract status is", vm)
self.reset(vm[0])
else:
if needRestore:
Log("could not find" + self.symbol + "progress recovery information")
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):
if marketPosition is not None:
self.marketPosition = marketPosition
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, "Position", pos)
else :
raise Exception("Restore" + self.symbol + "position status is wrong, no position information found")
Log("Restore", self.symbol, "average holding position price:", self.holdPrice, "Number of positions:", self.holdAmount)
self.status["vm"] = [self.marketPosition]
else :
self.marketPosition = 0
self.holdPrice = 0
self.holdAmount = 0
self.holdProfit = 0
self.holdProfit = 0
self.lastErr = ""
self.lastErrTime = ""
def Status(self):
self.status["marketPosition"] = self.marketPosition
self.status["holdPrice"] = self.holdPrice
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_IDLE:
self.task["desc"] = "idle"
self.task["onFinish"] = None
else:
if action != Manager.ACT_COVER:
self.task["desc"] = ("Adding long position" if action == Manager.ACT_LONG else "Adding short position") + "(" + str(amount) + ")"
else :
self.task["desc"] = "Closing Position"
Log("Task received", 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, "Close today's position" if positions[i]["Type"] == PD_LONG else "Close yesterday's position", "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, "Close today's position" if positions[i]["Type"] == PD_SHORT else "Close yesterday's position", "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"]
# records pre position
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 = int(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_IDLE)
return Manager.ERR_SUCCESS
def Poll(self, subroutine = False):
# Judge the trading hours
self.status["isTrading"] = ext.IsTrading(self.symbol)
if not self.status["isTrading"]:
return
# Perform order trading tasks
if self.task["action"] != Manager.ACT_IDLE:
retCode = self.processTask()
if self.task["action"] != Manager.ACT_IDLE:
self.setLastError("The task was not successfully processed:" + Manager.errMsg[retCode] + ", " + self.task["desc"] + ", Retry:" + str(self.task["retry"]))
else :
self.setLastError()
return
if subroutine:
return
suffix = "@" if WXPush else ""
# switch symbol
_C(exchange.SetContractType, self.symbol)
# Get K-line data
records = exchange.GetRecords()
if records is None:
self.setLastError("Failed to get K line")
return
self.status["recordsLen"] = len(records)
if len(records) < self.fastPeriod + 2 or len(records) < self.slowPeriod + 2:
self.setLastError("The length of the K line is less than the moving average period:" + str(self.fastPeriod) + "or" + str(self.slowPeriod))
return
opCode = 0 # 0 : IDLE , 1 : LONG , 2 : SHORT , 3 : CoverALL
lastPrice = records[-1]["Close"]
self.lastPrice = lastPrice
fastMA = TA.EMA(records, self.fastPeriod)
slowMA = TA.EMA(records, self.slowPeriod)
# Strategy logic
if self.marketPosition == 0:
if fastMA[-3] < slowMA[-3] and fastMA[-2] > slowMA[-2]:
opCode = 1
elif fastMA[-3] > slowMA[-3] and fastMA[-2] < slowMA[-2]:
opCode = 2
else:
if self.marketPosition < 0 and fastMA[-3] < slowMA[-3] and fastMA[-2] > slowMA[-2]:
opCode = 3
elif self.marketPosition > 0 and fastMA[-3] > slowMA[-3] and fastMA[-2] < slowMA[-2]:
opCode = 3
# If no condition is triggered, the opcode is 0 and return
if opCode == 0:
return
# Preforming closing position action
if opCode == 3:
def coverCallBack(ret):
self.reset()
_G(self.symbol, None)
self.setTask(Manager.ACT_COVER, 0, coverCallBack)
return
account = _bot.GetAccount()
canOpen = int((account["Balance"] - self.keepBalance) / (self.symbolDetail["LongMarginRatio"] if opCode == 1 else self.symbolDetail["ShortMarginRatio"]) / (lastPrice * 1.2) / self.symbolDetail["VolumeMultiple"])
unit = min(1, canOpen)
# Set up trading tasks
def setTaskCallBack(ret):
if not ret:
self.setLastError("Placing Order failed")
return
self.holdPrice = ret["position"]["Price"]
self.holdAmount = ret["position"]["Amount"]
self.marketPosition += 1 if opCode == 1 else -1
self.status["vm"] = [self.marketPosition]
_G(self.symbol, self.status["vm"])
self.setTask(Manager.ACT_LONG if opCode == 1 else Manager.ACT_SHORT, unit, setTaskCallBack)
def onexit():
Log("Exited strategy...")
def main():
if exchange.GetName().find("CTP") == -1:
raise Exception("Only support commodity futures CTP")
SetErrorFilter("login|ready|flow control|connection failed|initial|Timeout")
mode = exchange.IO("mode", 0)
if mode is None:
raise Exception("Failed to switch modes, please update to the latest docker!")
while not exchange.IO("status"):
Sleep(3000)
LogStatus("Waiting for connection with the trading server," + _D())
positions = _C(exchange.GetPosition)
if len(positions) > 0:
Log("Detecting the current holding position, the system will start to try to resume the progress...")
Log("Position information:", positions)
initAccount = _bot.GetAccount()
initMargin = json.loads(exchange.GetRawJSON())["CurrMargin"]
keepBalance = _N((initAccount["Balance"] + initMargin) * (KeepRatio / 100), 3)
Log("Asset information", initAccount, "Retain funds:", keepBalance)
tts = []
symbolFilter = {}
arr = Instruments.split(",")
arrFastPeriod = FastPeriodArr.split(",")
arrSlowPeriod = SlowPeriodArr.split(",")
if len(arr) != len(arrFastPeriod) or len(arr) != len(arrSlowPeriod):
raise Exception("The moving average period parameter does not match the number of added contracts, please check the parameters!")
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 + "Already exists, please check the parameters!")
symbolFilter[symbol] = True
hasPosition = False
for j in range(len(positions)):
if positions[j]["ContractType"] == symbol:
hasPosition = True
break
fastPeriod = int(arrFastPeriod[i])
slowPeriod = int(arrSlowPeriod[i])
obj = Manager(hasPosition, symbol, keepBalance, fastPeriod, slowPeriod)
tts.append(obj)
preTotalHold = -1
lastStatus = ""
while True:
if GetCommand() == "Pause/Resume":
Log("Suspending trading ...")
while GetCommand() != "Pause/Resume":
Sleep(1000)
Log("Continue trading...")
while not exchange.IO("status"):
Sleep(3000)
LogStatus("Waiting for connection with the trading server," + _D() + "\n" + lastStatus)
tblStatus = {
"type" : "table",
"title" : "Position information",
"cols" : ["Contract Name", "Direction of Position", "Average Position Price", "Number of Positions", "Position profits and Losses", "Number of Positions Added", "Current Price"],
"rows" : []
}
tblMarket = {
"type" : "table",
"title" : "Operating status",
"cols" : ["Contract name", "Contract multiplier", "Margin rate", "Trading time", "Bar length", "Exception description", "Time of occurrence"],
"rows" : []
}
totalHold = 0
vmStatus = {}
ts = time.time()
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 ("long" if d["marketPosition"] > 0 else "short"), d["holdPrice"], d["holdAmount"], d["holdProfit"], abs(d["marketPosition"]), d["lastPrice"]])
tblMarket["rows"].append([d["symbolDetail"]["InstrumentName"], d["symbolDetail"]["VolumeMultiple"], str(_N(d["symbolDetail"]["LongMarginRatio"], 4)) + "/" + str(_N(d["symbolDetail"]["ShortMarginRatio"], 4)), "is #0000ff" if d["isTrading"] else "not #ff0000", d["recordsLen"], d["lastErr"], d["lastErrTime"]])
totalHold += abs(d["holdAmount"])
now = time.time()
elapsed = now - ts
tblAssets = _bot.GetAccount(True)
nowAccount = _bot.Account()
if len(tblAssets["rows"]) > 10:
tblAssets["rows"][0] = ["InitAccount", "Initial asset", initAccount]
else:
tblAssets["rows"].insert(0, ["NowAccount", "Currently available", nowAccount])
tblAssets["rows"].insert(0, ["InitAccount", "Initial asset", initAccount])
lastStatus = "`" + json.dumps([tblStatus, tblMarket, tblAssets]) + "`\nPolling time:" + str(elapsed) + " Seconds, current time:" + _D() + ", Number of varieties held:" + str(holdSymbol)
if totalHold > 0:
lastStatus += "\nManually restore the string:" + json.dumps(vmStatus)
LogStatus(lastStatus)
if preTotalHold > 0 and totalHold == 0:
LogProfit(nowAccount.Balance - initAccount.Balance - initMargin)
preTotalHold = totalHold
Sleep(LoopInterval * 1000)
Địa chỉ chiến lược:https://www.fmz.com/strategy/208512
Chúng tôi so sánh phiên bản JavaScript và phiên bản Python của chiến lược với backtest.
Chúng tôi sử dụng một máy chủ công cộng để backtest, và chúng tôi có thể thấy rằng backtest của phiên bản Python là nhanh hơn một chút.
Có thể thấy rằng kết quả backtest là chính xác như nhau.
Chúng ta hãy thực hiện một minh chứng mở rộng và mở rộng chức năng biểu đồ cho chiến lược, như được hiển thị trong hình:
objChart
PlotRecords
Một số sửa đổi khác được thực hiện xung quanh hai điểm này. Bạn có thể so sánh sự khác biệt giữa hai phiên bản và tìm hiểu các ý tưởng về các hàm mở rộng.
Phiên bản Python của chiến lược trung bình động đa biến tương lai hàng hóa (bảng mở rộng)