Python超级精简的多品种MACD趋势策略框架, 代码超级精简, 注释超级详细啰嗦。 >_<! 需要引用 python版CTP商品期货交易类库(支持2/3 测试版) 模板, 模板的代码有JS语言版本的注释(逻辑一致)。
'''
/*backtest
start: 2016-01-30
end: 2016-12-30
period: 1440
periodBase: 60
mode: 0
*/
'''
# 以上 为设置的回测默认参数
# ------------
# 作者: Zero
# ------------
class Trader: # 声明一个 python 类
def __init__(self, q, symbol): # Trader 类的构造函数, 参数 self(代表类实例化以后的对象) , q(引用 商品期货交易类库 模板 构造的 交易处理对象), symbol (商品期货合约代码)
self.q = q # 给 构造函数 构造的对象添加属性q ,并用 参数 q 赋值。
self.symbol = symbol # 同上, 给构造的对象添加symbol 属性,并用 参数 symbol 赋值。
self.position = 0 # 添加 属性 position 赋值 0 , 该属性是用于 记录仓位数量。
self.isPending = False # 添加 属性 isPending 赋值 False , 该属性用于标记 对象状态,是否是挂起状态。
def onOpen(self, task, ret): # 类成员函数 , 执行开仓完成后的 回调函数(即 在模拟多线程处理交易的对象q 完成当前任务后 回调该 onOpen 函数处理一些开仓后的工作。)
if ret: # 交易处理对象 q ,会在处理交易任务完成后 回调onOpen ,传入2个参数 ,第一个 就是由形参task 接收,具体数据为执行的交易任务数据, 第二个参数就是 交易完成的情况
# 如果ret 为有效数据(交易未成功 ret 为 None),则处理 if 块内代码
self.position = ret['position']['Amount'] * (1 if (ret['position']['Type'] == PD_LONG or ret['position']['Type'] == PD_LONG_YD) else -1)
# 对调用该函数的对象的属性position 赋值, ret['position']['Amount'] 为交易后的持仓数量,根据 ret['position']['Type'] 持仓类型 等于 PD_LONG (持多仓)
# 还是 PD_LONG_YD(持空仓)去选择 ret['position']['Amount'] 乘 1 还是 -1 ,最后把持仓数量赋值 给 position ,(作用是通过 position 区分持多仓还是持空仓)
Log(task["desc"], "Position:", self.position, ret) # 打印多个项: q 执行的任务的数据 task 字典的 描述内容,赋值过的position , q 对象处理的交易的完成情况(当前持仓信息)
self.isPending = False # 给 isPending 赋值 False 即代表 当前的 品种 交易逻辑 处于 非挂起状态,可以接受任务。
def onCover(self, task, ret): # 平仓任务完成后要执行的回调函数, 参数同 onOpen 一致
self.isPending = False # 设置 isPending 为False 当前 品种的交易逻辑为 非挂起状态,可以接受任务。
self.position = 0 # 给记录持仓的变量 position 赋值 为0, 即没有持仓。
Log(task["desc"], ret) # 打印 交易处理对象 q 本次处理的任务 描述(desc), 完成处理的结果(ret)
def onTick(self): # 主要交易逻辑 , MACD 策略核心。
if self.isPending: # 如果 Trader类构造的当前逻辑对象的 isPending 属性 为True 则代表 目前有 交易任务 正在 交易处理对象q 队列中执行。
return # 交易逻辑处于挂起状态,不做任何处理。
ct = exchange.SetContractType(self.symbol) # 根据构造函数 传入的 symbol 赋值给 对象成员属性symbol 传入 API 函数 即:交易平台对象exchange 的 SetContractType 函数, 用来设置操作的合约类型
if not ct: # SetContractType 函数切换 交易合约代码(symbol) 成功后会返回 该合约的详细信息, 如果返回 None , 即 not ct 为真, 则立即返回,等待下一轮。
return
r = exchange.GetRecords() # 声明 变量 r (用来储存K线数据) , 调用 API 函数 GetRecords 获取 设置后的该合约的 K线数据,赋值给 r 。
if not r or len(r) < 35: # 如果 r 为 None 或者 r的长度小于35 (因为要计算 MACD 指标,必须有足够数量的K线bar,小于35 无法计算)
return # 立即返回 ,下一轮处理。
macd = TA.MACD(r) # 调用 API 指标函数 TA.MACD ,传入实参 r ,计算MACD 指标数据 ,并赋值给 变量 macd 。(TA.MACD 成功返回的数据是一个 二维数组 [dif, dea, 量柱])
# 不明白 dif 、dea 的可以百度 MACD 指标
diff = macd[0][-2] - macd[1][-2] # 计算dif和dea的差值 (注意,此处计算使用的是计算出的倒数第二bar的dif、dea,因为倒数第一bar 的K线是一直变动的,macd指标也是一直在变动,准确不变的是倒数第二bar)
if abs(diff) > 0 and self.position == 0: # 开仓,如果此刻 指标 (dif - dea)的绝对值 大于0 并且 没有持仓(即: position 等于 0)
self.isPending = True # 改变状态 设置为 挂起状态, isPending = True
self.q.pushTask(exchange, self.symbol, ("buy" if diff > 0 else "sell"), 1, self.onOpen) # 调用交易处理对象q的成员函数pushTask发出开仓交易任务,参数:exchange交易平台对象(传入即可)
# self.symbol 合约代码(构造时传入), 根据diff 大于0 还是小于0 去设置 "buy" 或者 "sell", 1 这个参数指的是下单量1手, self.onOpen 传入回调函数的引用
if abs(diff) > 0 and ((diff > 0 and self.position < 0) or (diff < 0 and self.position > 0)): # 平仓,如果此刻指标(dif - dea)的绝对值 大于0 并且 and后的 条件任意一个成立执行if 块内代码,
# diff 大于0 并且 持空仓 或者 diff小于0 并且 持多仓 ,均为 平仓条件。
self.isPending = True # 设置 为挂起状态。
self.q.pushTask(exchange, self.symbol, ("closebuy" if self.position > 0 else "closesell"), 1, self.onCover) # 发送平仓交易任务, 参数 同上发送开仓任务。
def main(): # 入口函数
q = ext.NewTaskQueue() # 调用 python版商品期货交易类库 模板 的导出函数(即接口), ext.NewTaskQueue 返回一个 构造的 交易处理对象。引用 给 变量 q
Log(_C(exchange.GetAccount)) # 启动 调用 _C 容错函数 ,传入要容错处理的 API : GetAccount 函数, 返回账户信息 由 Log 函数 输出到日志 , 显示。
tasks = [] # 声明一个 空数组 tasks 。
for symbol in ["MA701", "rb1701"]: # 遍历 数组 ["MA701", "rb1701"] 中的元素 , 每次循环 把其中的元素symbol 和 交易处理对象q 作为 参数 传递给 Trader 类的构造函数 去构造交易逻辑对象。
tasks.append(Trader(q, symbol)) # 构造好的 交易逻辑对象 压入 tasks 数组。用以 循环遍历执行处理。
while True: # 设置一个 while 死循环
if exchange.IO("status"): # 每次循环 调用 API 函数 IO ,传入参数 "status" 去检测 与 期货公司 前置服务器 的连接状态(CTP协议), 返回 True 即连接 交易服务器 和 行情服务器
for t in tasks: # 遍历 tasks 数组, 调用 构造 的 Trader 类的 对象 t的成员函数 onTick ,不断检测行情, 择时开仓 、平仓。
t.onTick() # 见 Trader 类 中的 onTick 函数
q.poll() # 调用 交易处理对象 q 的成员函数 poll 去处理 q 对象内的队列中的 交易任务。
Sleep(1000) # 程序每次 while 循环 暂停一段时间 Sleep(1000) 即: 暂停1秒 (1000毫秒),以免访问 API 过于 频繁。
欢迎提出BUG ,建议。