VeighNa量化社区
你的开源社区量化交易平台
KeKe's Avatar
Member
离线
187 帖子
声望: 51

原版海龟策略的出入场交易信号分成2个版本:

  • 短周期版本:1)入场信号:若期货指数价格突破20日最高价/最低价,买入/卖出1个头寸单位。过滤条件是上一次突破是盈利性突破,则当前入市信号无效(其保障性突破点为在55日通道入市)。 2)离场信号:采用10日唐奇安通道突破退出法则,即多头价格跌破10日最低点离场,空头价格超过10日最高点离场。

  • 长周期版本:1)入场信号若价格突破55日最高价/最低价,买入/卖出1个头寸单位。对于长周期版本来说,所有突破都被视为有效信号,无论上一次突破是亏损性还是盈利性的。2)离场信号:采用20日唐奇安通道突破退出法则,即多头价格跌破20日最低点离场,空头价格超过10日最高点离场。

 

《海龟交易法则》的作者费思认为这个2版本的出入场信号都是有效的,海龟们可以选择其中一个版本进行交易,也可以把资金分割成2部分,一部分使用短周期信号,另一部分使用长周期信号。

 

但是这都是30年前在美国市场有效的版本,照帮到当今的国内期货市场不能够保证其有效性,故需要对其进行检验。
检验方法是把海龟策略的信号系统拆分成3个:标准版本(长周期+短周期),短周期版本和长周期版本。然后通过上一章节挑选出来的海龟组合进行验证,其效果如图所示。

 

enter image description here

 

结果显示:标准版本年化收益45.11%,百分比最大回撤-29.46%,夏普比率达1.5。长周期版本年化收益39.96%,百分比最大回撤-25.81%,夏普比率达1.44。短周期版本年化收益44.34%,百分比最大回撤-25.61%,夏普比率达1.65。因此可以推测出是长周期版本整体效果较差,拖累了短周期版本信号效果,从而拉低了整体的夏普比率。

 

海龟组合的角度来看,应该剔除长周期版本的出入场信号,仅仅保留短周期版本。

 

为了再一次验证其结论的有效性,逐个对单品种进行测试,然后统计其3个版本的夏普比率。为了表述起来更加直观,本次使用打分法,步骤如下:

  1. 根据夏普比率的测试效果对3个版本的海龟策略进行排序。
  2. 对于不同排名附加权重不同,如排名第一得1分,排名第二得0.8分,排名第三得0分。
  3. 统计3个版本海龟策略的总分
     

单品种的下面但品质长短周期的分数表,图中显示
 

  • 中金所短周期版本总分最高,为2.8分,远高于标准版本(1分)和长周期版本(0.8分),反过来说短周期版本得分最低。
  • 上期所长周期全品种组合得分最低(5分),热门品种组合也是最低(2.8分);
  • 郑商所长周期的全品种组合为5分,热门品种组合为2.8分;
  • 大商所长周期的全品种组合为4.6分,热门品种组合为1分。
     

enter image description here

 
综上所述,国内四大交易所长周期在热门品种组合和全品种组合得分均最低,故判断长周期出入场信号无效,应该剔除。新的海龟策略仅仅保留短周期信号,并且以后的验证也是基于短周期版本的海龟策略。

作者:张国平 ;来源:韦恩的派论坛
 
 

在尝试写tick级别的策略,由于交易反馈时间要求高,感觉需要对单个order的事有个全面了解,就花了些时间尝试性去分析了VNPY中 从发送交易指令(sendOrder())到交易所,和接收成交返回信息(onOrder()/onTrade())的代码。如果有错误或者遗漏,请指正。这里先将发送

一·在策略中,一般不直接调用sendOrder(), 而且用四个二次封装函数函数,这些都是在class CtaTemplate中定义的,这里面主要区别就是sendorder()函数的中ordertype制定不一样,用来区分是买开卖开等交易类型。

返回一个vtOrderIDList, 这个list里面包含vtOrderID,这个是个内部给号,可以用做追踪同一个order的状态。
 

二·接下来我们看看那sendOrder()源码,还在class CtaTemplate中定义;如果stop为True是本地停止单,这个停止单并没有发送给交易所,而是存储在内部,使用ctaEngine.sendStopOrder()函数;否则这直接发送到交易所,使用ctaEngine.sendStopOrder函数。这里会返回一个vtOrderIDList, 这个list里面包含vtOrderID,和上面说的一样。这里补充一下,对于StopOrder真是交易通常是涨停价或者跌停价发出的市价单(Market price),参数price只是触发条件;而普通sendOrder是真正按照参数price的限价单(Limit price)
 
三·这里我们首先看看ctaEngine.sendStopOrder()函数,在class CtaEngine中定义的,首先实例初始化时候定义了两个字典,用来存放stoporder,区别一个是停止单撤销后删除,一个不会删除;还定义了一个字典,策略对应的所有orderID。
然后在函数sendStopOrder中,首先记录给本地停止单一个专门编号,就是前缀加上顺序编号,其中STOPORDERPREFIX是 'CtaStopOrder.',那么第一条本地编码就是'CtaStopOrder.1',后面是这个单据信息;这里可以发现orderType其实是一个direction和offset的组合。交易方向direction有Long、short两个情况,交易对offset有open和close两个情况。组合就是上面买开,卖平等等。然后把这个stoporder放入字典,等待符合价格情况到达触发真正的发单。这里返回本地编码作为vtOrderIDList。
 

四· 下面是processStopOrder()函数,也在classCtaEngine中定义的,主要是当行情符合时候如何发送真正交易指令,因为stopOrderID不是tick交易重点,这里简单讲讲,具体请看源码。
当接收到tick时候,会查看tick.vtSymbol,是不是存在workingStopOrderDict的so.vtSymbol有一样的,如果有,再看tick.lastPrice价格是否可以满足触发阈值,如果满足,根据原来so的交易Direction,Long按照涨停价,Short按照跌停价发出委托。然后从workingStopOrderDic和strategyOrderDict移除该so,并更新so状态,并触发事件onStopOrder(so).
这里发现,so只是只是按照涨停价发单给交易所,并没有确保成绩,而且市价委托的实际交易vtOrderID也没有返回;从tick交易角度,再收到tick后再发送交易,本事也是有了延迟一tick。所以一般tick级别交易不建议使用stoporder。
 

五·前面说了这么多,终于到了正主sendOrder(),也在class CtaEngine中定义的。代码较长,下面做了写缩减。
1)通过mainEngine.getContract获得这个品种的合约的信息,包括这个合约的名称,接口名gateway(国内期货就是ctp),交易所,最小价格变动等信息;
2)创建一个class VtOrderReq的对象req,在vtObject.py中,这个py包括很多事务类的定义;然后赋值,包括contract获得信息,交易手数,和price,和priceType,这里只有限价单。
3)根据orderType赋值direction和offset,之前sendStopOrder中已经说了,就不重复。
4)然后跳到mainEngine.convertOrderReq(req),这里代码比较跳,分析下来,如果之前没有持仓,或者是直接返回[req];如果有持仓就调用PositionDetail.convertOrderReq(req),这个时候如果是平仓操作,就分析持仓量,和平今和平昨等不同操作返回拆分的出来[reqTd,reqYd],这里不展开。
5) 如果上一部没有返回[req],则委托有问题,直接返回控制。如果有[req],因为存在多个req情况,就遍历每个req,使用mainEngine.sendOrder发单,并保存返回的vtOrderID到orderStrategyDict[],strategyOrderDict[]两个字典;然后把vtOrderIDList返回。

 

六·在mainEngine.sendOrder中,这里不列举代码了,首先进行风控,如果到阈值就不发单,然后看gateway是否存在,如果存在,就调用gateway.sendOrder(orderReq)方法;下面用ctpgateway说明。class CtpGateway(VtGateway)是VtGateway是继承,把主要发单,返回上面都实现,同时对于不同的接口,比如外汇,只要用一套接口标准就可以,典型继承使用。
CtpGateway.sendOrder实际是调用class CtpTdApi(TdApi)的,这个就是一套ctp交易交口,代码很简单,最后是调用封装好C++的ctp接口reqOrderInsert()。最关键返回的vtOrderID是接口名+顺序数。

 

整个流程下来,不考虑stoporder,是ctaTemplate -> CtaEngine->mainEngine ->ctpgateway ->CtpTdApi, 传到C++封装的接口。返回的就是vtOrderID; 因为存在平昨,平今还有锁仓,反手等拆分情况,返回的可能是一组。

(继续上一章节未完成的测试)

 

3)3年回望周期测试

选择标准:回归夏普比率>0.4

a.2014-2016测试


对初步筛选出来的样本进行2014-2016年回测,选择回归夏普比率>0.4的品种,然后构成组合,如图所示。

enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here

 

根据回归夏普比率>0.4的准则,筛选出了17个品种,其历史表现和2017年预测表现如图6-31所示。投资组合在2014-2016年年化收益73.7%,百分比最大回撤-22.99%,夏普比率达2.48,资金曲线平滑且整体向上,但是2017年预测表现不佳,资金曲线不断上下震荡,夏普比率仅仅是-0.23。

enter image description here

 

 

b.2015-2017测试


2015-2017年回测是最后一轮策略,选择回归夏普比率>0.4的品种,然后构成最终的海龟组合,如图所示。

enter image description here
enter image description here
enter image description here
enter image description here
enter image description here

 
根据回归夏普比率>0.4的准则,筛选出了14个品种,其历史表现和2018年预测表现如图6-33所示。投资组合在2015-2017年年化收益62.2%,百分比最大回撤-23.21%,夏普比率达1.82;但是2017年震荡向上,夏普比率达0.59,故2018年是盈利的;全时间区间的夏普比率为1.42。
enter image description here

 
 
若把筛选标准改成回归夏普比率>0.6后,投资组合的品种数量降低到12个,其其历史表现和2018年预测表现如图6-34所示。投资组合在2015-2017年年化收益63.36%,百分比最大回撤-20.78%,夏普比率达1.81; 2018年预测表现是先发生回撤然后行情走好,夏普比率达0.59;全时间区间的夏普比率为1.5。
enter image description here
 
 
若把筛选标准改成回归夏普比率>0.8后,投资组合的品种数量降低到9个,其其历史表现和2018年预测表现如图6-35所示。投资组合在2015-2017年年化收益63.85%,百分比最大回撤-24.24%,夏普比率达1.84; 2018年预测表现是先发生回撤然后行情走好,夏普比率达0.57;全时间区间的夏普比率为1.52。
enter image description here
 
 

4)4年回望周期测试


选择标准是回归夏普比率>0.4,对初步筛选出来的样本进行2014-2017年回测,然后一步到位构成组合,如图所示。
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here

 

根据回归夏普比率>0.4的准则,筛选出了15个品种,其历史表现和2017年预测表现如图6-37所示。投资组合在2015-2017年年化收益57.2%,百分比最大回撤-22.61%,夏普比率达1.82;但是2018年资金曲线上下震荡,夏普比率仅仅是0.38;全时间区间的夏普比率为1.61。

enter image description here
 
 

根据回归夏普比率>0.6的准则,剔除了鸡蛋这个品种,其历史表现和2017年预测表现如图6-38所示。投资组合在2015-2017年年化收益57.2%,百分比最大回撤-22.61%,夏普比率达1.67;但是2018年夏普比率是0.33;全时间区间的夏普比率为1.49。
enter image description here
 
 

根据回归夏普比率>0.8的准则,剔除了一号棉花这个品种,其历史表现和2017年预测表现如图6-39所示。投资组合在2015-2017年年化收益58.68%,百分比最大回撤-38.46%,夏普比率达1.48;但是2018年夏普比率是-0.12;全时间区间的夏普比率为1.28。

enter image description here
 
 

根据回归夏普比率>1.0的准则,剔除的品种包括:沪深300股指、铜、铅,其历史表现和2018年预测表现如图6-40所示。投资组合在2015-2017年年化收益53.4%,百分比最大回撤-28.38%,夏普比率达1.37; 2018年夏普比率是0.43;全时间区间的夏普比率为1.19。

enter image description here
 
 

5)多种回望周期回测总结


在上面的测试中,尝试了通过不同的回望周期(如2年、3年、4年)和不同的筛选标准得到了10个备选的海龟组合,下面仅仅以2018年预测效果和全时间区间的夏普比率构建备选表格,如图所示。从该表格可以看出,整体表现最好的是回望周期为3年,筛选标准是回归夏普比率>0.6的组合,故以该投资组合成为最终的海龟组合,用于其他关键要素的验证,比如单位头寸限制,长短周期出入场信号,上一笔盈利过滤等等。
enter image description here

作者:viponedream ;来源:维恩的派论坛
 

# ----------------------------------------------------------------------
def convert_datetime(dt=None, oldtz=None, newtz=None, fmt=None, returnStr=False, withFlag=False):
    """输入一个日期,时间,返回一个新时区的datetime对象
    DateTime   可以是字符串,数字,日期,时间或者日期对象
    可能的值 (可以带时区,默认为UTC)
       20160530 10:59:59    字符 日期 时间
       20160530             字符 日期
       10:59:59             字符  时间
       1536249467.2627187   timestamp(可字符或数字)


    传入的日期或字符串日期, 如20160530 10:59:59
                     或者传入一个时间戳   时间戳没有时区概念,是GMT以来的秒数
    newtz            想要的新时区,有新时区则返回新时区,否则返回UTC时间
    oldtz            传入日期的原来时区 可选参数 CST  EST  GMT  UTC
    withFlag
    """
    """
    now(), datetime(), time()都是可以指定tzinfo信息的,而date是不行的

    """


    # 定义输出格式   '%Y%m%d %H:%M:%S'     '%Y%m%d %H:%M:%S.%f'
    if not fmt:
        fmt = '%Y%m%d %H:%M:%S.%f'

    # 定义常用时区
    utcZone = pytz.timezone('utc')  # 标准时间 UTC, GMT
    estZone = pytz.timezone('US/Eastern')  # 北美 EST 时区  US/Eastern
    cstZone = pytz.timezone('Asia/Shanghai')  # 中国时区 CST

    # 判断传入时间的时区  UTC, GMT, CST, EST
    if oldtz is not None:
        oldtz = oldtz.upper()

    if not oldtz or oldtz in ['UTC', 'GMT']:  # 没有传入默认为 UTC(GMT)
        oldtz = utcZone
    elif oldtz == 'CST':  # 传入的是中国时区
        oldtz = cstZone
    elif oldtz == 'EST':  # 传入的是北美时区
        oldtz = estZone
    else:
        print(u'传入的时区没法识别')
        return

    # 判断要转换出去的时区  UTC, GMT, CST, EST
    if newtz is not None:
        newtz = newtz.upper()
    if not newtz or newtz in ['UTC', 'GMT']:  # 没有传入默认为 UTC(GMT)
        newtz = utcZone
        dtEndStr = 'GMT'
    elif newtz == 'CST':  # 传入的是中国时区
        newtz = cstZone
        dtEndStr = 'CST'
    elif newtz == 'EST':  # 传入的是北美时区
        newtz = estZone
        dtEndStr = 'EST'
    else:
        print(u'传入的时区没法识别')
        return

    newdt = None
    # 先判断是否日期对象
    if isinstance(dt, str):
        # 2016-01-05 21:55:20
        # 如果只是导入的 21:55:20,则先在前面加上日期,
        dt = dt.replace('-', '')

    if dt is None or not dt:  # 如果dt为空或者没有传进来
        now = time.time()
        newdt = datetime.datetime.fromtimestamp( now, datetime.timezone.utc)
    elif isinstance(dt, datetime.datetime):  # 日期格式,
        newdt = dt
    elif isinstance(dt,datetime.date):       # 只有日期
        tt = datetime.time(23, 59, 59, tzinfo=oldtz)
        newdt = datetime.datetime.combine(dt, tt)
    elif isinstance(dt, datetime.time):       #只有时间
        newdt = datetime.utcnow()
        newdt = newdt.replace(hour=dt.hour, minute=dt.minute, second=dt.second, tzinfo=oldtz)
    elif isinstance(dt, (int, float)):  # 数字,就是 timestamp
        newdt = datetime.datetime.fromtimestamp(float(dt), datetime.timezone.utc)
    elif isinstance(dt, str) and len(dt) == 8:
        if ':' in dt:  # 21:55:20
            t = datetime.strptime(dt, '%H:%M:%S')
            newdt = datetime.utcnow()
            newdt = newdt.replace(hour=t.hour, minute=t.minute, second=t.second, tzinfo=utcZone)
        else:  # YYYYmmdd   20160815
            y = int(dt[0:4])
            m = int(dt[4:6])
            d = int(dt[6:8])
            tt = datetime.time(23, 59, 59, tzinfo=oldtz)
            dd = datetime.date(y, m, d)  # date没有时区信息
            newdt = datetime.datetime.combine(dd, tt)

    elif "." in dt and ':' in dt:  # 20180906 16:46:20.157940
        newdt = datetime.datetime.strptime(dt, '%Y%m%d %H:%M:%S.%f')
    elif ':' in dt and not "." in dt:  # 20180906 16:46:20
        newdt = datetime.datetime.strptime(dt, '%Y%m%d %H:%M:%S')
    elif dt.replace(".", '').isdigit():  # timestamp  1536249467.2627187    1536249467
        newdt = datetime.datetime.fromtimestamp(float(dt), datetime.timezone.utc)

    if not isinstance(newdt, datetime.datetime):  # 日期格式,
        print( f" MY_vtFunction 日期转化不对{dt}")
        return

    try:
        newdt = oldtz.localize(newdt)  # 设置时区
    except:
        pass

    utcDateTime = newdt.astimezone(utcZone)  # 再转为UTC标准时间
    newDateTime = newtz.normalize(utcDateTime)  # 转出为 newtz

    # 返回时再把时区信息去掉。
    newDateTime = newDateTime.replace(tzinfo=None)

    if returnStr:  # 返回字符串
        newDateTime = newDateTime.strftime(fmt)
        if withFlag:
            newDateTime = ' '.join([newDateTime, dtEndStr])  # 加上时区标识

    return newDateTime

重新构建投资组合

 

1)初步筛选

 
初步筛选从仅仅基于历史行情外,还加多了品种波动率和自相关性的要求,故总的来说其初步筛选条件为三点:

  • 历史行情:2014年1月1日前上市
  • 调整后波动率比值>1
  • ADF值>10%

 

根据初步筛选标准,剔除了不符合要求品种后,测试样本从调整前的35个缩小至27个,根据其调整后波动率比值的大小按从大到小排序,如图所示。
结合高成交量特征,一般来说,成交量高的品种,其波动率高,自相关性强,故具有正相关性。
 
enter image description here
 
根据交易所分类,这27个品种划分成4部分:

  • 中金所:IF
  • 上交所:ZN、RB、CU、WR、PB、BU、AL
  • 郑商所:TA、CF、RS、SR、RI、WH、FG
  • 大商所:J、BB、B、JM、JD、A、Y、C、FB、M、L、V

 
初步筛选之后,我们会通过不同的回望周期(如2年、3年、4年)以及基于回归夏普比率不同的筛选标准来得到若干个海龟组合备选方案,最后通过相互比较得到最终的组合。
(以下测试基于米筐RQData的小时级别期货指数数据,有兴趣的朋友可以自行验证或者使用别的数据源测试一下!)
 

2)2年回望周期测试

选择标准:回归夏普比率>0.4
 

a.2014-2015年测试


对初步筛选出来的样本进行2014-2015年回测,选择回归夏普比率>0.4的品种,然后构成组合,如图所示。

enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here

 

根据回归夏普比率>0.4的准则,筛选出了14个品种,其历史表现和2016年预测表现如图6-23所示。投资组合在2014-2015年年化收益96.46%,百分比最大回撤-32.75%,夏普比率达2.04,资金曲线平滑且整体向上,但是2016年预测表现不佳,需要剔除更多噪声因子。
下面分析一下挑选出来的品种成分,按交易所分类如下:

  • 中金所:沪深300股指
  • 上期所:铝、铜、螺纹钢、铅、线材、锌
  • 郑商所:普麦、PTA
  • 大商所:玉米、铁矿石、焦煤、黄大豆2号、豆粕、聚乙烯

enter image description here

 
 

b.2015-2016年测试


同样对剩下的样本进行2015-2016年回测,选择回归夏普比率>0.4的品种,然后构成组合。

enter image description here
enter image description here
enter image description here
enter image description here
enter image description here

 
经过第二轮筛选后,剩下9个品种,同样按照交易所分类,如下:

  • 上期所:铝、铜、螺纹钢、锌
  • 郑商所:普麦
  • 大商所:玉米、铁矿石、焦炭、豆粕

 
在新的投资组合中,年化收益达92.04%,百分比最大回撤是-16.8%,夏普比率达2.4,整体资金曲线比较平滑。在2017年预测表现理想,年化收益46.49%,百分比最大回撤-30.45%,夏普比率达1.09,如图所示。

enter image description here

 
 

c.2016-2017测试


最后一轮策略,将挑选出最终的品种组成海龟组合,单品种品种如图所示。
enter image description here
enter image description here
enter image description here

 
第三轮筛选后,基于回归夏普比率>0.4得到由铝、铜、锌、普麦、铁矿石、焦炭、螺纹钢组成的海龟组合,2016-2017年标准夏普达1.56,2018年预测的夏普比率是-0.12,全时间区间的夏普比率表现是1.17。投资组合效果差强人意。

enter image description here
 

基于上面2年回望周期所做展示的回测图,可以更加便捷的更改筛选标准而不用从新进行测试就得到结果,故下面把筛选标准改成回归夏普比率>0.6,其测试情况如图所示。
把筛选标准提高0.2后,得到的样本数量降低到5个,分别是铝、铜、锌、铁矿石、焦炭,2016-2017年标准夏普达1.29,2018年预测的夏普比率是0.7,全时间区间的夏普比率表现是1.22。
enter image description here
 

若把筛选标准提升至回归夏普比率>0.8,则得到4个样本品种:铜、锌、铁矿石、焦炭,2016-2017年标准夏普达1.87,2018年预测的夏普比率是-0.03,全时间区间的夏普比率表现是1.38,如图所示。

enter image description here
 

作者:爱茶语 ;来源:维恩的派论坛

 

  • 原文测试时间区间是20120111--20171117,样本内夏普比率达1.35。
  • 今进行样本外测试,时间区间20130111--20190102,夏普比率为0.78。

结果显示尽管参数不多,但是模型还是过拟合了。但是在策略内实现Tick数据聚合成X分钟K线还是值得学习一下
 

回测设置

# 设置回测使用的数据
engine.setBacktestingMode(engine.BAR_MODE)    # 设置引擎的回测模式为K线
engine.setDatabase(MINUTE_DB_NAME, 'RB99')  # RQDATA一分钟期货指数数据
engine.setStartDate('20130101')               # 设置回测用的数据起始日期

# 配置回测引擎参数
engine.setSlippage(1)      # 设置滑点为1跳
engine.setRate(1/1000)    # 设置手续费
engine.setSize(10)         # 设置合约大小 
engine.setPriceTick(1)     # 设置最小价格变动   
engine.setCapital(200000)   # 设置回测本金

 
 

回测效果

 
enter image description here

 
 
策略代码如下
 

# encoding: UTF-8


import talib
import numpy as np

from vnpy.trader.vtObject import VtBarData
from vnpy.trader.vtConstant import EMPTY_INT,EMPTY_STRING
from vnpy.trader.app.ctaStrategy.ctaTemplate import CtaTemplate

########################################################################
class RBMAStrategy(CtaTemplate):
    """结合MA的一个30分钟线交易策略"""
    className = 'RBMAStrategy'
    author = 'xldistance'

    #策略参数

    initDays = 33    # 初始化数据所用的天数默认35
    open_pos = 10   #每次交易的手数
    OCM = 30      #操作分钟周期(1,60)默认30
    # 策略变量
    bar = None                  # K线对象
    barMinute = EMPTY_STRING    # K线当前的分钟
    minutebar = None        # minuteK线对象
    ma_windows1 = 20    #默认20
    ma_windows2 = 200    #默认200
    # 参数列表,保存了参数的名称
    paramList = ['name',
                 'className',
                 'author',
                 'vtSymbol',
                 'open_pos']

    # 变量列表,保存了变量的名称
    varList = ['inited',
               'trading',
               'pos',
               'OCM',
               'ma20_value',
               'ma200_value']
    #----------------------------------------------------------------------
    def __init__(self, ctaEngine, setting):
        """Constructor"""
        super(RBMAStrategy, self).__init__(ctaEngine, setting)
        """
        如果是多合约实例的话,变量需要放在__init__里面
        """
        #self.orderList = []
        self.barList = []
        self.bufferSize = 201                    # 需要缓存的数据的大小
        self.bufferCount = 0                     # 目前已经缓存了的数据的计数
        self.highArray = np.zeros(self.bufferSize)    # K线最高价的数组
        self.lowArray = np.zeros(self.bufferSize)     # K线最低价的数组
        self.closeArray = np.zeros(self.bufferSize)   # K线收盘价的数组
        self.openArray = np.zeros(self.bufferSize)   # K线开盘价的数组
        self.LongEnterable = False
        self.ShortEnterable = False
        self.ma20_value = 0
        self.ma200_value = 0
    def onInit(self):
        self.writeCtaLog('%s策略初始化' %self.name)

        # 载入历史数据,并采用回放计算的方式初始化策略数值
        initData = self.loadBar(self.initDays)
        for bar in initData:
            self.onBar(bar)

        self.putEvent()

    #----------------------------------------------------------------------
    def onStart(self):
        """启动策略(必须由用户继承实现)"""
        self.writeCtaLog('%s策略启动' %self.name)
        self.putEvent()

    #----------------------------------------------------------------------
    def onStop(self):
        """停止策略(必须由用户继承实现)"""
        self.writeCtaLog('%s策略停止' %self.name)
        self.putEvent()

    #----------------------------------------------------------------------
    def onTick(self, tick):
        """收到行情TICK推送(必须由用户继承实现)"""


        tickMinute = tick.datetime.minute
        if tickMinute != self.barMinute:
            if self.bar:
                self.onBar(self.bar)

            bar = VtBarData()
            bar.vtSymbol = tick.vtSymbol
            bar.symbol = tick.symbol
            bar.exchange = tick.exchange

            bar.open = tick.lastPrice
            bar.high = tick.lastPrice
            bar.low = tick.lastPrice
            bar.close = tick.lastPrice

            bar.date = tick.date
            bar.time = tick.time
            bar.datetime = tick.datetime    # K线的时间设为第一个Tick的时间

            self.bar = bar                  # 这种写法为了减少一层访问,加快速度
            self.barMinute = tickMinute     # 更新当前的分钟
        else:                               # 否则继续累加新的K线
            bar = self.bar                  # 写法同样为了加快速度

            bar.high = max(bar.high, tick.lastPrice)
            bar.low = min(bar.low, tick.lastPrice)
            bar.close = tick.lastPrice

   #----------------------------------------------------------------------
    def onBar(self, bar):
        """收到Bar推送(必须由用户继承实现)"""

        if self.LongEnterable:
            if self.pos == 0:# and bar.close > self.dayOpen
                self.buy(bar.close,self.open_pos,True)
            elif self.pos < 0 :
                self.cover(bar.close,abs(self.pos),True)
        if self.ShortEnterable:
            if self.pos ==0:#and bar.close < self.dayOpen
                self.short(bar.close,self.open_pos,True)
            elif self.pos > 0:
                self.sell(bar.close,abs(self.pos),True)

        if bar.datetime.minute  % self.OCM == 0:
            # 如果已经有聚合minuteK线
            if self.minutebar:
                # 将最新分钟的数据更新到目前minute线中
                minutebar = self.minutebar
                minutebar.high = max(minutebar.high, bar.high)
                minutebar.low = min(minutebar.low, bar.low)
                minutebar.close = bar.close

                # 推送minute线数据
                self.onminutebar(minutebar)

                # 清空minute线数据缓存
                self.minutebar = None
        else:
            # 如果没有缓存则新建
            if not self.minutebar:
                minutebar = VtBarData()

                minutebar.vtSymbol = bar.vtSymbol
                minutebar.symbol = bar.symbol
                minutebar.exchange = bar.exchange

                minutebar.open = bar.open
                minutebar.high = bar.high
                minutebar.low = bar.low
                minutebar.close = bar.close

                minutebar.date = bar.date
                minutebar.time = bar.time
                minutebar.datetime = bar.datetime

                self.minutebar = minutebar
            else:
                minutebar = self.minutebar
                minutebar.high = max(minutebar.high, bar.high)
                minutebar.low = min(minutebar.low, bar.low)
                minutebar.close = bar.close
        # 发出状态更新事件
        self.putEvent()

    #----------------------------------------------------------------------
    def onminutebar(self,bar):
        """收到Bar推送(必须由用户继承实现)"""
        # 撤销之前发出的尚未成交的委托(包括限价单和停止单)
        #for orderID in self.orderList:
            #self.cancelOrder(orderID)
        #self.orderList = []

        # 保存K线数据
        self.closeArray[0:self.bufferSize-1] = self.closeArray[1:self.bufferSize]
        self.highArray[0:self.bufferSize-1] = self.highArray[1:self.bufferSize]
        self.lowArray[0:self.bufferSize-1] = self.lowArray[1:self.bufferSize]
        self.openArray[0:self.bufferSize-1] = self.openArray[1:self.bufferSize]
        self.closeArray[-1] = bar.close
        self.highArray[-1] = bar.high
        self.lowArray[-1] = bar.low
        self.openArray[-1] = bar.open
        self.bufferCount += 1
        if self.bufferCount < self.bufferSize:
            return
        # 计算指标数值
        ma_20 = talib.EMA(self.closeArray,timeperiod = self.ma_windows1)
        ma_200 = talib.EMA(self.closeArray,timeperiod = self.ma_windows2)
        self.ma20_value = ma_20[-1]
        self.ma200_value = ma_200[-1]
        self.LongEnterable = ma_20[-1] > ma_200[-1] and ma_20[-2] < ma_200[-2]
        self.ShortEnterable = ma_20[-1] < ma_200[-1] and ma_20[-2] > ma_200[-2]

        # 发出状态更新事件
        self.putEvent()

    #----------------------------------------------------------------------
    def onOrder(self, order):
        """收到委托变化推送(必须由用户继承实现)"""
        pass

    #----------------------------------------------------------------------
    def onTrade(self, trade):
        # 发出状态更新事件
        self.putEvent()
    def onStopOrder(self, so):
        """停止单推送"""
        pass

本章主要讲述构建海龟组合的痛点和解决方案

构建海龟组合痛点


在追求策略稳健性前提下,通过传统的滚动测试的方法不能筛选出盈利组合,即这种适用于日内CTA策略的检验方法针对海龟组合是无效的。
构建海龟组合困难表现为以下几点:

 

a. 历史数据过于少

从2014年到2018年这5年间,其日线数据仅仅是1,200根。其数据过少导致调整回望周期的灵活性降低,即最多回望周期只能设置为2年、3年、4年;同时数据少很难检验出策略的稳健性和有效性。(原版海龟策略历史回测周期是10年)

反观传统日内CTA策略,该策略是基于分钟级别的,1年的分钟K线就接近6,000根。用传统的筛选品种的方法,2年回望周期K线达到12,000根,在数据足够大的情况下更易于体现出策略的稳健性。

 

b. 回撤过大

海龟策略属于高风险高收益的中低频策略,夸张的比喻就是“3年不开张,开张吃3年”,在设定回测区间可能表现出持续的亏损,但也有可能在之后出现一波大行情,短期的盈利能够快速覆盖长期的亏损。但是滚动回测筛选品种时因为夏普过低而剔除该品种,导致错过隐含策略收益。

 

c. 筛选指标不稳健

本次滚动回测筛选标准是夏普比率,但是夏普比率对于策略回测时间区间起始点和终点非常敏感,对于趋势的把握不足。举例说明:在2年时间区间夏普比率是1.3,若整体回测区间向后移动3个月,得到的夏普比率可能是0.9。

故夏普比率不适应于海龟策略这种波动性大的类型,必须找到一种指标可以预测其趋势,比如“新式夏普比率”,在2年间比值是1.3,整体向前或者向后滚动3个月,其比值也接近1.3。

 

d. 样品数量噪音过大

尽管通过历史行情,其2014年前上市品种,已经把检测样本从51个降低到35个。但是对巨大的样品数量检验也造成很大的操作负担。例如一些低流动性或者持续走震荡行数的品种,没有必要来进行海龟策略的测试。

 
 

海龟组合筛选的解决方案


1. 重新进行初步筛选

从新对“流动性强”进行定义,在统计意义上,可以指行情波动巨大和具有大趋势,故通过2个指标来专门对35个品种重新进行筛选,这2个指标分别是:
 
a. 调整后波动率比值:

计算公式是收盘价的标准差/(合约最小价格变动 *100),调整后波动率比值越大,代表其品种的相对波动幅度越大,趋势跟踪策略的盈利能力越强。

举个例子说明,假设趋势跟踪策略正确预测行情的概率是x%,其固定交易成本为c,那么收益率r = (x% * 波动幅度) - 固定成本。所以当波动幅度足够大时,交易才有利可图,否则交易盈利覆盖不了交易成本,导致单子做得越多,亏的也越多。

 

b. ADF值

这是推论统计学概念,通过ADF检验来得出其ADF值,然后与10%进行比较,若ADF值要大于10%,证明完全不能拒绝原假设,即原假设成立。
原假设为存在单位根,有单位根代表着品种自相关性强,具有趋势行情。

 

总结一下,增加了两个初步筛选的标准,分别是

  • 调整后波动率比值>1
  • ADF值>10%

 
代码实现步骤

在jupter notebook上运行下面脚本代码,逐个测试样本

import numpy as np
import pandas as pd
import statsmodels.tsa.stattools as ts

#################################################
def volPriceTick (SYM, PT):
    c=pymongo.MongoClient()
    symbol = SYM          #合约代号
    col = c['VnTrader_Daily_Db'][symbol]
    cx = col.find()
    l = list(cx)
    d = {}
    for key in l[0].keys():
        d[key] = []
    for  data in l:
        for k, v in data.items():
            d[k].append(v)
    df = pd.DataFrame.from_dict(d)
    PriceTick = PT        #最小价格变动           
    close = df['close']
    var = np.std(close)               #收盘价的标准差

    ratio = var/(PriceTick*100)       # 定义调整后波动率比值
    adfresult = ts.adfuller(close)    # 得到ADF值 
    return ratio,adfresult

 
调用定义好的函数进行测试,如图所示,品种是沪深300股指期货(IF99),其最小价格变动是0.02。

enter image description here

得到的调整后波动率比值为342.19,比值远大于1说明品种价格变化挺活跃的;然后ADF值是0.36,同样大于10%说明其自相关性强,适用于趋势跟踪类型策略。
 
 

2 回测时使用新的指标

在上面已经提到,夏普比率不稳健的特点,导致其很难去识别一些行情趋势,而且对于回撤承受度不高,难于应用于像海龟策略这种高风险高收益的策略。所以产生了新筛选指标的需求。
 
新的指标应该具有3方面特点:

  • 稳健性强
  • 能够判断出整体趋势
  • 对回撤容忍度高

 

故开发“回归夏普比率”用于品种筛选,其步骤如下:
 

  1. 用最小二乘法对累计资金曲线求得线性回归方程,得到斜率slope 和截距intercept。斜率为平均每天的盈亏资金,截距为回归起始资金(注意,仅仅是数学概念,可以大于或者少于1千万,资金曲线表现得特别好的时候截距可以为负数)
  2. 通过斜率,截距与时间求得期末的回归资金RegendBalance
  3. 通过期末回归资金和截距的比值得到回归年化收益RegtotalReturn
  4. 把单利转换为复利,得到回归年化收益
  5. 直接除以年化系数annualDays(240天)转化为回归日收益
  6. 在计算夏普比率公式中,把标准的日收益换成回归日收益,得到回归夏普比率(注意,由于回归线是笔直的,无标准差,故以原始标准差计算)

 
在海龟策略回测引擎的calculateResult中加入上面逻辑即可,其代码如下:
 

        #计算回归总收益 
        daysList = range(1,totalDays+1)

        #slope , intercept = np.polyfit (changduList , balanceList , 1)  #numpy方法
        slope, intercept, r_value, p_value, slope_std_error = stats.linregress(daysList , balanceList)  #scipy方法

        RegendBalance = intercept + totalDays * slope
        RegtotalReturn = (RegendBalance / intercept - 1) * 100

        计算回归年化收益
        if RegtotalReturn > 0:
            RegannualizedReturn = (((1+RegtotalReturn/100) **(annualDays/totalDays) )-1)*100
        else:
            RegannualizedReturn =  -(((1-RegtotalReturn/100) **(annualDays/totalDays) )-1)*100

        #计算回归日收益
        RegdailyReturn = RegannualizedReturn/annualDays

        #计算回归夏普比率
        if returnStd:
            sharpeRatio = dailyReturn / returnStd * np.sqrt(annualDays)
            RegsharpeRatio = RegdailyReturn / returnStd * np.sqrt(annualDays)
        else:
            sharpeRatio = 0
            RegsharpeRatio = 0

 

新指标的缺点:

  • 高估资金低估表现非常好的品种:资金曲线非常好,其回归线的截距(即回归起始资金)可能远远少于1千万,其斜率变大,故标准夏普率可能是1.3,回归夏普比率可以达到3.3。
  • 低估资金曲线表现非常差的品种:资金曲线在某个时间段剧烈向下移动,其回归线的截取可能远远大于1千万,其斜率变小,故标准夏普比率可能是-3,其回归夏普比率可以达-0.6。

 
综合回归夏普比率的优势和缺点,我们得知其对回撤的容忍度高,可以摒弃噪声从而捕捉到趋势,故适用于作为一个筛选标准;另一方面有时候会出现高估或者低估行情表现,故不能作为判断业绩表现的指标(如标准的夏普比率)。

 
 

3.多种回望周期测试

把回望周期分别设置为2年,3年和4年。

理论上较短回测周期使用较为宽松的筛选标准(如回归夏普比率大于0.4),较长回测周期使用较为苛刻的筛选标准(如回归夏普比率大于0.8)。
当然为了测试的全面新,通过3个不同的回望周期(如:2年、3年、4年),通过不同的回归夏普比率(如:0.4、0.6、0.8)进行筛选,力求新的海龟组合能够较好地预测2018年行情。(即表现出较高的稳健性)

作者:赵信的派 ;来源:维恩的派论坛

 

ctaHistoryData可以导入历史数据,但不支持wind。这里增加wind的分钟数据导入函数,并在主程序中调用即可。
 

#----------------------------------------------------------------------    
def loadWind(code,dbName, symbol):
    """将wind的历史数据插入到Mongo数据库中"""
    from WindPy import *
    import pandas as pd
    from pandas import DataFrame 

    w.start()

    start = time()

    print u'开始读取wind文件%s中的数据插入到%s的%s中' %(code, dbName, symbol)

    # 锁定集合,并创建索引
    host, port, logging = loadMongoSetting()

    client = pymongo.MongoClient(host, port)    
    collection = client[dbName][symbol]
    collection.ensure_index([('datetime', pymongo.ASCENDING)], unique=True)   

    # 读取数据和插入到数据库
    wsd_data = w.wsi(code, "open,high,low,close,volume,amt,oi", "2017-02-19 09:00:00", "2017-05-21 00:06:20", "")
    print(wsd_data)

    df1=DataFrame(wsd_data.Data,index=wsd_data.Fields,columns=wsd_data.Times)
    print '1'
    df1=df1.T
    df_wind=df1.dropna(subset=['open'])
    index=0

    print df_wind

    for d in df_wind.index:
        bar = CtaBarData()
        bar.vtSymbol = symbol
        bar.symbol = symbol
        bar.open = float(df_wind['open'].iloc[index])
        bar.high = float(df_wind['high'].iloc[index])
        bar.low = float(df_wind['low'].iloc[index])
        bar.close = float(df_wind['close'].iloc[index])
        print(type(d))
        bar.datetime=datetime.strftime(d.to_pydatetime(),'%Y-%m-%d %H:%M:%S')
        bar.date = datetime.strftime(d.to_pydatetime(),'%Y%m%d')
        bar.time = datetime.strftime(d.to_pydatetime(),'%H:%M:%S')



        bar.volume = df_wind['volume'].iloc[index]

        flt = {'datetime': bar.datetime}
        collection.update_one(flt, {'$set':bar.__dict__}, upsert=True)  
        print bar.datetime
#主函数
if __name__ == '__main__':
    ## 简单的测试脚本可以写在这里
    #from time import sleep
    #e = HistoryDataEngine()
    #sleep(1)
    #e.downloadEquityDailyBar('000001')
    #e.downloadEquityDailyBarts('000001')

    # 这里将项目中包含的股指日内分钟线csv导入MongoDB,作者电脑耗时大约3分钟
    #loadMcCsv('IF0000_1min.csv', MINUTE_DB_NAME, 'IF0000')
    start = time()
    #导入wind数据
    loadWind("MA709.CZC",MINUTE_DB_NAME, 'MA709')
    #测时
    print u'插入完毕,耗时:%s' % (time()-start)
    #导入通达信历史分钟数据
    #loadTdxCsv('CL8.csv', MINUTE_DB_NAME, 'c0000)

传统筛选品种的方法


传统意义上,为保证策略的稳健性,应该进行样本内外检验,其具体步骤如下:

  1. 设定好历史回测周期,如2年的回望周期
  2. 设置固定的筛选标准,如夏普比率大于0.6
  3. 基于筛选标准,对每个品种进行策略回测,然后选择合格的品种组成投资组合
  4. 通过样本内筛选出来的组合,去预测其未来表现,如预测1年的策略表现
  5. 回望周期向后滚动,基于新组合再次筛选品种,直到预测最新年份未知

 
 

1 初步筛选

 
初步的筛选仅仅基于历史行情。

因为本次测试规定历史回测时间从2014年1月1日开始,故首先剔除在该时间点后才上市的品种,所以进行历史回测的品种从51个降低至35个。

所以一些例如苹果之类非常热门的新品种,不在本次测试范围之内。
 
 

2. 滚动回测

 
滚动回测标准如下

  • 回望周期:2年
  • 夏普比率标准:>0.6
  • 回望周期步进:1年

 

1)2014-2015 测试

对35个品种进行海龟策略的历史回测,如图所示。(下图小字“夏普统计出错”,其意思是总收益为负数,有时候转换成年化收益变成正数,导致负的夏普比率变成正数。)

enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here

根据夏普比率>0.6的准则,筛选出了17个品种,其历史表现和2016年预测表现如图所示。
投资组合在2014-2015年年化收益74.06%,百分比最大回撤-45.09%,夏普比率,1.41,整体上还可以,但是去2016年预测表现非常糟糕。这说明投资组合里噪声过多,稳健性弱。
下面分析一下挑选出来的品种成分,按交易所分类如下:

  • 中金所:沪深300股指
  • 上期所:铝、铜、螺纹钢、白银、线材、锌
  • 郑商所:普麦、一号棉花、菜籽粕、PTA
  • 大商所:玉米、鸡蛋、铁矿石、焦煤、黄大豆2号、豆粕

可以发现占绝大部分的是成交量巨大的品种,推测可能是流动性差的品种,如黄大豆2号等,导致其投资组合在2016年亏损,故非常有必要进行滚动回测来剔除表现不好的品种。
 
enter image description here
 
 

2)2015-2016 测试

 

第一次从35个样本中筛选出17个,本次同样将继续剔除噪声因子,试图提高海龟组合的预测效果,测试如图所示。
 
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here

 

经过第二轮筛选后,剩下11个品种,同样按照交易所分类,如下:

  • 上期所:铝、铜、螺纹钢、锌
  • 郑商所:普麦、一号棉花
  • 大商所:玉米、鸡蛋、铁矿石、焦炭、豆粕

在新的投资组合中,年化收益达94.05%,百分比最大回撤是-19.94%,夏普比率达2.36,整体资金曲线比较平滑,但是其组合的稳健性仍未提高,在2017年仍然亏损,百分比最大回撤达43.79%,还需要在剔除无效的样本。
 
enter image description here

 
 

3)2016-2017 测试

 
第三轮测试也是最后一轮测试,将决定最终海龟组合内品种的构成,其单品种测试效果如图所示。
enter image description here

enter image description here
enter image description here
 
第三轮筛选后,得到由铝、铜、锌、普麦、铁矿石、焦炭、豆粕组成的海龟组合,都符合了流动性强的特征,该组合的历史回测和2018年预测效果如图所示。

结果同样是不理想,在样本内表现出色,样本外却亏损。
 
enter image description here
 

作者:量化学习菜鸟 ;来源:维恩的派论坛
 
 

def onBar(self, bar):
        """收到Bar推送(必须由用户继承实现)"""        
        if self.zhouqi == 'm':
            dt = bar.datetime.minute
            kaiguan = dt % self.zhouqi_val == 0
        elif self.zhouqi == 'h':
            dt = bar.datetime.hour
            kaiguan = dt % self.zhouqi_val == 0 and bar.datetime.minute == 0 
        if kaiguan:
            if self.fiveBar:
                fiveBar = self.fiveBar
                fiveBar.high = max(fiveBar.high, bar.high)
                fiveBar.low = min(fiveBar.low, bar.low)
                fiveBar.close = bar.close

                # 推送
                self.onFiveBar(fiveBar)
                # 清空缓存
                self.fiveBar = None
            else:
                fiveBar = VtBarData()

                fiveBar.vtSymbol = bar.vtSymbol
                fiveBar.symbol = bar.symbol
                fiveBar.exchange = bar.exchange

                fiveBar.open = bar.open
                fiveBar.high = bar.high
                fiveBar.low = bar.low
                fiveBar.close = bar.close

                fiveBar.date = bar.date
                fiveBar.time = bar.time
                fiveBar.datetime = bar.datetime 

                #发出去
                self.onFiveBar(fiveBar)
                #清空
                self.fiveBar = None                                
        else:
            # 如果没有缓存则新建
            #print 'dt zhengchu//0'
            if not self.fiveBar:
                fiveBar = VtBarData()

                fiveBar.vtSymbol = bar.vtSymbol
                fiveBar.symbol = bar.symbol
                fiveBar.exchange = bar.exchange

                fiveBar.open = bar.open
                fiveBar.high = bar.high
                fiveBar.low = bar.low
                fiveBar.close = bar.close

                fiveBar.date = bar.date
                fiveBar.time = bar.time
                fiveBar.datetime = bar.datetime 

                self.fiveBar = fiveBar
            else:
                fiveBar = self.fiveBar
                fiveBar.high = max(fiveBar.high, bar.high)
                fiveBar.low = min(fiveBar.low, bar.low)
                fiveBar.close = bar.close

作者:moonnejs ;来源:维恩的派论坛
 
为了研究高频策略,我优化了下vnpy的回测引擎,有错误的地方希望大家指点。
 
大概优化了下面这些内容:

  1. 增加选项,可以选择是否延迟1 TICK撮合
  2. 挂价发单时,维护订单在列表中排队值。根据这个TICK内成交均价和上1TICK的盘口价,计算在1档盘口两边成交量,更新排队值
  3. 每笔订单成交量不能大于盘口量
  4. 跨交易日订单自动丢弃
  5. 双合约回测,同时成交的两个合约按单笔结算
  6. 保存每笔成交细节到文件
  7. 参数扫描结果保存到文件
  8. 每个交易日的资金曲线,保存到数据库
  9. 增加数据线程,可以在MongoDB IO等待时撮合计算
  10. 和实盘完全一致的成交和委托回报。回测通过的挂撤单逻辑,如果没有时序错误,基本可以直接实盘

 

ctaTemple.py文件


# encoding: UTF-8

'''
本文件包含了CTA引擎中的策略开发用模板,开发策略时需要继承CtaTemplate类。
'''
import datetime
import copy
from ctaBase import *
from vtConstant import *
from vtGateway import *



########################################################################
class CtaTemplate1(object):
    """CTA策略模板"""

    # 策略类的名称和作者
    className = 'CtaTemplate'
    author = EMPTY_UNICODE

    # MongoDB数据库的名称,K线数据库默认为1分钟
    tickDbName = TICK_DB_NAME
    barDbName = MINUTE_DB_NAME

    productClass = EMPTY_STRING    # 产品类型(只有IB接口需要)
    currency = EMPTY_STRING        # 货币(只有IB接口需

    # 策略的基本参数
    name = EMPTY_UNICODE           # 策略实例名称
    vtSymbol = EMPTY_STRING        # 交易的合约vt系统代码    
    vtSymbol1 = EMPTY_STRING        # 交易的合约2vt系统代码    

    # 策略的基本变量,由引擎管理
    inited = False                 # 是否进行了初始化
    trading = False                # 是否启动交易,由引擎管理
    backtesting = False            # 回测模式

    pos = 0            # 总投机方向
    pos1 = 0               # 总投机方向

    tpos0L = 0             # 今持多仓
    tpos0S = 0             # 今持空仓
    ypos0L = 0             # 昨持多仓
    ypos0S = 0             # 昨持空仓

    tpos1L = 0             # 今持多仓
    tpos1S = 0             # 今持空仓
    ypos1L = 0             # 昨持多仓
    ypos1S = 0             # 昨持空仓

    # 参数列表,保存了参数的名称
    paramList = ['name',
                 'className',
                 'author',
                 'vtSymbol']

    # 变量列表,保存了变量的名称
    varList = ['inited',
               'trading',
               'pos']

    #----------------------------------------------------------------------
    def __init__(self, ctaEngine, setting):
        """Constructor"""
        self.ctaEngine = ctaEngine

        self.productClass = EMPTY_STRING    # 产品类型(只有IB接口需要)
        self.currency = EMPTY_STRING        # 货币(只有IB接口需

        # 策略的基本变量,由引擎管理
        self.inited = False                 # 是否进行了初始化
        self.trading = False                # 是否启动交易,由引擎管理
        self.backtesting = False            # 回测模式

        self.pos = 0               # 总投机方向
        self.pos1 = 0              # 总投机方向

        self.tpos0L = 0            # 今持多仓
        self.tpos0S = 0            # 今持空仓
        self.ypos0L = 0            # 昨持多仓
        self.ypos0S = 0            # 昨持空仓

        self.tpos1L = 0            # 今持多仓
        self.tpos1S = 0            # 今持空仓
        self.ypos1L = 0            # 昨持多仓
        self.ypos1S = 0            # 昨持空仓

        # 设置策略的参数
        if setting:
            d = self.__dict__
            for key in self.paramList:
                if key in setting:
                    d[key] = setting[key]

    #----------------------------------------------------------------------
    def onInit(self):
        """初始化策略(必须由用户继承实现)"""
        raise NotImplementedError

    #----------------------------------------------------------------------
    def onUpdate(self,setting):
        """刷新策略"""
        if setting:
            d = self.__dict__
            for key in self.paramList:
                if key in setting:
                    d[key] = setting[key]

    #----------------------------------------------------------------------
    def onStart(self):
        """启动策略(必须由用户继承实现)"""
        raise NotImplementedError

    #----------------------------------------------------------------------
    def onStop(self):
        """停止策略(必须由用户继承实现)"""
        raise NotImplementedError

    #----------------------------------------------------------------------
    def confSettle(self, vtSymbol):
        """确认结算信息"""
    self.ctaEngine.confSettle(vtSymbol)

    #----------------------------------------------------------------------
    def onTick(self, tick):
        """收到行情TICK推送(必须由用户继承实现)"""
    if not self.backtesting:
        hour = datetime.datetime.now().hour
        if hour >= 16 and hour <= 19:
            return
        if hour >= 2 and hour <= 7:
            return
        if hour >= 12 and hour <= 12:
            return
        if tick.datetime.hour == 20 and tick.datetime.minute == 59: 
            self.output(u'开始确认结算信息')
            self.confSettle(self.vtSymbol)
    if tick.datetime.hour == 20 and tick.datetime.minute == 59:
        self.ypos0L += self.tpos0L
        self.tpos0L = 0
        self.ypos0S += self.tpos0S
        self.tpos0S = 0
        self.ypos1L += self.tpos1L
        self.tpos1L = 0
        self.ypos1S += self.tpos1S
        self.tpos1S = 0


    #----------------------------------------------------------------------
    def onOrder(self, order):
        """收到委托变化推送(必须由用户继承实现)"""
    pass

    #----------------------------------------------------------------------
    def onTrade(self, trade):
        """收到成交推送(必须由用户继承实现)"""
        # 对于无需做细粒度委托控制的策略,可以忽略onOrder
        # CTA委托类型映射
        if trade != None and trade.direction == u'多':
        if trade.vtSymbol == self.vtSymbol:
            self.pos += trade.volume
        elif trade.vtSymbol == self.vtSymbol1: 
            self.pos1 += trade.volume
        if trade != None and trade.direction == u'空':
        if trade.vtSymbol == self.vtSymbol:
            self.pos -= trade.volume
        elif trade.vtSymbol == self.vtSymbol1: 
            self.pos1 -= trade.volume
        if trade != None and trade.direction == u'多' and trade.offset == u'开仓':
        if trade.vtSymbol == self.vtSymbol:
            self.tpos0L += trade.volume
        elif trade.vtSymbol == self.vtSymbol1: 
            self.tpos1L += trade.volume
        elif trade != None and trade.direction == u'空' and trade.offset == u'开仓':
        if trade.vtSymbol == self.vtSymbol:
            self.tpos0S += trade.volume
        elif trade.vtSymbol == self.vtSymbol1: 
            self.tpos1S += trade.volume
        elif trade != None and trade.direction == u'多' and trade.offset == u'平仓':
        if trade.vtSymbol == self.vtSymbol:
            self.ypos0S -= trade.volume
        elif trade.vtSymbol == self.vtSymbol1: 
            self.ypos1S -= trade.volume
        elif trade != None and trade.direction == u'多' and trade.offset == u'平今':
        if trade.vtSymbol == self.vtSymbol:
            self.tpos0S -= trade.volume
        elif trade.vtSymbol == self.vtSymbol1: 
            self.tpos1S -= trade.volume
        elif trade != None and trade.direction == u'多' and trade.offset == u'平昨':
        if trade.vtSymbol == self.vtSymbol:
            self.ypos0S -= trade.volume
        elif trade.vtSymbol == self.vtSymbol1: 
            self.ypos1S -= trade.volume
        elif trade != None and trade.direction == u'空' and trade.offset == u'平仓':
        if trade.vtSymbol == self.vtSymbol:
            self.ypos0L -= trade.volume
        elif trade.vtSymbol == self.vtSymbol1: 
            self.ypos1L -= trade.volume
        elif trade != None and trade.direction == u'空' and trade.offset == u'平今':
        if trade.vtSymbol == self.vtSymbol:
            self.tpos0L -= trade.volume
        elif trade.vtSymbol == self.vtSymbol1: 
            self.tpos1L -= trade.volume
        elif trade != None and trade.direction == u'空' and trade.offset == u'平昨':
        if trade.vtSymbol == self.vtSymbol:
            self.ypos0L -= trade.volume
        elif trade.vtSymbol == self.vtSymbol1: 
            self.ypos1L -= trade.volume

    #----------------------------------------------------------------------
    def onBar(self, bar):
        """收到Bar推送(必须由用户继承实现)"""
        raise NotImplementedError

    #----------------------------------------------------------------------
    def buy(self, price, volume, stop=False):
        """买开"""
        return self.sendOrder(CTAORDER_BUY, price, volume, stop)

    #----------------------------------------------------------------------
    def sell(self, price, volume, stop=False):
        """卖平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos0L)
    y_vol = volume - t_vol
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrder(CTAORDER_SELL_TODAY, price, t_vol, stop)       
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrder(CTAORDER_SELL, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def sell_y(self, price, volume, stop=False):
        """卖平"""
    return self.sendOrder(CTAORDER_SELL, price, volume, stop)       

    def sell_t(self, price, volume, stop=False):
        """卖平"""
    return self.sendOrder(CTAORDER_SELL_TODAY, price, volume, stop)       

    #----------------------------------------------------------------------
    def sell1_y(self, price, volume, stop=False):
        """卖平"""
    return self.sendOrder1(CTAORDER_SELL, price, volume, stop)       

    def sell1_t(self, price, volume, stop=False):
        """卖平"""
    return self.sendOrder1(CTAORDER_SELL_TODAY, price, volume, stop)       

    #----------------------------------------------------------------------
    def short(self, price, volume, stop=False):
        """卖开"""
        return self.sendOrder(CTAORDER_SHORT, price, volume, stop)          

    #----------------------------------------------------------------------
    def cover(self, price, volume, stop=False):
        """买平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos0S)
    y_vol = volume - t_vol
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrder(CTAORDER_COVER_TODAY, price, t_vol, stop)       
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrder(CTAORDER_COVER, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def cover_y(self, price, volume, stop=False):
        """买平"""
    return self.sendOrder(CTAORDER_COVER, price, volume, stop)       

    #----------------------------------------------------------------------
    def cover_t(self, price, volume, stop=False):
        """买平"""
    return self.sendOrder(CTAORDER_COVER_TODAY, price, volume, stop)       

    #----------------------------------------------------------------------
    def cover1_y(self, price, volume, stop=False):
        """买平"""
    return self.sendOrder1(CTAORDER_COVER, price, volume, stop)       

    #----------------------------------------------------------------------
    def cover1_t(self, price, volume, stop=False):
        """买平"""
    return self.sendOrder1(CTAORDER_COVER_TODAY, price, volume, stop)       


    #----------------------------------------------------------------------
    def buy_fok(self, price, volume, stop=False):
        """买开"""
        return self.sendOrderFOK(CTAORDER_BUY, price, volume, stop)

    #----------------------------------------------------------------------
    def sell_fok(self, price, volume, stop=False):
        """卖平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos0L)
    y_vol = volume - t_vol
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrderFOK(CTAORDER_SELL_TODAY, price, t_vol, stop)       
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrderFOK(CTAORDER_SELL, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def short_fok(self, price, volume, stop=False):
        """卖开"""
        return self.sendOrderFOK(CTAORDER_SHORT, price, volume, stop)          

    #----------------------------------------------------------------------
    def cover_fok(self, price, volume, stop=False):
        """买平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos0S)
    y_vol = volume - t_vol
    if t_vol <= 0 and y_vol <=0:
        self.output("买平出错")
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrderFOK(CTAORDER_COVER_TODAY, price, t_vol, stop)       
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrderFOK(CTAORDER_COVER, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def buy_fak(self, price, volume, stop=False):
        """买开"""
        return self.sendOrderFAK(CTAORDER_BUY, price, volume, stop)

    #----------------------------------------------------------------------
    def sell_fak(self, price, volume, stop=False):
        """卖平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos0L)
    y_vol = volume - t_vol
    if t_vol <= 0 and y_vol <=0:
        self.output("卖平出错")
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrderFAK(CTAORDER_SELL_TODAY, price, t_vol, stop)       
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrderFAK(CTAORDER_SELL, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def short_fak(self, price, volume, stop=False):
        """卖开"""
        return self.sendOrderFAK(CTAORDER_SHORT, price, volume, stop)          

    #----------------------------------------------------------------------
    def cover_fak(self, price, volume, stop=False):
        """买平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos0S)
    y_vol = volume - t_vol
    if t_vol <= 0 and y_vol <=0:
        self.output("买平出错")
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrder_FAK(CTAORDER_COVER_TODAY, price, t_vol, stop)       
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrder_FAK(CTAORDER_COVER, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def sendOrder(self, orderType, price, volume, stop=False):
        """发送委托"""
        if self.trading:
            # 如果stop为True,则意味着发本地停止单
            if stop:
                vtOrderID = self.ctaEngine.sendStopOrder(self.vtSymbol, orderType, price, volume, self)
            else:
                vtOrderID = self.ctaEngine.sendOrder(self.vtSymbol, orderType, price, volume, self) 
            return vtOrderID
        else:
            return ''        

    #----------------------------------------------------------------------
    def sendOrderFOK(self, orderType, price, volume, stop=False):
        """发送委托"""
        if self.trading:
            # 如果stop为True,则意味着发本地停止单
            if stop:
                vtOrderID = self.ctaEngine.sendStopOrder(self.vtSymbol, orderType, price, volume, self)
            else:
                vtOrderID = self.ctaEngine.sendOrderFOK(self.vtSymbol, orderType, price, volume, self) 
            return vtOrderID
        else:
            return ''        

    #----------------------------------------------------------------------
    def sendOrderFAK(self, orderType, price, volume, stop=False):
        """发送委托"""
        if self.trading:
            # 如果stop为True,则意味着发本地停止单
            if stop:
                vtOrderID = self.ctaEngine.sendStopOrder(self.vtSymbol, orderType, price, volume, self)
            else:
                vtOrderID = self.ctaEngine.sendOrderFAK(self.vtSymbol, orderType, price, volume, self) 
            return vtOrderID
        else:
            return ''        


    #----------------------------------------------------------------------
    def buy1(self, price, volume, stop=False):
        """买开"""
        return self.sendOrder1(CTAORDER_BUY, price, volume, stop)

    #----------------------------------------------------------------------
    def sell1(self, price, volume, stop=False):
        """卖平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos1L)
    y_vol = volume - t_vol
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrder1(CTAORDER_SELL_TODAY, price, t_vol, stop)       
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrder1(CTAORDER_SELL, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def short1(self, price, volume, stop=False):
        """卖开"""
        return self.sendOrder1(CTAORDER_SHORT, price, volume, stop)          

    #----------------------------------------------------------------------
    def cover1(self, price, volume, stop=False):
        """买平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos1S)
    y_vol = volume - t_vol
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrder1(CTAORDER_COVER_TODAY, price, t_vol, stop)       
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrder1(CTAORDER_COVER, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def buy1_fok(self, price, volume, stop=False):
        """买开"""
        return self.sendOrder1FOK(CTAORDER_BUY, price, volume, stop)

    #----------------------------------------------------------------------
    def sell1_fok(self, price, volume, stop=False):
        """卖平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos1L)
    y_vol = volume - t_vol
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrder1FOK(CTAORDER_SELL_TODAY, price, t_vol, stop)       
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrder1FOK(CTAORDER_SELL, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def short1_fok(self, price, volume, stop=False):
        """卖开"""
        return self.sendOrder1FOK(CTAORDER_SHORT, price, volume, stop)          

    #----------------------------------------------------------------------
    def cover1_fok(self, price, volume, stop=False):
        """买平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos1S)
    y_vol = volume - t_vol
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrder1FOK(CTAORDER_COVER_TODAY, price, t_vol, stop) 
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrder1FOK(CTAORDER_COVER, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def buy1_fak(self, price, volume, stop=False):
        """买开"""
        return self.sendOrder1FAK(CTAORDER_BUY, price, volume, stop)

    #----------------------------------------------------------------------
    def sell1_fak(self, price, volume, stop=False):
        """卖平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos1L)
    y_vol = volume - t_vol
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrder1FAK(CTAORDER_SELL_TODAY, price, t_vol, stop)       
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrder1FAK(CTAORDER_SELL, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def short1_fak(self, price, volume, stop=False):
        """卖开"""
        return self.sendOrder1FAK(CTAORDER_SHORT, price, volume, stop)          

    #----------------------------------------------------------------------
    def cover1_fak(self, price, volume, stop=False):
        """买平"""
    t_vol = 0
    y_vol = 0
    t_vol = min(volume,self.tpos1S)
    y_vol = volume - t_vol
    orderId = None
    orderId1 = None
    orderIds = []
    if t_vol > 0:
        orderId=self.sendOrder1FAK(CTAORDER_COVER_TODAY, price, t_vol, stop) 
    if t_vol ==0 and y_vol > 0:
        orderId1=self.sendOrder1FAK(CTAORDER_COVER, price, y_vol, stop)       
    if orderId:
        orderIds.append(orderId)
    if orderId1:
        orderIds.append(orderId1)
    return orderIds

    #----------------------------------------------------------------------
    def sendOrder1(self, orderType, price, volume, stop=False):
        """发送委托"""
        if self.trading:
            # 如果stop为True,则意味着发本地停止单
            if stop:
                vtOrderID = self.ctaEngine.sendStopOrder(self.vtSymbol1, orderType, price, volume, self)
            else:
                vtOrderID = self.ctaEngine.sendOrder(self.vtSymbol1, orderType, price, volume, self) 
            return vtOrderID
        else:
            return ''        

    #----------------------------------------------------------------------
    def sendOrder1FOK(self, orderType, price, volume, stop=False):
        """发送委托"""
        if self.trading:
            # 如果stop为True,则意味着发本地停止单
            if stop:
                vtOrderID = self.ctaEngine.sendStopOrder(self.vtSymbol1, orderType, price, volume, self)
            else:
                vtOrderID = self.ctaEngine.sendOrderFOK(self.vtSymbol1, orderType, price, volume, self) 
            return vtOrderID
        else:
            return ''        

    #----------------------------------------------------------------------
    def sendOrder1FAK(self, orderType, price, volume, stop=False):
        """发送委托"""
        if self.trading:
            # 如果stop为True,则意味着发本地停止单
            if stop:
                vtOrderID = self.ctaEngine.sendStopOrder(self.vtSymbol1, orderType, price, volume, self)
            else:
                vtOrderID = self.ctaEngine.sendOrderFAK(self.vtSymbol1, orderType, price, volume, self) 
            return vtOrderID
        else:
            return ''        


    #----------------------------------------------------------------------
    def cancelOrder(self, vtOrderID):
        """撤单"""
        return self.ctaEngine.cancelOrder(vtOrderID)
        #if STOPORDERPREFIX in vtOrderID:
        #    return self.ctaEngine.cancelStopOrder(vtOrderID)
        #else:
        #    return self.ctaEngine.cancelOrder(vtOrderID)

    #----------------------------------------------------------------------
    def insertTick(self, tick):
        """向数据库中插入tick数据"""
        self.ctaEngine.insertData(self.tickDbName, self.vtSymbol, tick)

    #----------------------------------------------------------------------
    def insertBar(self, bar):
        """向数据库中插入bar数据"""
        self.ctaEngine.insertData(self.barDbName, self.vtSymbol, bar)

    #----------------------------------------------------------------------
    def loadTick(self, days):
        """读取tick数据"""
        return self.ctaEngine.loadTick(self.tickDbName, self.vtSymbol, days)

    #----------------------------------------------------------------------
    def loadBar(self, days):
        """读取bar数据"""
        return self.ctaEngine.loadBar(self.barDbName, self.vtSymbol, days)

    #----------------------------------------------------------------------
    def writeCtaLog(self, content):
        """记录CTA日志"""
        content = self.name + ':' + content
        self.ctaEngine.writeCtaLog(content)

    #----------------------------------------------------------------------
    def putEvent(self):
        """发出策略状态变化事件"""
        self.ctaEngine.putStrategyEvent(self.name)

 
 

ctaBacktesting.py文件


 

# encoding: UTF-8

'''
本文件中包含的是CTA模块的回测引擎,回测引擎的API和CTA引擎一致,
可以使用和实盘相同的代码进行回测。
'''
from __future__ import division  
from itertools import product
import copy
import os
import csv
import multiprocessing
import json
import pymongo
import threading

from datetime import datetime
from collections import OrderedDict
from progressbar import ProgressBar
from collections import deque

from ctaBase import *

from vtConstant import *
from vtBase import VtOrderData, VtTradeData
from vtFunction import loadMongoSetting

CAPITAL_DB_NAME = 'vt_trader_cap_db'

########################################################################
class BacktestingEngine(object):
    """
    CTA回测引擎
    函数接口和策略引擎保持一样,
    从而实现同一套代码从回测到实盘。
    增加双合约回测功能
    增加快速慢速切换功能(挂单策略建议使用快速模式)
    """

    TICK_MODE = 'tick'
    BAR_MODE = 'bar'
    bufferSize = 1000
    Version = 20161122

    #----------------------------------------------------------------------
    def __init__(self,optimism=False):
        """Constructor"""
        # 本地停止单编号计数
        self.stopOrderCount = 0
        # stopOrderID = STOPORDERPREFIX + str(stopOrderCount)

        # 本地停止单字典
        # key为stopOrderID,value为stopOrder对象
        self.stopOrderDict = {}             # 停止单撤销后不会从本字典中删除
        self.workingStopOrderDict = {}      # 停止单撤销后会从本字典中删除

        # 回测相关
        self.strategy = None        # 回测策略
        self.mode = self.BAR_MODE   # 回测模式,默认为K线
        self.shfe = True        # 上期所  
        self.fast = False           # 是否支持排队

    self.plot = True
        self.plotfile = False
        self.optimism = False

        self.leverage = 0.07
        self.slippage = 0           # 回测时假设的滑点
        self.rate = 0               # 回测时假设的佣金比例(适用于百分比佣金)
        self.rate1 = 0              # 回测时假设的佣金比例(适用于百分比佣金)
        self.size = 1               # 合约大小,默认为1        
        self.size1 = 1              # 合约大小,默认为1        
        self.mPrice = 1             # 最小价格变动,默认为1        
        self.mPrice1 = 1            # 最小价格变动,默认为1        

        self.dbClient = None        # 数据库客户端
        self.mcClient = None        # 数据库客户端
        self.dbCursor = None        # 数据库指针
        self.dbCursor1 = None       # 数据库指针

        self.backtestingData = deque([])   # 回测用的数据
        self.backtestingData1 = deque([])  # 回测用的数据

        self.dataStartDate = None       # 回测数据开始日期,datetime对象
        self.dataEndDate = None         # 回测数据结束日期,datetime对象
        self.strategyStartDate = None   # 策略启动日期(即前面的数据用于初始化),datetime对象

        self.limitOrderDict = OrderedDict()         # 限价单字典
        self.workingLimitOrderDict = OrderedDict()  # 活动限价单字典,用于进行撮合用
        self.limitOrderCount = 0                    # 限价单编号

        self.limitOrderDict1 = OrderedDict()         # 合约2限价单字典
        self.workingLimitOrderDict1 = OrderedDict()  # 合约2活动限价单字典,用于进行撮合用

        self.tradeCount = 0                # 成交编号
        self.tradeDict = OrderedDict()     # 成交字典

        self.tradeCount1 = 0               # 成交编号1
        self.tradeDict1 = OrderedDict()    # 成交字典1

        self.tradeSnap = OrderedDict()     # 主合约市场快照
        self.tradeSnap1 = OrderedDict()    # 副合约市场快照

        self.trade1Snap = OrderedDict()    # 主合约市场快照1
        self.trade1Snap1 = OrderedDict()   # 副合约市场快照1

    self.i=0            # 主合约数据准备进度
        self.j=0            # 副合约数据准备进度
        self.dataClass = None

        self.logList = []               # 日志记录

        self.orderPrice = {}        # 主合约限价单价格
    self.orderVolume = {}       # 副合约限价单盘口

    self.orderPrice1 = {}       # 限价单价格
    self.orderVolume1 = {}      # 限价单盘口

        # 当前最新数据,用于模拟成交用
        self.tick = None
        self.tick1 = None
        self.lasttick = None
        self.lasttick1 = None
        self.bar = None
        self.bar1 = None
        self.dt = None      # 最新的时间

    #----------------------------------------------------------------------
    def setStartDate(self, startDate='20100416', initDays=10):
        """设置回测的启动日期
           支持两种日期模式"""
    if len(startDate) == 8:
        self.dataStartDate = datetime.strptime(startDate, '%Y%m%d')
    else:
        self.dataStartDate = datetime.strptime(startDate, '%Y%m%d %H:%M:%S')

    #----------------------------------------------------------------------
    def setEndDate(self, endDate='20100416'):
        """设置回测的结束日期
           支持两种日期模式"""
    if len(endDate) == 8:
            self.dataEndDate= datetime.strptime(endDate, '%Y%m%d')
        else:
        self.dataEndDate= datetime.strptime(endDate, '%Y%m%d %H:%M:%S')

    #----------------------------------------------------------------------
    def setBacktestingMode(self, mode):
        """设置回测模式"""
        self.mode = mode
        if self.mode == self.BAR_MODE:
            self.dataClass = CtaBarData
        else:
            self.dataClass = CtaTickData

    #----------------------------------------------------------------------
    def loadHistoryData1(self, dbName, symbol):
        """载入历史数据"""
        host, port = loadMongoSetting()
    if not self.dbClient:
            self.dbClient = pymongo.MongoClient(host, port, socketKeepAlive=True)
        collection = self.dbClient[dbName][symbol]          
        self.output(u'开始载入合约2数据')

        # 首先根据回测模式,确认要使用的数据类
        if self.mode == self.BAR_MODE:
            dataClass = CtaBarData
            func = self.newBar1
        else:
            dataClass = CtaTickData
            func = self.newTick1
        self.output("Start : " + str(self.dataStartDate))
        self.output("End : " + str(self.dataEndDate))

        # 载入回测数据
        if not self.dataEndDate:
            flt = {'datetime':{'$gte':self.dataStartDate}}   # 数据过滤条件
        else:
            flt = {'datetime':{'$gte':self.dataStartDate,
                               '$lte':self.dataEndDate}}  
        self.dbCursor1 = collection.find(flt,no_cursor_timeout=True).batch_size(self.bufferSize)

        self.output(u'载入完成,数据量:%s' %(self.dbCursor1.count()))
        self.output(u' ')


    #----------------------------------------------------------------------
    def loadHistoryData(self, dbName, symbol):
        """载入历史数据"""
        host, port = loadMongoSetting()
    if not self.dbClient:
            self.dbClient = pymongo.MongoClient(host, port, socketKeepAlive=True)
        collection = self.dbClient[dbName][symbol]          
        self.output(u'开始载入数据')

        # 首先根据回测模式,确认要使用的数据类
        if self.mode == self.BAR_MODE:
            dataClass = CtaBarData
            func = self.newBar
        else:
            dataClass = CtaTickData
            func = self.newTick

        # 载入回测数据
    self.output("Start : " + str(self.dataStartDate))
    self.output("End : " + str(self.dataEndDate))
        if not self.dataEndDate:
            flt = {'datetime':{'$gte':self.dataStartDate}}   # 数据过滤条件
        else:
            flt = {'datetime':{'$gte':self.dataStartDate,
                               '$lte':self.dataEndDate}}  
        self.dbCursor = collection.find(flt,no_cursor_timeout=True).batch_size(self.bufferSize)

        self.output(u'载入完成,数据量:%s' %(self.dbCursor.count()))
        self.output(u' ')

    #----------------------------------------------------------------------
    def prepareData(self,dbCursor_count,dbCursor_count1):
        """数据准备线程"""
    while len(self.backtestingData) < self.bufferSize and self.j < dbCursor_count:
        d = self.dbCursor.next()
                data = self.dataClass()
                data.__dict__ = d
            self.backtestingData.append(data)
            self.j += 1
    while len(self.backtestingData1) < self.bufferSize and self.i < dbCursor_count1:
            d1 = self.dbCursor1.next()
                data1 = self.dataClass()
                data1.__dict__ = d1
            self.backtestingData1.append(data1)
            self.i += 1

    #----------------------------------------------------------------------
    def runBacktesting(self):
        """运行回测
           判断是否双合约"""
    if self.strategy.vtSymbol1 == None:
        self.runBacktesting_one()
    else:
        self.runBacktesting_two()

    #----------------------------------------------------------------------
    def runBacktesting_two(self):
        """运行回测"""
        if self.mode == self.BAR_MODE:
            self.dataClass = CtaBarData
            func = self.newBar
            func1 = self.newBar1
        else:
            self.dataClass = CtaTickData
            func = self.newTick
            func1 = self.newTick1
            func2 = self.newTick01
        self.output(u'开始回测')

        self.strategy.inited = True
        self.strategy.onInit()
        self.output(u'策略初始化完成')

        self.strategy.trading = True
        self.strategy.onStart()
        self.output(u'策略启动完成')

        self.output(u'开始回放数据2')
    dbCursor_count=self.dbCursor.count()
    dbCursor_count1=self.dbCursor1.count()
    self.i = 0;
    self.j = 0;
    lastData = None
    lastData1 = None
    t = None
        # 双合约回测
    while (self.i < dbCursor_count1 and self.j < dbCursor_count) or (self.backtestingData and self.backtestingData1):   
            # 启动数据准备线程
        t = threading.Thread(target=self.prepareData,args=(dbCursor_count,dbCursor_count1))
        t.start()
            # 模拟撮合
        while self.backtestingData and self.backtestingData1:
                # 考虑切片数据可能不连续,同步两个合约的数据
        if self.backtestingData1[0].datetime > self.backtestingData[0].datetime:
            if lastData1:
                func2(self.backtestingData[0],lastData1)
            lastData = self.backtestingData.popleft()
        elif self.backtestingData[0].datetime > self.backtestingData1[0].datetime:
            if lastData:
                func2(lastData,self.backtestingData1[0])
            lastData1 = self.backtestingData1.popleft()
            elif self.backtestingData and self.backtestingData1 and self.backtestingData1[0].datetime == self.backtestingData[0].datetime:
            func2(self.backtestingData[0],self.backtestingData1[0])
            lastData = self.backtestingData.popleft()
            lastData1 = self.backtestingData1.popleft()
        t.join()

        self.strategy.onStop()
        self.output(u'数据回放结束')

    #----------------------------------------------------------------------
    def runBacktesting_one(self):
        """运行回测"""
        if self.mode == self.BAR_MODE:
            self.dataClass = CtaBarData
            func = self.newBar
            func1 = self.newBar1
        else:
            self.dataClass = CtaTickData
            func = self.newTick
        self.output(u'开始回测')

        self.strategy.inited = True
        self.strategy.onInit()
        self.output(u'策略初始化完成')

        self.strategy.trading = True
        self.strategy.onStart()
        self.output(u'策略启动完成')

        self.output(u'开始回放数据1')
    dbCursor_count=self.dbCursor.count()
    self.j = 0;
    self.i = 0;
    dbCursor_count1 = 0
    lastData = None
    lastData1 = None
    t = None
        # 单合约回测
    while self.j < dbCursor_count or self.backtestingData:  
            # 启动数据准备线程
        t = threading.Thread(target=self.prepareData,args=(dbCursor_count,dbCursor_count1))
        t.start()
            # 模拟撮合
        while self.backtestingData:
        lastData = self.backtestingData.popleft()
        func(lastData)
        t.join()

        self.strategy.onStop()
        self.output(u'数据回放结束')

    #----------------------------------------------------------------------
    def newBar(self, bar):
        """新的K线"""
        self.bar = bar
        self.dt = bar.datetime
        self.crossLimitOrder()      # 先撮合限价单
        #self.crossStopOrder()       # 再撮合停止单
        self.strategy.onBar(bar)    # 推送K线到策略中

    #----------------------------------------------------------------------
    def newBar1(self, bar):
        """新的K线"""
        self.bar1 = bar
        self.dt = bar.datetime
        self.crossLimitOrder1()      # 先撮合限价单
        self.strategy.onBar(bar)    # 推送K线到策略中

    #----------------------------------------------------------------------
    def newTick(self, tick):
        """新的Tick"""
        self.tick = tick
        self.dt = tick.datetime
        # 低速模式(延时1个Tick撮合)
    self.crossLimitOrder()
        self.strategy.onTick(tick)
        # 高速模式(直接撮合)
    if self.optimism:
           self.crossLimitOrder()
    self.lasttick = tick

    #----------------------------------------------------------------------
    def newTick1(self, tick):
        """新的Tick"""
        self.tick1 = tick
        self.dt = tick.datetime
        # 低速模式(延时1个Tick撮合)
    self.crossLimitOrder()
        self.strategy.onTick(tick)
        # 高速模式(直接撮合)
    if self.optimism:
           self.crossLimitOrder()
    self.lasttick1 = tick

    #----------------------------------------------------------------------
    def newTick01(self, tick, tick1):
        """新的Tick"""
        self.dt = tick.datetime
        self.tick = tick
        self.tick1 = tick1
        # 低速模式(延时1个Tick撮合)
        self.crossLimitOrder1()
        self.crossLimitOrder()
        # 没有切片的合约不发送行情(为了和实盘一致)
    if tick1.datetime >= tick.datetime:
           self.strategy.onTick(self.tick1)
    if tick.datetime >= tick1.datetime:
           self.strategy.onTick(self.tick)
        # 高速模式(直接撮合)
    if self.optimism:
       self.crossLimitOrder1()
           self.crossLimitOrder()
    self.lasttick = self.tick
    self.lasttick1 = self.tick1

    #----------------------------------------------------------------------
    def initStrategy(self, strategyClass, setting=None):
        """
        初始化策略
        setting是策略的参数设置,如果使用类中写好的默认设置则可以不传该参数
        """
        self.strategy = strategyClass(self, setting)
        #self.strategy.name = self.strategy.className

    #----------------------------------------------------------------------
    def sendOrder(self, vtSymbol, orderType, price, volume, strategy):
        """发单"""
        self.limitOrderCount += 1
        orderID = str(self.limitOrderCount)

        order = VtOrderData()
        order.vtSymbol = vtSymbol
        order.price = price
        order.priceType = PRICETYPE_LIMITPRICE
        order.totalVolume = volume
        order.status = STATUS_NOTTRADED     # 刚提交尚未成交
        order.orderID = orderID
        order.vtOrderID = orderID
        order.orderTime = str(self.dt)

        # CTA委托类型映射
        if orderType == CTAORDER_BUY:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_OPEN
        elif orderType == CTAORDER_SELL and not self.shfe:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_CLOSE
        elif orderType == CTAORDER_SELL and self.shfe:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_CLOSEYESTERDAY
        elif orderType == CTAORDER_SELL_TODAY:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_CLOSETODAY
        elif orderType == CTAORDER_SHORT:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_OPEN
        elif orderType == CTAORDER_COVER and not self.shfe:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_CLOSE     
        elif orderType == CTAORDER_COVER and self.shfe:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_CLOSEYESTERDAY     
        elif orderType == CTAORDER_COVER_TODAY:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_CLOSETODAY     

        # 保存到限价单字典中
    if vtSymbol == strategy.vtSymbol:
            self.workingLimitOrderDict[orderID] = order
            self.limitOrderDict[orderID] = order
    elif vtSymbol == strategy.vtSymbol1:
            self.workingLimitOrderDict1[orderID] = order
            self.limitOrderDict1[orderID] = order

        return orderID

    #----------------------------------------------------------------------
    def sendOrderFAK(self, vtSymbol, orderType, price, volume, strategy):
        """发单"""
        self.limitOrderCount += 1
        orderID = str(self.limitOrderCount)

        order = VtOrderData()
        order.vtSymbol = vtSymbol
        order.price = price
        order.priceType = PRICETYPE_FAK
        order.totalVolume = volume
        order.status = STATUS_NOTTRADED     # 刚提交尚未成交
        order.orderID = orderID
        order.vtOrderID = orderID
        order.orderTime = str(self.dt)

        # CTA委托类型映射
        if orderType == CTAORDER_BUY:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_OPEN
        elif orderType == CTAORDER_SELL and not self.shfe:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_CLOSE
        elif orderType == CTAORDER_SELL and self.shfe:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_CLOSEYESTERDAY
        elif orderType == CTAORDER_SELL_TODAY:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_CLOSETODAY
        elif orderType == CTAORDER_SHORT:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_OPEN
        elif orderType == CTAORDER_COVER and not self.shfe:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_CLOSE     
        elif orderType == CTAORDER_COVER and self.shfe:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_CLOSEYESTERDAY     
        elif orderType == CTAORDER_COVER_TODAY:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_CLOSETODAY     

        # 保存到限价单字典中
    if vtSymbol == strategy.vtSymbol:
            self.workingLimitOrderDict[orderID] = order
            self.limitOrderDict[orderID] = order
    elif vtSymbol == strategy.vtSymbol1:
            self.workingLimitOrderDict1[orderID] = order
            self.limitOrderDict1[orderID] = order

        return orderID

    #----------------------------------------------------------------------
    def sendOrderFOK(self, vtSymbol, orderType, price, volume, strategy):
        """发单"""
        self.limitOrderCount += 1
        orderID = str(self.limitOrderCount)

        order = VtOrderData()
        order.vtSymbol = vtSymbol
        order.price = price
        order.priceType = PRICETYPE_FOK
        order.totalVolume = volume
        order.status = STATUS_NOTTRADED     # 刚提交尚未成交
        order.orderID = orderID
        order.vtOrderID = orderID
        order.orderTime = str(self.dt)

        # CTA委托类型映射
        if orderType == CTAORDER_BUY:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_OPEN
        elif orderType == CTAORDER_SELL and not self.shfe:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_CLOSE
        elif orderType == CTAORDER_SELL and self.shfe:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_CLOSEYESTERDAY
        elif orderType == CTAORDER_SELL_TODAY:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_CLOSETODAY
        elif orderType == CTAORDER_SHORT:
            order.direction = DIRECTION_SHORT
            order.offset = OFFSET_OPEN
        elif orderType == CTAORDER_COVER and not self.shfe:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_CLOSE     
        elif orderType == CTAORDER_COVER and self.shfe:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_CLOSEYESTERDAY     
        elif orderType == CTAORDER_COVER_TODAY:
            order.direction = DIRECTION_LONG
            order.offset = OFFSET_CLOSETODAY     

        # 保存到限价单字典中
    if vtSymbol == strategy.vtSymbol:
            self.workingLimitOrderDict[orderID] = order
            self.limitOrderDict[orderID] = order
    elif vtSymbol == strategy.vtSymbol1:
            self.workingLimitOrderDict1[orderID] = order
            self.limitOrderDict1[orderID] = order

        return orderID

    #----------------------------------------------------------------------
    def cancelOrder(self, vtOrderID):
        """撤单"""
        # 找到订单
    if vtOrderID in self.workingLimitOrderDict:
        order = self.workingLimitOrderDict[vtOrderID]
    elif vtOrderID in self.workingLimitOrderDict1:
        order = self.workingLimitOrderDict1[vtOrderID]
    else:
        order = None
        return False
        # 委托回报
    if order.status == STATUS_NOTTRADED:
            order.status = STATUS_CANCELLED
            order.cancelTime = str(self.dt)
        self.strategy.onOrder(order)
    else:
            order.status = STATUS_PARTTRADED_PARTCANCELLED
            order.cancelTime = str(self.dt)
        self.strategy.onOrder(order)
        # 删除数据
    if vtOrderID in self.workingLimitOrderDict:
            self.removeOrder(vtOrderID)
        elif vtOrderID in self.workingLimitOrderDict1:
            self.removeOrder1(vtOrderID)
    return True

    #----------------------------------------------------------------------
    def sendStopOrder(self, vtSymbol, orderType, price, volume, strategy):
        """发停止单(本地实现)"""
        self.stopOrderCount += 1
        stopOrderID = STOPORDERPREFIX + str(self.stopOrderCount)

        so = StopOrder()
        so.vtSymbol = vtSymbol
        so.price = price
        so.volume = volume
        so.strategy = strategy
        so.stopOrderID = stopOrderID
        so.status = STOPORDER_WAITING

        if orderType == CTAORDER_BUY:
            so.direction = DIRECTION_LONG
            so.offset = OFFSET_OPEN
        elif orderType == CTAORDER_SELL:
            so.direction = DIRECTION_SHORT
            so.offset = OFFSET_CLOSE
        elif orderType == CTAORDER_SHORT:
            so.direction = DIRECTION_SHORT
            so.offset = OFFSET_OPEN
        elif orderType == CTAORDER_COVER:
            so.direction = DIRECTION_LONG
            so.offset = OFFSET_CLOSE           

        # 保存stopOrder对象到字典中
        self.stopOrderDict[stopOrderID] = so
        self.workingStopOrderDict[stopOrderID] = so

        return stopOrderID

    #----------------------------------------------------------------------
    def cancelStopOrder(self, stopOrderID):
        """撤销停止单"""
        # 检查停止单是否存在
        if stopOrderID in self.workingStopOrderDict:
            so = self.workingStopOrderDict[stopOrderID]
            so.status = STOPORDER_CANCELLED
            del self.workingStopOrderDict[stopOrderID]

    #----------------------------------------------------------------------
    def filterTradeTime(self):
        """过滤非交易时间"""
        if self.dt:
            hour = self.dt.hour
            # 丢弃非交易时间错误数据
        if (hour >= 15 and hour < 20) or (hour >= 2 and hour < 8):
                return True
            # 清空隔交易日订单
        elif hour == 8:
                self.lasttick = None
                self.lasttick1 = None
            for orderID in self.workingLimitOrderDict:
                self.cancelOrder(orderID)
            for orderID in self.workingLimitOrderDict1:
                self.cancelOrder(orderID)
                return True
        elif hour == 20:
                self.lasttick = None
                self.lasttick1 = None
            for orderID in self.workingLimitOrderDict:
                self.cancelOrder(orderID)
            for orderID in self.workingLimitOrderDict1:
                self.cancelOrder(orderID)
                return True
        return False

    #----------------------------------------------------------------------
    def calcTickVolume(self,tick,lasttick,size):
        """计算两边盘口的成交量"""
        if (not lasttick):
        currentVolume = tick.volume
                currentTurnOver = tick.turnover
                pOnAsk = tick.askPrice1
                pOnBid = tick.bidPrice1
        else:
        currentVolume = tick.volume - lasttick.volume
                currentTurnOver = tick.turnover - lasttick.turnover
                pOnAsk = lasttick.askPrice1
                pOnBid = lasttick.bidPrice1

        if lasttick and currentVolume > 0: 
                currentPrice = currentTurnOver/currentVolume/size
                ratio = (currentPrice-lasttick.bidPrice1)/(lasttick.askPrice1-lasttick.bidPrice1)
                ratio = max(ratio,0)
                ratio = min(ratio,1)
                volOnAsk = ratio*currentVolume
                volOnBid = currentVolume - volOnAsk
        else:
                volOnAsk = 0
                volOnBid = 0
        return volOnBid,volOnAsk,pOnBid,pOnAsk

    #----------------------------------------------------------------------
    def removeOrder(self, orderID):
        """清除订单信息"""
        del self.workingLimitOrderDict[orderID]
        if orderID in self.orderPrice:
            del self.orderPrice[orderID]
        if orderID in self.orderVolume:
            del self.orderVolume[orderID]

    #----------------------------------------------------------------------
    def removeOrder1(self, orderID):
        """清除订单信息"""
        del self.workingLimitOrderDict1[orderID]
        if orderID in self.orderPrice1:
            del self.orderPrice1[orderID]
        if orderID in self.orderVolume1:
            del self.orderVolume1[orderID]

    #----------------------------------------------------------------------
    def snapMarket(self, tradeID):
        """快照市场"""
        if self.mode == self.TICK_MODE:
        self.tradeSnap[tradeID] = copy.copy(self.tick)
        self.tradeSnap1[tradeID] = copy.copy(self.tick1)
        else:
        self.tradeSnap[tradeID] = copy.copy(self.bar)
        self.tradeSnap1[tradeID] = copy.copy(self.bar1)

    #----------------------------------------------------------------------
    def strategyOnTrade(self, order, volumeTraded, priceTraded):
        """处理成交回报"""
        # 推送成交数据,
        self.tradeCount += 1
        tradeID = str(self.tradeCount)
        trade = VtTradeData()
        #省略回测无关内容
        #trade.tradeID = tradeID
        #trade.vtTradeID = tradeID
        #trade.orderID = order.orderID
        #trade.vtOrderID = order.orderID
        trade.dt = self.dt
        trade.vtSymbol = order.vtSymbol
        trade.direction = order.direction
        trade.offset = order.offset
        trade.tradeTime = self.dt.strftime('%Y%m%d %H:%M:%S.')+self.dt.strftime('%f')[:1] 
        trade.volume = volumeTraded
        trade.price = priceTraded
        self.strategy.onTrade(copy.copy(trade))
        # 快照市场,用于计算持仓盈亏,暂不支持
        # self.snapMarket(tradeID)
        if trade.vtSymbol == self.strategy.vtSymbol:
        self.tradeDict[tradeID] = trade
        else:
        self.tradeDict1[tradeID] = trade

    #----------------------------------------------------------------------
    def crossLimitOrder(self):
        """基于最新数据撮合限价单"""
        # 缓存数据
        tick  = self.tick
        lasttick = self.lasttick
        # 过滤数据
        if self.filterTradeTime():
            return

        # 确定撮合价格
        if self.mode == self.BAR_MODE:
            # Bar价格撮合,目前不支持FokopenFak
            buyCrossPrice = self.bar.low   # 若买入方向限价单价格高于该价格,则会成交
            sellCrossPrice = self.bar.high  # 若卖出方向限价单价格低于该价格,则会成交
        else:
            # Tick采用对价撮合,支持Fok,Fak
            buyCrossPrice  = tick.askPrice1 if tick.askPrice1 > 0 else tick.bidPrice1+self.mPrice
            sellCrossPrice = tick.bidPrice1 if tick.bidPrice1 > 0 else tick.askPrice1-self.mPrice

        # 遍历限价单字典中的所有限价单
        for orderID, order in self.workingLimitOrderDict.items():
            # 判断是否会成交
            buyCross  = order.direction==DIRECTION_LONG  and order.price>=buyCrossPrice
            sellCross = order.direction==DIRECTION_SHORT and order.price<=sellCrossPrice

            # 如果可以对价撮合
        if buyCross or sellCross:

                # 计算成交量
                volumeTraded = (order.totalVolume-order.tradedVolume) 
                if self.mode == self.TICK_MODE:
                    volumeTraded = min(volumeTraded, tick.askVolume1) if buyCross \
                        else min(volumeTraded, tick.bidVolume1)
                volumeTraded = max(volumeTraded,1)

                # 计算成交价
                if orderID in self.orderPrice and order.tradedVolume == 0:
                    priceTraded = order.price
                else:
                    priceTraded = min(order.price,buyCrossPrice) if buyCross \
                        else max(order.price,sellCrossPrice)

                # 推送委托数据
                order.tradedVolume += volumeTraded
                # 分别处理普通限价,FOK,FAK订单
        if order.priceType == PRICETYPE_FOK:
            if order.tradedVolume < order.totalVolume:
                order.status = STATUS_CANCELLED
                            volumeTraded = 0
            else:
                order.status = STATUS_ALLTRADED

        elif order.priceType == PRICETYPE_FAK:
            if order.tradedVolume < order.totalVolume:
                order.status = STATUS_PARTTRADED_PARTCANCELLED
            else:
                order.status = STATUS_ALLTRADED
        else:
            if order.tradedVolume < order.totalVolume:
                order.status = STATUS_PARTTRADED
                self.orderPrice[orderID] = order.price
                self.orderVolume[orderID] = 0
            else:
                order.status = STATUS_ALLTRADED
                # 推送委托回报
        self.strategy.onOrder(order)
                # 推送成交回报
                if volumeTraded > 0:
                    self.strategyOnTrade(order, volumeTraded,priceTraded)
                # 处理完毕,删除数据
                if not order.status == STATUS_PARTTRADED:
                    self.removeOrder(orderID)

            # 模拟排队撮合部分,TICK模式有效(使用Tick内成交均价简单估计两边盘口的成交量)
            elif self.mode == self.TICK_MODE and not self.fast:

                # 计算估计的两边盘口的成交量
                volOnBid,volOnAsk,pOnBid,pOnAsk = self.calcTickVolume(tick, lasttick, self.size)

                # 排队队列维护
        if orderID in self.orderPrice:
                        # 非首次进入队列
            if orderID not in self.orderVolume: 
                            if order.price == sellCrossPrice and order.direction==DIRECTION_LONG:
                    self.orderVolume[orderID] = tick.bidVolume1 
                            elif order.price == buyCrossPrice and order.direction==DIRECTION_SHORT:
                    self.orderVolume[orderID] = tick.askVolume1
                        # 首先排队进入,然后被打穿(不允许直接在买卖盘中间成交)
                        elif order.price > sellCrossPrice and order.direction==DIRECTION_LONG:
                self.orderVolume[orderID] = 0
                        elif order.price < buyCrossPrice and order.direction==DIRECTION_SHORT:
                self.orderVolume[orderID] = 0
                        # 更新排队值
                        elif order.price == pOnBid and order.direction==DIRECTION_LONG:
                self.orderVolume[orderID] -= volOnBid
                        elif order.price == pOnAsk and order.direction==DIRECTION_SHORT:
                self.orderVolume[orderID] -= volOnAsk
        else:
                        # 首次进入队列
            self.orderPrice[orderID] = order.price
            if order.direction==DIRECTION_SHORT and order.price == tick.askPrice1:
                self.orderVolume[orderID] = tick.askVolume1
            elif order.direction==DIRECTION_LONG and order.price == tick.bidPrice1:
                self.orderVolume[orderID] = tick.bidVolume1

                # 排队成交,注意,目前简单一次性全部成交!!
        if orderID in self.orderVolume and self.orderVolume[orderID] <= 0:

                    # 推送委托数据
                    priceTraded  = order.price
                    volumeTraded = order.totalVolume - order.tradedVolume
                    order.tradedVolume = order.totalVolume
                    order.status = STATUS_ALLTRADED
                    self.strategy.onOrder(order)

                        # 推送成交回报
                        self.strategyOnTrade(order, volumeTraded, priceTraded)

                    # 从字典中删除该限价单
                        self.removeOrder(orderID)
        else:
            order.tradedVolume = 0
                    order.status = STATUS_NOTTRADED
            if order.priceType == PRICETYPE_FOK or order.priceType == PRICETYPE_FAK:
                order.status = STATUS_CANCELLED
                            self.removeOrder(orderID)
                    self.strategy.onOrder(order)

    #----------------------------------------------------------------------
    def crossLimitOrder1(self):
        """基于最新数据撮合限价单"""
        # 缓存数据
        lasttick1 = self.lasttick1
        tick1 = self.tick1
        bar1  = self.bar1
        if self.filterTradeTime():
            return

        # 区分K线撮合和TICK撮合模式
        if self.mode == self.BAR_MODE:
            buyCrossPrice  = bar1.low    # 若买入方向限价单价格高于该价格,则会成交
            sellCrossPrice = bar1.high   # 若卖出方向限价单价格低于该价格,则会成交
        else:
            # TICK对价撮合,并过滤涨跌停板
            buyCrossPrice  = tick1.askPrice1 if tick1.askPrice1 > 0 else tick1.bidPrice1+self.mPrice1
            sellCrossPrice = tick1.bidPrice1 if tick1.bidPrice1 > 0 else tick1.askPrice1-self.mPrice1

        # 遍历限价单字典中的所有限价单
        for orderID, order in self.workingLimitOrderDict1.items():

            # 判断是否对价直接成交
            buyCross  = order.direction==DIRECTION_LONG and order.price>=buyCrossPrice
            sellCross = order.direction==DIRECTION_SHORT and order.price<=sellCrossPrice

            # 如果直接对价成交
        if buyCross or sellCross:
                # 计算成交量
            volumeTraded = (order.totalVolume-order.tradedVolume) 
                if self.mode == self.TICK_MODE:
                    volumeTraded = min(volumeTraded, tick1.askVolume1) if buyCross \
                        else min(volumeTraded, tick1.bidVolume1)
                volumeTraded = max(volumeTraded,1)

                # 计算成交价
                if orderID in self.orderPrice1 and order.tradedVolume == 0:
                    priceTraded = order.price
                else:
                    priceTraded = min(order.price,buyCrossPrice) if buyCross else max(order.price,sellCrossPrice)

                # 委托回报,区分普通限价单,FOK,FAK
        order.tradedVolume += volumeTraded
        if order.priceType == PRICETYPE_FOK:
            if order.tradedVolume < order.totalVolume:
                order.status = STATUS_CANCELLED
                            volumeTraded = 0
            else:
                order.status = STATUS_ALLTRADED
        elif order.priceType == PRICETYPE_FAK:
            if order.tradedVolume < order.totalVolume:
                order.status = STATUS_PARTTRADED_PARTCANCELLED
            else:
                order.status = STATUS_ALLTRADED
        else:
            if order.tradedVolume < order.totalVolume:
                order.status = STATUS_PARTTRADED
                self.orderPrice1[orderID] = order.price
                self.orderVolume1[orderID] = 0
            else:
                order.status = STATUS_ALLTRADED

                # 推送委托回报
                self.strategy.onOrder(order)
                # 推送成交回报
                if volumeTraded > 0:
                    self.strategyOnTrade(order, volumeTraded, priceTraded)
                # 清除订单信息
                if not order.status == STATUS_PARTTRADED:
                    self.removeOrder1(orderID)

            # 模拟排队撮合部分,只在TICK模式有效    
            elif self.mode == self.TICK_MODE and not self.fast:

                # 计算两边盘口的成交量
                volOnBid,volOnAsk,pOnBid,pOnAsk = self.calcTickVolume(tick1, lasttick1,self.size1)

                # 排队队列维护
        if orderID in self.orderPrice1:
                        # 非首次进入队列
            if orderID not in self.orderVolume1: 
                            if order.price == sellCrossPrice and order.direction==DIRECTION_LONG:
                    self.orderVolume1[orderID] = tick1.bidVolume1
                            elif order.price == buyCrossPrice and order.direction==DIRECTION_SHORT:
                    self.orderVolume1[orderID] = tick1.askVolume1
                        # 首先排队进入,然后被打穿(不允许直接在买卖盘中间成交)
                        elif order.price > sellCrossPrice and order.direction==DIRECTION_LONG:
                self.orderVolume1[orderID] = 0
                        elif order.price < buyCrossPrice and order.direction==DIRECTION_SHORT:
                self.orderVolume1[orderID] = 0
                        # 更新排队值
                        elif order.price == pOnBid and order.direction==DIRECTION_LONG:
                self.orderVolume1[orderID] -= volOnBid
                        elif order.price == pOnAsk and order.direction==DIRECTION_SHORT:
                self.orderVolume1[orderID] -= volOnAsk
        else:
                        # 首次进入队列
            self.orderPrice1[orderID] = order.price
            if order.direction==DIRECTION_SHORT and order.price == tick1.askPrice1:
                self.orderVolume1[orderID] = tick1.askVolume1
            elif order.direction==DIRECTION_LONG and order.price == tick1.bidPrice1:
                self.orderVolume1[orderID] = tick1.bidVolume1

                # 排队成功,注意,目前模拟为一次性成交所有订单量!!
        if orderID in self.orderVolume1 and self.orderVolume1[orderID] <= 0:
                    # 推送委托数据
                    priceTraded = order.price
                    volumeTraded = order.totalVolume - order.tradedVolume
                    order.tradedVolume = order.totalVolume
                    order.status = STATUS_ALLTRADED
                    self.strategy.onOrder(order)

                        # 推送成交回报
                    self.strategyOnTrade(order, volumeTraded, priceTraded)

                    # 从字典中删除该限价单
                        self.removeOrder1(orderID)
        else:
            order.tradedVolume = 0
                    order.status = STATUS_NOTTRADED
            if order.priceType == PRICETYPE_FOK or order.priceType == PRICETYPE_FAK:
                order.status = STATUS_CANCELLED
                            self.removeOrder1(orderID)
                    self.strategy.onOrder(order)

    #----------------------------------------------------------------------
    def crossStopOrder(self):
        """基于最新数据撮合停止单"""
        # 停止单撮合未更新
        # 先确定会撮合成交的价格,这里和限价单规则相反
        if self.mode == self.BAR_MODE:
            buyCrossPrice = self.bar.high    # 若买入方向停止单价格低于该价格,则会成交
            sellCrossPrice = self.bar.low    # 若卖出方向限价单价格高于该价格,则会成交
            bestCrossPrice = self.bar.open   # 最优成交价,买入停止单不能低于,卖出停止单不能高于
        else:
            buyCrossPrice = self.tick.lastPrice
            sellCrossPrice = self.tick.lastPrice
            bestCrossPrice = self.tick.lastPrice

        # 遍历停止单字典中的所有停止单
        for stopOrderID, so in self.workingStopOrderDict.items():
            # 判断是否会成交
            buyCross = so.direction==DIRECTION_LONG and so.price<=buyCrossPrice
            sellCross = so.direction==DIRECTION_SHORT and so.price>=sellCrossPrice

            # 如果发生了成交
            if buyCross or sellCross:
                # 推送成交数据
                self.tradeCount += 1            
                tradeID = str(self.tradeCount)
                trade = VtTradeData()
                trade.vtSymbol = so.vtSymbol
                trade.tradeID = tradeID
                trade.vtTradeID = tradeID

                if buyCross:
                    trade.price = max(bestCrossPrice, so.price)
                else:
                    trade.price = min(bestCrossPrice, so.price)                

                self.limitOrderCount += 1
                orderID = str(self.limitOrderCount)
                trade.orderID = orderID
                trade.vtOrderID = orderID

                trade.direction = so.direction
                trade.offset = so.offset
                trade.volume = so.volume
        trade.tradeTime = self.dt.strftime('%Y%m%d %H:%M:%S.') + self.dt.strftime('%f')[:1] 
                trade.dt = self.dt
                self.strategy.onTrade(copy.copy(trade))

                self.tradeDict[tradeID] = trade

                # 推送委托数据
                so.status = STOPORDER_TRIGGERED

                order = VtOrderData()
                order.vtSymbol = so.vtSymbol
                order.symbol = so.vtSymbol
                order.orderID = orderID
                order.vtOrderID = orderID
                order.direction = so.direction
                order.offset = so.offset
                order.price = so.price
                order.totalVolume = so.volume
                order.tradedVolume = so.volume
                order.status = STATUS_ALLTRADED
                order.orderTime = trade.tradeTime
                self.strategy.onOrder(order)

                self.limitOrderDict[orderID] = order

                # 从字典中删除该限价单
                del self.workingStopOrderDict[stopOrderID]        

    #----------------------------------------------------------------------
    def insertData(self, dbName, collectionName, data):
        """考虑到回测中不允许向数据库插入数据,防止实盘交易中的一些代码出错"""
        pass

    #----------------------------------------------------------------------
    def writeCtaLog(self, content):
        """记录日志"""
        log = str(self.dt) + ' ' + content 
        self.logList.append(log)

    #----------------------------------------------------------------------
    def output(self, content):
        """输出内容"""
    if not self.plot:
        return
    if self.plotfile:
        print content.encode('utf8')
    else:
        print content

    #----------------------------------------------------------------------
    def makeRecord(self, tradeTime, offset, direction, price, pnl):
        """记录成交内容"""
    resDict = {}
    resDict[u'datetime'] = tradeTime 
    resDict[u'price'] = price 
    resDict[u'contract0'] = self.strategy.vtSymbol 
    resDict[u'contract1'] = self.strategy.vtSymbol 
    resDict[u'offset'] = offset
    resDict[u'direction'] = direction
    resDict[u'pnl'] = pnl
    if self.strategy.vtSymbol1:
        resDict[u'contract1'] = self.strategy.vtSymbol1 
        return resDict

    #----------------------------------------------------------------------
    def calculateBacktestingResult(self, detial = False):
        """
        计算回测结果
        """
        self.output(u'按逐笔对冲计算回测结果')
        # 首先基于回测后的成交记录,计算每笔交易的盈亏
        pnlDict = OrderedDict()      # 每笔盈亏的记录 
        longTrade = deque([])        # 未平仓的多头交易
        shortTrade = deque([])       # 未平仓的空头交易
        longTrade1 = deque([])       # 合约2未平仓的多头交易
        shortTrade1 = deque([])      # 合约2未平仓的空头交易
    resList = [{"name":self.strategy.name}]

        # 计算滑点,一个来回包括两次
        totalSlippage = self.slippage * 2 
    self.output(u'总交易量 : '+str(len(self.tradeDict)))
    self.output(u'总交易量1 : '+str(len(self.tradeDict1)))

    leg2 = True

    if self.tradeDict.values():
        dict_trade = self.tradeDict.values()
    else:
        dict_trade = self.tradeDict1.values()
        leg2 = False

    if self.tradeDict1.values():
        dict_trade1 = self.tradeDict1.values()
    else:
        dict_trade1 = self.tradeDict.values()
        leg2 = False

        for trade1 in dict_trade1:
            # 多头交易
            if trade1.direction == DIRECTION_LONG:
                # 当前多头交易为平空
        untraded=True
                while (shortTrade1 and untraded):
                    entryTrade = shortTrade1[0]
            exitTrade = trade1
            volume = min(entryTrade.volume,exitTrade.volume)
            entryTrade.volume = entryTrade.volume-volume
            exitTrade.volume = exitTrade.volume-volume
            if entryTrade.volume == 0:
            shortTrade1.popleft()
            if exitTrade.volume == 0:
            untraded = False

            if exitTrade.dt not in pnlDict:
                        pnlDict[exitTrade.dt] = TradingResult(entryTrade.price, entryTrade.dt, 
                                               exitTrade.price, exitTrade.dt,
                                               -volume, self.rate1, self.slippage1, self.size1)
            elif leg2:
                        pnlDict[exitTrade.dt].add(entryTrade.price, entryTrade.dt, 
                                               exitTrade.price, exitTrade.dt,
                                               -volume, self.rate1, self.slippage1, self.size1)
            if exitTrade.dt in pnlDict and leg2:
                        pnlDict[exitTrade.dt].posPnl = self.calcPosPNL1(exitTrade.tradeID,shortTrade,longTrade,shortTrade1,longTrade1,leg2)
                    elif not leg2:
                        pnlDict[exitTrade.dt].posPnl = self.calcPosPNL1(exitTrade.tradeID,shortTrade,longTrade,shortTrade1,longTrade1,leg2)
                # 如果尚无空头交易
                if untraded:
                    longTrade1.append(trade1)
            # 空头交易        
            else:
                # 当前空头交易为平多
        untraded=True
                while(untraded and longTrade1):
                    entryTrade = longTrade1[0]
            exitTrade = trade1
            volume = min(entryTrade.volume,exitTrade.volume)
            entryTrade.volume = entryTrade.volume-volume
            exitTrade.volume = exitTrade.volume-volume
            if entryTrade.volume == 0:
            longTrade1.popleft()
            if exitTrade.volume == 0:
            untraded = False

            if exitTrade.dt not in pnlDict:
                        pnlDict[exitTrade.dt] = TradingResult(entryTrade.price, entryTrade.dt, 
                                               exitTrade.price, exitTrade.dt,
                                               volume, self.rate1, self.slippage1, self.size1)
            elif leg2:
                        pnlDict[exitTrade.dt].add(entryTrade.price, entryTrade.dt, 
                                               exitTrade.price, exitTrade.dt,
                                               volume, self.rate1, self.slippage1, self.size1)
            if exitTrade.dt in pnlDict and leg2:
                        pnlDict[exitTrade.dt].posPnl = self.calcPosPNL1(exitTrade.tradeID,shortTrade,longTrade,shortTrade1,longTrade1,leg2)
                    elif not leg2:
                        pnlDict[exitTrade.dt].posPnl = self.calcPosPNL1(exitTrade.tradeID,shortTrade,longTrade,shortTrade1,longTrade1,leg2)

                # 如果尚无多头交易
                if untraded:
                    shortTrade1.append(trade1)

        for trade in dict_trade:
            # 多头交易
            if trade.direction == DIRECTION_LONG:
                # 当前多头交易为平空
        untraded = True
                while (shortTrade and untraded):
                    entryTrade = shortTrade[0]
                    exitTrade = trade

                    # 计算比例佣金
            volume = min(entryTrade.volume,exitTrade.volume)
            entryTrade.volume = entryTrade.volume-volume
            exitTrade.volume = exitTrade.volume-volume
            if entryTrade.volume == 0:
            shortTrade.popleft()
            if exitTrade.volume == 0:
            untraded = False

            if exitTrade.dt not in pnlDict:
                        pnlDict[exitTrade.dt] = TradingResult(entryTrade.price, entryTrade.dt, 
                                               exitTrade.price, exitTrade.dt,
                                               -volume, self.rate, self.slippage, self.size)
            elif leg2:
                        pnlDict[exitTrade.dt].add(entryTrade.price, entryTrade.dt, 
                                               exitTrade.price, exitTrade.dt,
                                               -volume, self.rate, self.slippage, self.size)
            if exitTrade.dt in pnlDict and leg2:
                        pnlDict[exitTrade.dt].posPnl = self.calcPosPNL(exitTrade.tradeID,shortTrade,longTrade,shortTrade1,longTrade1,leg2)
                    elif not leg2:
                        pnlDict[exitTrade.dt].posPnl = self.calcPosPNL(exitTrade.tradeID,shortTrade,longTrade,shortTrade1,longTrade1,leg2)
            pnl = pnlDict[exitTrade.dt].pnl
                    # 记录用来可视化的成交内容
                    resDict = self.makeRecord(entryTrade.tradeTime,u'开',u'卖',entryTrade.price,pnl)
            resList.append(resDict)
                    resDict = self.makeRecord(exitTrade.tradeTime,u'平',u'买',exitTrade.price,pnl)
            resList.append(resDict)
                # 如果尚无空头交易
                if untraded:
                    longTrade.append(trade)

            # 空头交易        
            else:
                # 当前空头交易为平多
        untraded=True
                while (longTrade and untraded):
                    entryTrade = longTrade[0]
            exitTrade = trade
                    # 计算比例佣金
            volume = min(entryTrade.volume,exitTrade.volume)
            entryTrade.volume = entryTrade.volume-volume
            exitTrade.volume = exitTrade.volume-volume
            if entryTrade.volume == 0:
            longTrade.popleft()
            if exitTrade.volume == 0:
            untraded = False

            if exitTrade.dt not in pnlDict:
                        pnlDict[exitTrade.dt] = TradingResult(entryTrade.price, entryTrade.dt, 
                                               exitTrade.price, exitTrade.dt,
                                               volume, self.rate, self.slippage, self.size)
            elif leg2:
                        pnlDict[exitTrade.dt].add(entryTrade.price, entryTrade.dt, 
                                               exitTrade.price, exitTrade.dt,
                                               volume, self.rate, self.slippage, self.size)
            if exitTrade.dt in pnlDict and leg2:
                        pnlDict[exitTrade.dt].posPnl = self.calcPosPNL(exitTrade.tradeID,shortTrade,longTrade,shortTrade1,longTrade1,leg2)
                    elif not leg2:
                        pnlDict[exitTrade.dt].posPnl = self.calcPosPNL(exitTrade.tradeID,shortTrade,longTrade,shortTrade1,longTrade1,leg2)
            pnl = pnlDict[exitTrade.dt].pnl
                    # 记录用来可视化的成交内容
                    resDict = self.makeRecord(entryTrade.tradeTime,u'开',u'买',entryTrade.price,pnl)
            resList.append(resDict)
                    resDict = self.makeRecord(exitTrade.tradeTime,u'平',u'卖',exitTrade.price,pnl)
            resList.append(resDict)
                # 如果尚无多头交易
                if untraded:
                    shortTrade.append(trade)

        # 计算剩余持仓盈亏
        while (shortTrade):
            entryTrade = shortTrade.popleft()
        volume = entryTrade.volume
            if self.mode == self.TICK_MODE:
            exitTime = self.tick.datetime
            exitPrice = self.tick.askPrice1
            else:
            exitTime = self.bar.datetime
            exitPrice = self.bar.close

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate, self.slippage, self.size)
        pnl = pnlDict[exitTime].pnl
        elif leg2:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate, self.slippage, self.size)
        pnl = pnlDict[exitTime].pnl
            # 记录用来可视化的成交内容
            resDict = self.makeRecord(entryTrade.tradeTime,u'开持',u'卖',entryTrade.price,pnl)
        resList.append(resDict)
            resDict = self.makeRecord(str(exitTime),u'平持',u'买',exitPrice,pnl)
        resList.append(resDict)
        while (longTrade):
            entryTrade = longTrade.popleft()
        volume = entryTrade.volume
            if self.mode == self.TICK_MODE:
            exitTime = self.tick.datetime
            exitPrice = self.tick.bidPrice1
            else:
            exitTime = self.bar.datetime
            exitPrice = self.bar.close

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate, self.slippage, self.size)
        pnl = pnlDict[exitTime].pnl
        elif leg2:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate, self.slippage, self.size)
        pnl = pnlDict[exitTime].pnl
            # 记录用来可视化的成交内容
            resDict = self.makeRecord(entryTrade.tradeTime,u'开持',u'买',entryTrade.price,pnl)
        resList.append(resDict)
            resDict = self.makeRecord(str(exitTime),u'平持',u'卖',exitPrice,pnl)
        resList.append(resDict)
        while (leg2 and shortTrade1):
            entryTrade = shortTrade1.popleft()
        volume = entryTrade.volume
            if self.mode == self.TICK_MODE:
            exitTime = self.tick1.datetime
            exitPrice = self.tick1.askPrice1
            else:
            exitTime = self.bar1.datetime
            exitPrice = self.bar1.close

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate1, self.slippage1, self.size1)
        pnl = pnlDict[exitTime].pnl
        else:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate1, self.slippage1, self.size1)
        pnl = pnlDict[exitTime].pnl
            # 记录用来可视化的成交内容
            resDict = self.makeRecord(entryTrade.tradeTime,u'开持',u'卖',entryTrade.price,pnl)
        resList.append(resDict)
            resDict = self.makeRecord(str(exitTime),u'平持',u'买',exitPrice,pnl)
        resList.append(resDict)
        while (leg2 and longTrade1):
            entryTrade = longTrade1.popleft()
        volume = entryTrade.volume
            if self.mode == self.TICK_MODE:
            exitTime = self.tick1.datetime
            exitPrice = self.tick1.bidPrice1
            else:
            exitTime = self.bar.datetime
            exitPrice = self.bar.close

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate1, self.slippage1, self.size1)
        else:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate1, self.slippage1, self.size1)
        pnl = pnlDict[exitTime].pnl
            # 记录用来可视化的成交内容
            resDict = self.makeRecord(entryTrade.tradeTime,u'开持',u'买',entryTrade.price,pnl)
        resList.append(resDict)
            resDict = self.makeRecord(exitTime,u'平持',u'卖',exitPrice,pnl)
        resList.append(resDict)

        # 由于双合约的问题,需要整理时间序列和结果序列

        timeList = []         # 时间序列
        resultList = []           # 交易结果序列
    pnlDict0 = sorted(pnlDict.iteritems(),key=lambda d:d[0])
    for k,v in pnlDict0:
        timeList.append(k)
        resultList.append(v)

        # 然后基于每笔交易的结果,我们可以计算具体的盈亏曲线和最大回撤等        
        timeList = []           # 时间序列
        pnlList = []            # 每笔盈亏序列
        capital = 0             # 资金
        maxCapital = 0          # 资金最高净值
        drawdown = 0            # 回撤

        totalResult = 0         # 总成交数量
        totalTurnover = 0       # 总成交金额(合约面值)
        totalCommission = 0     # 总手续费
        totalSlippage = 0       # 总滑点

        capitalList = []        # 盈亏汇总的时间序列
        drawdownList = []       # 回撤的时间序列

        winningResult = 0       # 盈利次数
        losingResult = 0        # 亏损次数      
        totalWinning = 0        # 总盈利金额     
        totalLosing = 0         # 总亏损金额        

        for result in resultList:
            capital += result.pnl
            maxCapital = max(capital+result.posPnl, maxCapital)
            drawdown = round(capital+result.posPnl-maxCapital,2)

            pnlList.append(result.pnl)
            timeList.append(result.exitDt)      # 交易的时间戳使用平仓时间
            capitalList.append(capital+result.posPnl)
            drawdownList.append(drawdown)

            totalResult += 1
            totalTurnover += result.turnover
            totalCommission += result.commission
            totalSlippage += result.slippage

            if result.pnl >= 0:
                winningResult += 1
                totalWinning += result.pnl
            else:
                losingResult += 1
                totalLosing += result.pnl

        # 计算盈亏相关数据
    if totalResult:
            winningRate = winningResult*1.0/totalResult*100         # 胜率
    else:
            winningRate = 0

        averageWinning = 0                                  # 这里把数据都初始化为0
        averageLosing = 0
        profitLossRatio = 0

        if winningResult:
            averageWinning = totalWinning/winningResult     # 平均每笔盈利
    else:
            averageWinning = 0

        if losingResult:
            averageLosing = totalLosing/losingResult        # 平均每笔亏损
    else:
            averageLosing = 0

        if averageLosing:
            profitLossRatio = -averageWinning/averageLosing # 盈亏比
    else:
            profitLossRatio = 0

        # 返回回测结果
        d = {}
        d['capital'] = capital
        d['maxCapital'] = maxCapital
        d['drawdown'] = drawdown
        d['totalResult'] = totalResult
        d['totalTurnover'] = totalTurnover
        d['totalCommission'] = totalCommission
        d['totalSlippage'] = totalSlippage
        d['timeList'] = timeList
        d['pnlList'] = pnlList
        d['capitalList'] = capitalList
        d['drawdownList'] = drawdownList
        d['winningRate'] = winningRate
        d['averageWinning'] = averageWinning
        d['averageLosing'] = averageLosing
        d['profitLossRatio'] = profitLossRatio
        d['resList'] = resList

        return d

    #----------------------------------------------------------------------
    def calcPosPNL(self,tradeID,shortTrade,longTrade,shortTrade1,longTrade1,leg2):
        """
        根据市场快照,计算每笔成交时间的持仓盈亏(按对价结算并扣除了手续费和滑点)
        """
        # 判断是否有持仓,加快无持仓策略的计算速度
        return 0
        allPos0 = len(shortTrade)+len(longTrade)
        if allPos0 == 0:
            return 0
        pnlDict = OrderedDict()             # 每笔盈亏的记录 
        if tradeID in self.tradeSnap: 
            tick = self.tradeSnap[tradeID]      # 主合约行情
            tick1 = self.tradeSnap1[tradeID]    # 副合约行情
        elif tradeID in self.trade1Snap: 
            tick = self.trade1Snap[tradeID]      # 主合约行情
            tick1 = self.trade1Snap1[tradeID]    # 副合约行情
        else:
            tick = self.tradeSnap[tradeID]      # 主合约行情
            tick1 = self.tradeSnap1[tradeID]    # 副合约行情
        for entryTrade in shortTrade:
        volume = entryTrade.volume
        exitTime = tick.datetime
        exitPrice = tick.askPrice1

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate, self.slippage, self.size)
        elif leg2:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate, self.slippage, self.size)
        for entryTrade in longTrade:
        volume = entryTrade.volume
        exitTime = tick.datetime
        exitPrice = tick.bidPrice1

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate, self.slippage, self.size)
        elif leg2:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate, self.slippage, self.size)
        for entryTrade in shortTrade1:
        volume = entryTrade.volume
        exitTime = tick1.datetime
        exitPrice = tick1.askPrice1

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate1, self.slippage1, self.size1)
        else:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate1, self.slippage1, self.size1)
        for entryTrade in longTrade1:
        volume = entryTrade.volume
        exitTime = tick1.datetime
        exitPrice = tick1.bidPrice1

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate1, self.slippage1, self.size1)
        else:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate1, self.slippage1, self.size1)
        result = 0
        for v in pnlDict.values():
            result += v.pnl
        return result

    #----------------------------------------------------------------------
    def calcPosPNL1(self,tradeID,shortTrade,longTrade,shortTrade1,longTrade1,leg2):
        """
        根据市场快照,计算每笔成交时间的持仓盈亏(按对价结算并扣除了手续费和滑点)
        """
        return 0
        # 判断是否有持仓,加快无持仓策略的计算速度
        allPos1 = len(shortTrade1)+len(longTrade1)
        if allPos1 == 0:
            return 0
        pnlDict = OrderedDict()             # 每笔盈亏的记录 
        if tradeID in self.trade1Snap:
            tick = self.trade1Snap[tradeID]      # 主合约行情
            tick1 = self.trade1Snap1[tradeID]    # 副合约行情
        elif tradeID in self.tradeSnap:
            tick = self.tradeSnap[tradeID]      # 主合约行情
            tick1 = self.tradeSnap1[tradeID]    # 副合约行情
        else:
            return 0
        for entryTrade in shortTrade:
        volume = entryTrade.volume
        exitTime = tick.datetime
        exitPrice = tick.askPrice1

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate, self.slippage, self.size)
        elif leg2:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate, self.slippage, self.size)
        for entryTrade in longTrade:
        volume = entryTrade.volume
        exitTime = tick.datetime
        exitPrice = tick.bidPrice1

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate, self.slippage, self.size)
        elif leg2:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate, self.slippage, self.size)
        for entryTrade in shortTrade1:
        volume = entryTrade.volume
        exitTime = tick1.datetime
        exitPrice = tick1.askPrice1

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate1, self.slippage1, self.size1)
        else:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       -volume, self.rate1, self.slippage1, self.size1)
        for entryTrade in longTrade1:
        volume = entryTrade.volume
        exitTime = tick1.datetime
        exitPrice = tick1.bidPrice1

        if exitTime not in pnlDict:
                pnlDict[exitTime] = TradingResult(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate1, self.slippage1, self.size1)
        else:
                pnlDict[exitTime].add(entryTrade.price, entryTrade.dt, 
                                       exitPrice, exitTime,
                                       volume, self.rate1, self.slippage1, self.size1)
        result = 0
        for v in pnlDict.values():
            result += v.pnl
        return result

    #----------------------------------------------------------------------
    def showBacktestingResult(self):
        """
        显示回测结果
        """
    d = self.calculateBacktestingResult()
    timeList = d['timeList']
    pnlList = d['pnlList']
    capitalList = d['capitalList']
    drawdownList = d['drawdownList']
        resList = d['resList']

        self.output(u'显示回测结果')
        # 输出
    if len(resList)>1:
        import codecs
        settingFileName = self.strategy.name+'_DT_setting.json'
        if os.path.exists(os.getcwd() + '\\..\\dataViewer\\data\\'):
            settingFileName = os.getcwd() + '\\..\\dataViewer\\data\\' + settingFileName
        else:
            settingFileName = os.getcwd() + '\\dataViewer\\data\\' + settingFileName
        f = codecs.open(settingFileName,'w', 'utf-8')
        f.write(json.dumps(resList,indent=1,ensure_ascii=False)) 
        f.close()
    if len(timeList)>0:
        self.output('-' * 30)
            self.output(u'第一笔交易:\t%s' % d['timeList'][0])
            self.output(u'最后一笔交易:\t%s' % d['timeList'][-1])

            self.output(u'总交易次数:\t%s' % formatNumber(d['totalResult']))        
            self.output(u'总盈亏:\t%s' % formatNumber(d['capital']))
            self.output(u'最大回撤: \t%s' % formatNumber(min(d['drawdownList'])))                

            self.output(u'平均每笔盈利:\t%s' %formatNumber(d['capital']/d['totalResult']))
            self.output(u'平均每笔滑点:\t%s' %formatNumber(d['totalSlippage']/d['totalResult']))
            self.output(u'平均每笔佣金:\t%s' %formatNumber(d['totalCommission']/d['totalResult']))

            self.output(u'胜率\t\t%s%%' %formatNumber(d['winningRate']))
            self.output(u'平均每笔盈利\t%s' %formatNumber(d['averageWinning']))
            self.output(u'平均每笔亏损\t%s' %formatNumber(d['averageLosing']))
            self.output(u'盈亏比:\t%s' %formatNumber(d['profitLossRatio']))

                # 资金曲线插入数据库
                lastTime = None
                lastCap = 0
                lastDayCap = 0
                lastDraw = 0
                for (time,cap,drawdown) in zip(timeList,capitalList,drawdownList):
                    if lastTime and time.day != lastTime.day: 
                        capData = CtaCapData()
                        capData.name = self.strategy.name
                        capData.datetime = lastTime
                        capData.start = self.dataStartDate
                        capData.date = capData.datetime.replace(hour =0,minute\
                                =0,second = 0,microsecond = 0)
                        capData.cap = lastCap
                        capData.pnl = lastCap - lastDayCap
                        capData.drawdown = lastDraw
                        self.insertCap(CAPITAL_DB_NAME,self.strategy.name,capData)
                        lastDayCap = lastCap
                    lastTime = time
                    lastCap = cap
                    lastDraw = drawdown

                capData = CtaCapData()
                capData.name = self.strategy.name
                capData.datetime = lastTime
                capData.start = self.dataStartDate
                capData.date = capData.datetime.replace(hour =0,minute\
                        =0,second = 0,microsecond = 0)
                capData.cap = lastCap
                capData.pnl = lastCap - lastDayCap
                capData.drawdown = lastDraw
                self.insertCap(CAPITAL_DB_NAME,self.strategy.name,capData)

            # 绘图
            import matplotlib.pyplot as plt
        from matplotlib.dates import AutoDateLocator, DateFormatter  
                plt.close()
        autodates = AutoDateLocator()  
        yearsFmt = DateFormatter('%m-%d')  
        #yearsFmt = DateFormatter('%Y-%m-%d')  


            pCapital = plt.subplot(3, 1, 1)
            pCapital.set_ylabel("capital")
            pCapital.plot(timeList,capitalList)
                plt.title(self.strategy.name)

        plt.gcf().autofmt_xdate()              #设置x轴时间外观  
        plt.gcf().subplots_adjust(bottom=0.1)
        plt.gca().xaxis.set_major_locator(autodates)       #设置时间间隔  
        plt.gca().xaxis.set_major_formatter(yearsFmt)      #设置时间显示格式  

            pDD = plt.subplot(3, 1, 2)
            pDD.set_ylabel("DD")
            pDD.bar(range(len(drawdownList)), drawdownList)         

            pPnl = plt.subplot(3, 1, 3)
            pPnl.set_ylabel("pnl")
            pPnl.hist(pnlList, bins=20)

            plt.show()

    #----------------------------------------------------------------------
    def insertCap(self, dbName, collectionName, d):
        """插入数据到数据库(这里的data可以是CtaTickData或者CtaBarData)"""
        host, port = loadMongoSetting()
    if not self.dbClient:
            self.dbClient = pymongo.MongoClient(host, port, socketKeepAlive=True)
        db = self.dbClient[dbName]
        collection = db[collectionName]
    collection.ensure_index([('date', pymongo.ASCENDING)], unique=True)   
    flt = {'date': d.date}
        collection.update_one(flt, {'$set':d.__dict__}, upsert=True)  

    #----------------------------------------------------------------------
    def showBacktestingResult_nograph(self,filepath):
        """
        显示回测结果
        """
    d = self.calculateBacktestingResult()
    timeList = d['timeList']
    pnlList = d['pnlList']
    capitalList = d['capitalList']
    drawdownList = d['drawdownList']

        self.output(u'显示回测结果')
        # 输出
    if len(timeList)>0:
            self.output('-' * 30)
            self.output(u'第一笔交易:\t%s' % d['timeList'][0])
            self.output(u'最后一笔交易:\t%s' % d['timeList'][-1])

            self.output(u'总交易次数:\t%s' % formatNumber(d['totalResult']))        
            self.output(u'总盈亏:\t%s' % formatNumber(d['capital']))
            self.output(u'最大回撤: \t%s' % formatNumber(min(d['drawdownList'])))                

            self.output(u'平均每笔盈利:\t%s' %formatNumber(d['capital']/d['totalResult']))
            self.output(u'平均每笔滑点:\t%s' %formatNumber(d['totalSlippage']/d['totalResult']))
            self.output(u'平均每笔佣金:\t%s' %formatNumber(d['totalCommission']/d['totalResult']))

            self.output(u'胜率\t\t%s%%' %formatNumber(d['winningRate']))
            self.output(u'平均每笔盈利\t%s' %formatNumber(d['averageWinning']))
            self.output(u'平均每笔亏损\t%s' %formatNumber(d['averageLosing']))
            self.output(u'盈亏比:\t%s' %formatNumber(d['profitLossRatio']))
            self.output(u'显示回测结果')

                # 资金曲线插入数据库
                lastTime = None
                lastCap = 0
                lastDayCap = 0
                lastDraw = 0
                for (time,cap,drawdown) in zip(timeList,capitalList,drawdownList):
                    if lastTime and time.day != lastTime.day: 
                        capData = CtaCapData()
                        capData.name = self.strategy.name
                        capData.datetime = lastTime
                        capData.start = self.dataStartDate
                        capData.date = capData.datetime.replace(hour =0,minute\
                                =0,second = 0,microsecond = 0)
                        capData.cap = lastCap
                        capData.pnl = lastCap - lastDayCap
                        capData.drawdown = lastDraw
                        self.insertCap(CAPITAL_DB_NAME,self.strategy.name,capData)
                        lastDayCap = lastCap
                    lastTime = time
                    lastCap = cap
                    lastDraw = drawdown

        # 绘图
                import matplotlib
                matplotlib.use('Qt4Agg')
        import matplotlib.pyplot as plt
        from matplotlib.dates import AutoDateLocator, DateFormatter  
        autodates = AutoDateLocator()  
        yearsFmt = DateFormatter('%m-%d')  

            pCapital = plt.subplot(3, 1, 1)
            pCapital.set_ylabel("capital")
            pCapital.plot(timeList,capitalList)
        plt.gcf().autofmt_xdate()        #设置x轴时间外观  
        plt.gcf().subplots_adjust(bottom=0.1)
        plt.gca().xaxis.set_major_locator(autodates)       #设置时间间隔  
        plt.gca().xaxis.set_major_formatter(yearsFmt)      #设置时间显示格式  

            pDD = plt.subplot(3, 1, 2)
            pDD.set_ylabel("DD")
            pDD.bar(range(len(drawdownList)), drawdownList)         

            pPnl = plt.subplot(3, 1, 3)
            pPnl.set_ylabel("pnl")
            pPnl.hist(pnlList, bins=20)

            plt.savefig(filepath)
        plt.close()

    #----------------------------------------------------------------------
    def putStrategyEvent(self, name):
        """发送策略更新事件,回测中忽略"""
        pass

    #----------------------------------------------------------------------
    def confSettle(self, name):
        """确认结算单,回测中忽略"""
        pass

    #----------------------------------------------------------------------
    def setSlippage(self, slippage):
        """设置滑点"""
        self.slippage = slippage
        self.slippage1 = slippage

    #----------------------------------------------------------------------
    def setSize(self, size):
        """设置合约大小"""
        self.size = size

    #----------------------------------------------------------------------
    def setSize1(self, size):
        """设置合约大小"""
        self.size1 = size

    #----------------------------------------------------------------------
    def setRate(self, rate):
        """设置佣金比例"""
        self.rate = rate

    #----------------------------------------------------------------------
    def setRate1(self, rate):
        """设置佣金比例"""
        self.rate1 = rate

    #----------------------------------------------------------------------
    def setLeverage(self, leverage):
        """设置杠杆比率"""
        self.leverage = leverage

    #----------------------------------------------------------------------
    def setPrice(self, price):
        """设置合约大小"""
        self.mPrice = price

    #----------------------------------------------------------------------
    def setPrice1(self, price):
        """设置合约大小"""
        self.mPrice1 = price

    #----------------------------------------------------------------------
    def loadTick(self, dbName, collectionName, days):
        """从数据库中读取Tick数据,startDate是datetime对象"""
        startDate = datetime.now()

        d = {'datetime':{'$lte':startDate}}
    host, port = loadMongoSetting()
    client = pymongo.MongoClient(host,port)
    collection = client[dbName][collectionName]

        cursor = collection.find(d).limit(days*10*60*120)

        l = []
        if cursor:
            for d in cursor:
                tick = CtaTickData()
                tick.__dict__ = d
                l.append(tick)

        return l    

    #----------------------------------------------------------------------
    def loadBar(self, dbName, collectionName, days):
        """从数据库中读取Tick数据,startDate是datetime对象"""
        startDate = datetime.now()

        d = {'datetime':{'$lte':startDate}}
    host, port = loadMongoSetting()
    client = pymongo.MongoClient(host,port)
    collection = client[dbName][collectionName]

        cursor = collection.find(d).limit(days*10*60)

        l = []
        if cursor:
            for d in cursor:
                bar = CtaBarData()
                bar.__dict__ = d
                l.append(bar)

        return l    

    #----------------------------------------------------------------------
    def runOptimization(self, strategyClass, setting_c, optimizationSetting):
        """串行优化"""
        # 获取优化设置        
        settingList = optimizationSetting.generateSetting()
        targetName = optimizationSetting.optimizeTarget

        # 检查参数设置问题
        if not settingList or not targetName:
            self.output(u'优化设置有问题,请检查')
        vtSymbol = setting_c['vtSymbol']
    if 'vtSymbol1' in setting_c: 
        vtSymbol1 = setting_c['vtSymbol1']
    else:
        vtSymbol1 = None
        # 遍历优化
        resultList = []
        opResults = []
        for setting in settingList:
            self.clearBacktestingResult()
        self.loadHistoryData(TICK_DB_NAME, vtSymbol)
            if vtSymbol1:
        self.loadHistoryData1(TICK_DB_NAME, vtSymbol1)
            self.output('-' * 30)
            self.output('setting: %s' %str(setting))
            self.initStrategy(strategyClass, setting_c)
            self.strategy.onUpdate(setting)
            self.runBacktesting()
        opResult = {}
            d = self.calculateBacktestingResult()
        for key in setting:
        opResult[key] = setting[key]        
        opResult['totalResult']=d['totalResult']        
            opResult['capital']=d['capital']
        if d['totalResult'] > 0:
                opResult['maxDrawdown']=min(d['drawdownList'])                
                opResult['winPerT']=d['capital']/d['totalResult']
                opResult['splipPerT']=d['totalSlippage']/d['totalResult']
                opResult['commiPerT']=d['totalCommission']/d['totalResult']
        else:
                opResult['maxDrawdown']=0
                opResult['winPerT']=0
                opResult['splipPerT']=0
                opResult['commiPerT']=0
            opResult['winningRate']=d['winningRate']
            opResult['averageWinning']=d['averageWinning']
            opResult['averageLosing']=d['averageLosing']
            opResult['profitLossRatio']=d['profitLossRatio']
            try:
                targetValue = d[targetName]
            except KeyError:
                targetValue = 0
            resultList.append(([str(setting)], targetValue))
            opResults.append(opResult)

        # 显示结果
    if os.path.exists('.\\ctaStrategy\\opResults\\'):
        filepath = '.\\ctaStrategy\\opResults\\'
    else:
        filepath = '.\\opResults\\'
    with open(filepath+self.strategy.name+'.csv','wb') as csvfile:
        fieldnames = opResult.keys()
        writer = csv.DictWriter(csvfile,fieldnames)
        writer.writeheader()
        for opDict in opResults:
        writer.writerow(opDict)
        resultList.sort(reverse=True, key=lambda result:result[1])
        self.output('-' * 30)
        self.output(u'优化结果:')
        for result in resultList:
            self.output(u'%s: %s' %(result[0], result[1]))
        return result

    #----------------------------------------------------------------------
    def clearBacktestingResult(self):
        """清空之前回测的结果"""
    # 交易行情相关
    self.dt = None
    self.backtestingData = deque([])
    self.backtestingData1 = deque([])
    self.tick = None
    self.tick1 = None
    self.bar = None
    self.bar1 = None
    self.lasttick = None
    self.lasttick1 = None


        self.logList = []               # 日志记录

        # 清空限价单相关
        self.limitOrderCount = 0
        self.limitOrderDict.clear()
        self.limitOrderDict1.clear()
        self.workingLimitOrderDict.clear()        
        self.workingLimitOrderDict1.clear()        
    self.orderPrice = {}        # 限价单价格
    self.orderVolume = {}       # 限价单盘口
    self.orderPrice1 = {}       # 限价单价格
    self.orderVolume1 = {}      # 限价单盘口

        # 清空停止单相关
        self.stopOrderCount = 0
        self.stopOrderDict.clear()
        self.workingStopOrderDict.clear()

        # 清空成交相关
        self.tradeCount = 0
        self.tradeDict.clear()
        self.tradeSnap.clear()
        self.tradeSnap1.clear()
        self.tradeCount1 = 0
        self.tradeDict1.clear()
        self.trade1Snap.clear()
        self.trade1Snap1.clear()


########################################################################
class TradingResult(object):
    """每笔交易的结果"""

    #----------------------------------------------------------------------
    def __init__(self, entryPrice, entryDt, exitPrice, 
                 exitDt, volume, rate, slippage, size):
        """Constructor"""
        self.entryPrice = entryPrice    # 开仓价格
        self.exitPrice = exitPrice      # 平仓价格

        self.entryDt = entryDt          # 开仓时间
        self.exitDt = exitDt            # 平仓时间

        self.volume = volume    # 交易数量(+/-代表方向)

        self.turnover = (self.entryPrice+self.exitPrice)*size*abs(volume)   # 成交金额
        self.commission = self.turnover*rate                                # 手续费成本
        self.slippage = slippage*2*size*abs(volume)                         # 滑点成本
        self.pnl = ((self.exitPrice - self.entryPrice) * volume * size 
                    - self.commission - self.slippage)                      # 净盈亏
        self.posPnl = 0                                                     # 当时持仓盈亏

    #----------------------------------------------------------------------
    def add(self, entryPrice, entryDt, exitPrice, 
                 exitDt, volume, rate, slippage, size):
        """Constructor"""
        self.entryPrice = entryPrice    # 开仓价格
        self.exitPrice = exitPrice      # 平仓价格

        self.entryDt = entryDt          # 开仓时间datetime    
        self.exitDt = exitDt            # 平仓时间

        self.volume += volume    # 交易数量(+/-代表方向)

    turnover = (self.entryPrice+self.exitPrice)*size*abs(volume)   
        self.turnover += turnover                      # 成交金额
    commission = turnover*rate
        self.commission += commission                                # 手续费成本
        slippage0 = slippage*2*size*abs(volume)                         
        self.slippage += slippage0                  # 滑点成本
        self.pnl += ((self.exitPrice - self.entryPrice) * volume * size 
                    - commission - slippage0)                      # 净盈亏


########################################################################
class OptimizationSetting(object):
    """优化设置"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.paramDict = OrderedDict()

        self.optimizeTarget = ''        # 优化目标字段

    #----------------------------------------------------------------------
    def addParameter(self, name, start, end, step):
        """增加优化参数"""
        if end <= start:
            print u'参数起始点必须小于终止点'
            return

        if step <= 0:
            print u'参数步进必须大于0'
            return

        l = []
        param = start

        while param <= end:
            l.append(param)
            param += step

        self.paramDict[name] = l

    #----------------------------------------------------------------------
    def generateSetting(self):
        """生成优化参数组合"""
        # 参数名的列表
        nameList = self.paramDict.keys()
        paramList = self.paramDict.values()

        # 使用迭代工具生产参数对组合
        productList = list(product(*paramList))

        # 把参数对组合打包到一个个字典组成的列表中
        settingList = []
        for p in productList:
            d = dict(zip(nameList, p))
            settingList.append(d)

        return settingList

    #----------------------------------------------------------------------
    def setOptimizeTarget(self, target):
        """设置优化目标字段"""
        self.optimizeTarget = target

#---------------------------------------------------------------------------------------
def backtesting(setting_c, StartTime = '', EndTime = '', slippage = 0, optimism = False, mode = 'T'):
    """读取策略配置"""
    setting_c[u'backtesting'] = True
    import sys
    import re
    from ctaSetting import STRATEGY_CLASS
    from ctaBacktesting import BacktestingEngine
    vtSymbol = setting_c[u'vtSymbol']
    if u'vtSymbol1' in setting_c:
        vtSymbol1 = setting_c[u'vtSymbol1']
    else:
        vtSymbol1 = None
    className = setting_c[u'className']
    with open("CTA_v_setting.json") as f:
        l = json.load(f)
        for setting in l:
             name = setting[u'name']
             match = re.search('^'+name+'[0-9]',vtSymbol)
             if match:
                rate = setting[u'mRate']
                price = setting[u'mPrice']
                size = setting[u'mSize']
                level = setting[u'mLevel']
             if vtSymbol1:
                match = re.search('^'+name+'[0-9]',vtSymbol1)
                if match:
                rate1 = setting[u'mRate']
                price1 = setting[u'mPrice']
                size1 = setting[u'mSize']
                level1 = setting[u'mLevel']

    output_s=sys.stdout
    sys.stderr = output_s
    engine=BacktestingEngine()
    engine.optimism = optimism
    # 设置引擎的回测模式为TICK
    if mode == 'T':
        engine.setBacktestingMode(engine.TICK_MODE)
        dbName = TICK_DB_NAME
    elif mode == 'B':
        engine.setBacktestingMode(engine.BAR_MODE)
        dbName = MINUTE_DB_NAME
    elif mode == 'D':
        engine.setBacktestingMode(engine.BAR_MODE)
        dbName = DAILY_DB_NAME

    if not StartTime:
        StartTime = str(setting_c[u'StartTime'])
    if not EndTime:
        EndTime = str(setting_c[u'EndTime'])


    # 设置回测用的数据起始日期
    engine.setStartDate(StartTime,1)
    engine.setEndDate(EndTime)

    # 载入历史数据到引擎中
    engine.loadHistoryData(dbName, vtSymbol)
    if vtSymbol1:
        engine.loadHistoryData1(dbName, vtSymbol1)

    # 设置产品相关参数
    engine.setSlippage(slippage)     # 滑点
    engine.setRate(rate)   # 万1.1
    engine.setSize(size)         # 合约大小    
    engine.setPrice(price)        # 最小价格变动    
    if vtSymbol1:
        engine.setRate1(rate1)   # 万1.1
        engine.setSize1(size1)         # 合约大小    
        engine.setPrice1(price1)       # 最小价格变动    
    else:
        engine.setRate1(rate)   # 万1.1
        engine.setSize1(size)         # 合约大小    
        engine.setPrice1(price)       # 最小价格变动    
    engine.setLeverage(level)         # 合约杠杆    
    engine.initStrategy(STRATEGY_CLASS[className],setting_c)
    engine.runBacktesting()
    engine.showBacktestingResult()
    sys.stdout=output_s

#----------------------------------------------------------------------
def runParallelOptimization(setting_c, optimizationSetting, optimism=False, startTime='', endTime='', slippage=0,mode='T'):
    """并行优化参数"""
    # 获取优化设置        
    global p
    global currentP 
    print(u'开始优化策略 : '+setting_c['name'])
    print(u' ')
    settingList = optimizationSetting.generateSetting()
    print(u'总共'+str(len(settingList))+u'个优化')
    targetName = optimizationSetting.optimizeTarget
    p = ProgressBar(maxval=len(settingList))
    p.start()
    currentP=0
    # 检查参数设置问题
    if not settingList or not targetName:
        print(u'优化设置有问题,请检查')

    # 多进程优化,启动一个对应CPU核心数量的进程池
    pool = multiprocessing.Pool(processes=multiprocessing.cpu_count()-1)
    l = []
    for setting in settingList:
        l.append(pool.apply_async(optimize, args=(setting_c,setting,targetName,optimism,startTime,endTime,slippage,mode), callback=showProcessBar))
    pool.close()
    pool.join()
    p.finish()

    # 显示结果
    resultList = [res.get() for res in l]
    print('-' * 30)
    print(u'优化结果:')
    if os.path.exists('.\\ctaStrategy\\opResults\\'):
        filepath = '.\\ctaStrategy\\opResults\\'
    else:
        filepath = '.\\opResults\\'
    with open(filepath+setting_c['name']+'.csv','wb') as csvfile:
        fieldnames = resultList[0][1].keys()
    fieldnames.sort()
        writer = csv.DictWriter(csvfile,fieldnames)
        writer.writeheader()
    setting_t = {}
    value_t = -99999
        for (setting,opDict) in resultList:
            writer.writerow(opDict)
        if opDict[targetName] > value_t:
        setting_t = setting 
        value_t = opDict[targetName]
        print(str(setting_t)+':'+str(value_t))    
    print(u'优化结束')
    print(u' ')

#----------------------------------------------------------------------
def showProcessBar(result):
    """显示进度条"""
    global p
    global currentP 
    currentP+=1
    p.update(currentP)

#----------------------------------------------------------------------
def getSetting(name):
    """获取策略基础配置"""
    setting_c = {}
    settingFileName = '.\\json\\CTA_setting.json'
    with open(settingFileName) as f:
        l = json.load(f)
        for setting in l:
        if setting['name'] == name:
        setting_c = setting
    setting_c[u'backtesting'] = True
    return setting_c

#----------------------------------------------------------------------
def formatNumber(n):
    """格式化数字到字符串"""
    rn = round(n, 2)        # 保留两位小数
    return format(rn, ',')  # 加上千分符

#----------------------------------------------------------------------
def optimize(setting_c, setting, targetName, optimism, startTime='', endTime='', slippage=0, mode = 'T'):
    """多进程优化时跑在每个进程中运行的函数"""
    setting_c[u'backtesting'] = True
    import re
    from ctaSetting import STRATEGY_CLASS
    from ctaBacktesting import BacktestingEngine
    vtSymbol = setting_c[u'vtSymbol']
    if u'vtSymbol1' in setting_c:
    vtSymbol1 = setting_c[u'vtSymbol1']
    else:
    vtSymbol1 = None
    className = setting_c[u'className']
    if os.path.exists("CTA_v_setting.json"):
    fileName = "CTA_v_setting.json"
    else:
    fileName = "..\\CTA_v_setting.json"
    with open(fileName) as f:
        l = json.load(f)
        for setting_x in l:
         name = setting_x[u'name']
         match = re.search('^'+name+'[0-9]',vtSymbol)
         if match:
        rate = setting_x[u'mRate']
        price = setting_x[u'mPrice']
        size = setting_x[u'mSize']
        level = setting_x[u'mLevel']
         if vtSymbol1:
        match = re.search('^'+name+'[0-9]',vtSymbol1)
        if match:
            rate1 = setting_x[u'mRate']
            price1 = setting_x[u'mPrice']
            size1 = setting_x[u'mSize']
            level1 = setting_x[u'mLevel']

    name = setting_c[u'name']
    engine=BacktestingEngine()
    #engine.plot = False
    #engine.fast = True 
    engine.plot = True
    engine.optimism = optimism
    # 设置引擎的回测模式
    if mode=='T':
        engine.setBacktestingMode(engine.TICK_MODE)
        dbName = TICK_DB_NAME
    elif mode=='B':
        engine.setBacktestingMode(engine.BAR_MODE)
        dbName = MINUTE_DB_NAME
    elif mode=='D':
        engine.setBacktestingMode(engine.BAR_MODE)
        dbName = DAILY_DB_NAME

    # 设置回测用的数据起始日期
    if not startTime:
        startTime = str(setting_c[u'StartTime'])
    if not endTime:
        endTime = str(setting_c[u'EndTime'])
    engine.setStartDate(startTime,1)
    engine.setEndDate(endTime)
    engine.loadHistoryData(dbName, vtSymbol)
    if vtSymbol1:
        engine.loadHistoryData1(dbName, vtSymbol1)

    # 设置产品相关参数
    engine.setSlippage(slippage)   # 滑点
    engine.setRate(rate)           # 手续费
    engine.setSize(size)           # 合约大小    
    engine.setPrice(price)         # 最小价格变动    
    if vtSymbol1:
    engine.setSize1(size1)         # 合约大小    
    engine.setRate1(rate1)         # 手续费
    engine.setPrice1(price1)       # 最小价格变动    
    else:
    engine.setSize1(size)         # 合约大小    
    engine.setRate1(rate)         # 手续费
    engine.setPrice1(price)       # 最小价格变动    
    engine.setLeverage(level)      # 合约杠杆    
    engine.initStrategy(STRATEGY_CLASS[className], setting_c)
    engine.strategy.onUpdate(setting)
    engine.runBacktesting()
    opResult = {}
    d = engine.calculateBacktestingResult()
    try:
        targetValue = d[targetName]
    except KeyError:
        targetValue = 0            
    for key in setting:
        opResult[key] = setting[key]        
    opResult['totalResult']=d['totalResult']        
    opResult['capital']=round(d['capital'],2)
    if d['totalResult'] > 0:
        opResult['maxDrawdown']=min(d['drawdownList'])                
        opResult['winPerT']=round(d['capital']/d['totalResult'],2)
        opResult['splipPerT']=round(d['totalSlippage']/d['totalResult'],2)
        opResult['commiPerT']=round(d['totalCommission']/d['totalResult'],2)
    else:
        opResult['maxDrawdown']=0
        opResult['winPerT']=0
        opResult['splipPerT']=0
        opResult['commiPerT']=0
    opResult['winningRate']=round(d['winningRate'],2)
    opResult['averageWinning']=round(d['averageWinning'],2)
    opResult['averageLosing']=round(d['averageLosing'],2)
    opResult['profitLossRatio']=round(d['profitLossRatio'],2)
    return (setting,opResult)

if __name__ == '__main__':
    # 建议使用ipython notebook或者spyder来做回测
    """读取策略配置"""
    begin = datetime.now()
    # 回测策略选择
    name = 'tl for rb'

    # 回测模式设置
    opt = False

    # 回测参数设置

    # 策略参数设置
    #optimizationSetting = OptimizationSetting()
    #optimizationSetting.addParameter('wLimit', 3, 6, 1)          

    # 确认检查
    print(u'即将开始优化回测,请确认下面的信息正确后开始回测:')
    print(u'1.回测引擎是正确的稳定版本')
    print(u'2.(乐观\悲观)模式选择正确')
    print(u'3.策略逻辑正确')
    print(u'4.策略参数初始化无遗漏')
    print(u'5.策略参数传递无遗漏')
    print(u'6.策略单次回测交割单检查正确')
    print(u'7.参数扫描区间合理')
    print(u'8.关闭结果文件')
    print(u'y/n:')
    choice = raw_input(u'')
    if not choice == 'y':
        exit(0)

    # 开始回测
    setting_c=getSetting(name)
    runParallelOptimization(setting_c,optimizationSetting,optimism=opt)
    end = datetime.now()
    print(end-begin)
    #outfile.close

策略回测操作


运行run.ipynb文件

首先进入“examples\TurtleStrategy”文件夹,通过Jupyter Notebook中打开run.ipynb可以执行策略回测。
 
1)调用海龟回测引擎
 

%matplotlib inline
from datetime import datetime
import numpy as np
import matplotlib.pyplot as plt
from turtleEngine import BacktestingEngine

2) 设置回测时间区间和起始资金,读取Csv文件的合约信息进行策略回测,然后显示逐日统计的相关指标和资金图,如图所示。
 

engine = BacktestingEngine()
engine.setPeriod(datetime(2014, 1, 1), datetime(2018, 12, 30))
engine.initPortfolio('setting.csv', 10000000)

engine.loadData()
engine.runBacktesting()
engine.showResult()

 

3)针对投资组合里面单个品种,查阅其逐步开平仓记录,如图所示。

tradeList = engine.getTradeData('J99')
for trade in tradeList:
    print '%s %s %s %s@%s' %(trade.vtSymbol, trade.direction, trade.offset,
                             trade.volume, trade.price)

 
 

配置json文件

在运行海龟策略回测会读取在同一文件夹内的Csv文件,下面以setting.csv为例说明一下,如下图

需要配置的合约信息包括:合约品种、合约规模、最小价格变动、手续费率(如每一手0.00003)、固定手续费(如每一手12块钱)、滑点。其中手续费率与固定手续费是二选一关系。以PTA合约为例,其品种信息为TA99,合约规模是5吨,最小价格变动是2元/吨,手续费率为0,固定手续费为12块,滑点为2元。

 
enter image description here
 

(《海龟交易法则》明确表示其交易信号源于期货指数合约,故用“99”结尾的RQData合约进行策略,等挑选完投资组合品种后,再用主连合约(“88”和“888”结尾)测试观察其差异性。)

 
 

原版投资组合测试


原版海龟策略选择标准主要是流动性强品种,若简单地理解为交易所成交量巨大的热门品种
 
根据交易所分类所构建的组合历史回测如图所示(测试环境是无手续费,无滑点),图中显示

  • 上期所热门品种组合夏普比率达到1.01,
  • 郑商所的达到0.8,
  • 大商所的达1.31,
  • 中金所因为只有IF股指期货成交量较高,故中金所只测试了一个品种,其夏普比率达0.88。

 
enter image description here
 

总体来看,原版海龟测试夏普比率都不错,有着一定的稳健性。

因为国内四大交易所其品种包含了金融产品,工业品,农业品,金属,化工等不同品种分类,为了分散投资组合各个头寸的风险,从而提高组合的夏普比率,故海龟策略投资组合品种必须涵盖四大交易所的品种,现在简单的把四大交易所热门品种组合起来进行测试,其效果如图1-14所示。
 
enter image description here
 

新组合的夏普比率达到1.34,要高于上面四个组合,年化收益43.91%,百分比最大回撤达到-29.84%,表现出来高风险高收益的特点,与原版海龟策略基本吻合。

当前品种选择的检验非常顺利,那么就有一个问题:能否在新组合中继续筛选,剔除一下表现不好的品种,去构建一个具有更高夏普比率的组合呢?

答案是否定的。这是一个思维误区,对过去历史表现进行优化,然后筛选出拟合历史行情最优品种,显然没有注意到未来函数的过拟合的问题。

那么,下一章将讲述通过样本内外测试来筛选海龟组合。

作者:张国平 ;来源:维恩的派论坛; vn,py版本:2018年7月版

VNPY中,大多策略都是基于bar分钟级别;国内tick是一秒两笔,频率不算太高。
这里尝试做了一个Tick基本准高频交易策略,只是为了实现思路。可以回测,不要直接用。。

 

回测设置


  • 回测模式改为TICK_MODE,
  • 数据库改为TICK_DB_NAME,
  • setStartDate时候initdays设为0,不需要回读历史天数,只需要当天数据;
  • TICK回测超过一天系统就报错内存不够, 所以最好一天就够。
  • 把currentTime改为开盘时间

 
 

交易信号


  • 入场: 每次读 Tick ,分析过去 10 个 tick 的的总计,如果买量大于卖量,开多单 ;反之空单。(下单价格是当前tick市价)
  • 止损:下单同时开反向2个价位的阻止单;
  • 离场:下次TICK读取时候,如果已经是买入价格正向3个点,再次判断买卖量比,如果已经不符合,市价卖出;如果还是符合原来量比就极小持有,清掉之前阻止单,改挂当前价位反向2个点阻止单。

 

代码如下

 

# encoding: UTF-8

"""


策略逻辑:
入场: 每两秒读一次,分析过去N1个tick的的总计,如果买量大于卖量K倍,且lastprice向上(最后一个值的lastprice大于或等于10个中的N2个tick的lastprice)
空单反之
下单价格是档期tick,bid or ask价格加一个点位的市价单,(止损)同时开反向2个点为的阻止单;
离场:如果已经是买入价格正向N3个点,再吃判断趋势,如果已经不符合,市价卖出。如何持有,清掉之前阻止单,改挂当前价位反向2个点阻止单。

"""

from __future__ import division
from vnpy.trader.vtGateway import *
from datetime import datetime, time
from vnpy.trader.vtObject import VtBarData
from vnpy.trader.vtConstant import EMPTY_STRING
import numpy as np
from vnpy.trader.app.ctaStrategy.ctaTemplate import (CtaTemplate,
                                                     BarGenerator,
                                                     ArrayManager)


########################################################################
class TickOneStrategy(CtaTemplate):
    """基于Tick的交易策略"""
    className = 'TickOneStrategy'
    author = u'BillyZhang'

    # 策略参数
    fixedSize = 1
    Ticksize = 10
    initDays = 0

    DAY_START = time(9, 00)  # 日盘启动和停止时间
    DAY_END = time(14, 58)
    NIGHT_START = time(21, 00)  # 夜盘启动和停止时间
    NIGHT_END = time(22, 58)

    # 策略变量
    posPrice = 0  # 持仓价格
    pos = 0       # 持仓数量


    # 参数列表,保存了参数的名称
    paramList = ['name',
                 'className',
                 'author',
                 'vtSymbol',
                 'initDays',
                 'Ticksize',
                 'fixedSize'
                 ]

    # 变量列表,保存了变量的名称
    varList = ['inited',
               'trading',
               'pos',
               'posPrice'
               ]

    # 同步列表,保存了需要保存到数据库的变量名称
    syncList = ['pos',
                'posPrice',
                'intraTradeHigh',
                'intraTradeLow']

    # ----------------------------------------------------------------------
    def __init__(self, ctaEngine, setting):
        """Constructor"""

        super(TickOneStrategy, self).__init__(ctaEngine, setting)

        #创建Array队列
        self.tickArray = TickArrayManager(self.Ticksize)

    # ----------------------------------------------------------------------
    def onminBarClose(self, bar):
        """"""

        # ----------------------------------------------------------------------

    def onInit(self):
        """初始化策略(必须由用户继承实现)"""
        self.writeCtaLog(u'%s策略初始化' % self.name)
        #tick级别交易,不需要过往历史数据


        self.putEvent()

    # ----------------------------------------------------------------------
    def onStart(self):
        """启动策略(必须由用户继承实现)"""
        self.writeCtaLog(u'%s策略启动' % self.name)
        self.putEvent()

    # ----------------------------------------------------------------------
    def onStop(self):
        """停止策略(必须由用户继承实现)"""
        self.writeCtaLog(u'%s策略停止' % self.name)
        self.putEvent()

    # ----------------------------------------------------------------------
    def onTick(self, tick):
        """收到行情TICK推送(必须由用户继承实现)"""
        # currentTime = datetime.now().time()
        currentTime = time(9,20)
        # 平当日仓位, 如果当前时间是结束前日盘15点28分钟,或者夜盘10点58分钟,如果有持仓,平仓。
        if ((currentTime >= self.DAY_START and currentTime <= self.DAY_END) or
            (currentTime >= self.NIGHT_START and currentTime <= self.NIGHT_END)):
            TA = self.tickArray
            TA.updateTick(tick)
            if not TA.inited:
                return
            if self.pos == 0:
                # 如果空仓,分析过去10个对比,ask卖方多下空单,bid买方多下多单,并防止两个差价阻止单
                if TA.askBidVolumeDif() > 0:
                    self.short(tick.lastPrice, self.fixedSize, False)
                    self.cover(tick.lastPrice + 2,self.fixedSize, True)
                elif TA.askBidVolumeDif() < 0:
                    self.buy(tick.lastPrice, self.fixedSize, False)
                    self.sell(tick.lastPrice - 2, self.fixedSize, True)

            elif self.pos > 0:
                # 如果持有多单,如果已经是买入价格正向N3个点,再次判断趋势,如果已经不符合,市价卖出。如何持有,清掉之前阻止单,改挂当前价位反向2个点阻止单。
                if  tick.lastPrice - self.posPrice >= 3:
                    if TA.askBidVolumeDif() < 0:
                        self.cancelAll()
                        self.sell(tick.lastPrice - 2, self.fixedSize, True)
                    else:
                        self.cancelAll()
                        self.sell(tick.lastPrice, self.fixedSize, False)

            elif self.pos < 0:
                # 如果持有空单,如果已经是买入价格反向N3个点,再次判断趋势,如果已经不符合,市价卖出。如何持有,清掉之前阻止单,改挂当前价位反向2个点阻止单。
                if  tick.lastPrice - self.posPrice <= -3:
                    if TA.askBidVolumeDif() > 0:
                        self.cancelAll()
                        self.cover(tick.lastPrice + 2, self.fixedSize, True)
                    else:
                        self.cancelAll()
                        self.cover(tick.lastPrice, self.fixedSize, False)
        else:
            if self.pos > 0:
                self.sell(tick.close, abs(self.pos),False)
            elif self.pos < 0:
                self.cover(tick.close, abs(self.pos),False)
            elif self.pos == 0:
                return





    # ----------------------------------------------------------------------
    def onBar(self, bar):
        """收到Bar推送(必须由用户继承实现)"""


    # ----------------------------------------------------------------------
    def onXminBar(self, bar):
        """收到X分钟K线"""



    # ----------------------------------------------------------------------
    def onOrder(self, order):
        """收到委托变化推送(必须由用户继承实现)"""
        pass

    # ----------------------------------------------------------------------
    def onTrade(self, trade):

        self.posPrice = trade.price
        # 同步数据到数据库
        self.saveSyncData()
        # 发出状态更新事件
        self.putEvent()

    # ----------------------------------------------------------------------
    def onStopOrder(self, so):
        """停止单推送"""
        pass

class TickArrayManager(object):
    """
    Tick序列管理工具,负责:
    1. Tick时间序列的维护
    2. 常用技术指标的计算
    """

    # ----------------------------------------------------------------------
    def __init__(self, size=10):
        """Constructor"""
        self.count = 0  # 缓存计数
        self.size = size  # 缓存大小
        self.inited = False  # True if count>=size

        self.TicklastPriceArray = np.zeros(self.size)
        self.TickaskVolume1Array = np.zeros(self.size)
        self.TickbidVolume1Array = np.zeros(self.size)
        self.TickaskPrice1Array = np.zeros(self.size)
        self.TickbidPrice1Array = np.zeros(self.size)
        self.TickopenInterestArray = np.zeros(self.size)
        self.TickvolumeArray = np.zeros(self.size)

    # ----------------------------------------------------------------------
    def updateTick(self, tick):
        """更新tick Array"""
        self.count += 1
        if not self.inited and self.count >= self.size:
            self.inited = True

        self.TicklastPriceArray[0:self.size - 1] = self.TicklastPriceArray[1:self.size]
        self.TickaskVolume1Array[0:self.size - 1] = self.TickaskVolume1Array[1:self.size]
        self.TickbidVolume1Array[0:self.size - 1] = self.TickbidVolume1Array[1:self.size]
        self.TickaskPrice1Array[0:self.size - 1] = self.TickaskPrice1Array[1:self.size]
        self.TickbidPrice1Array[0:self.size - 1] = self.TickbidPrice1Array[1:self.size]
        self.TickopenInterestArray[0:self.size - 1] = self.TickopenInterestArray[1:self.size]
        self.TickvolumeArray[0:self.size - 1] = self.TickvolumeArray[1:self.size]

        self.TicklastPriceArray[-1] = tick.lastPrice
        self.TickaskVolume1Array[-1] = tick.askVolume1
        self.TickbidVolume1Array[-1] = tick.bidVolume1
        self.TickaskPrice1Array[-1] = tick.askPrice1
        self.TickbidPrice1Array[-1] = tick.bidPrice1
        self.TickopenInterestArray[-1] = tick.openInterest
        self.TickvolumeArray[-1] = tick.volume

    def askBidVolumeDif(self):
        return (self.TickaskVolume1Array.sum() - self.TickbidVolume1Array.sum())

海龟策略7大要素分别是品种选择、头寸规模、单位头寸限制、入场信号、逐步建仓、止损、止盈。由于品种选择无法通过不属于交易策略内容,故只对后面5大要素的进行代码解析。
 

1.头寸规模


头寸规模=(1%账户资金)/(ATR 合约规模),其代码如下

class TurtlePortfolio(object):
......
    #----------------------------------------------------------------------
    def newSignal(self, signal, direction, offset, price, volume):
......
            riskValue = self.portfolioValue * 0.01
            multiplier = riskValue / (signal.atrVolatility * size)
            multiplier = int(round(multiplier, 0))   # multiplier即为头寸规模
            self.multiplierDict[signal.vtSymbol] = multiplier
......
    #----------------------------------------------------------------------
    def sendOrder(self, vtSymbol, direction, offset, price, volume, multiplier):
        """"""
        # 计算合约持仓
        if direction == DIRECTION_LONG:
            self.unitDict[vtSymbol] += volume         # volume即为单位头寸
            self.posDict[vtSymbol] += volume * multiplier
        else:
            self.unitDict[vtSymbol] -= volume
            self.posDict[vtSymbol] -= volume * multiplier  # 实际持仓=单位头寸 * 头寸规模

 
 

2.单位头寸限制


原版海龟策略规定了4个维度的单位头寸限制,分别是

  • 单个市场:头寸上限是4个
  • 高度关联的多个市场:单个方向头寸单位不超过6个
  • 松散关联的多个市场:某一个方向上的头寸单位不超过10个
  • 单个方向:最多12个
     

基于高度关联市场和松散关联市场判断起来都非常主观,并无统一标准。在客观上的层面只能实现单个市场和单个方向的头寸限制。
在开仓交易前,需要检查上一笔交易是否盈利,若是盈利则直接返回,不进行买卖操作(仅仅适用于短周期版本的入场策略,即profitCheck=True;长周期版本对应的是profitCheck=False)

 

MAX_PRODUCT_POS = 4         # 单品种最大持仓
MAX_DIRECTION_POS = 12      # 单方向最大持仓
......
class TurtlePortfolio(object):
......
    #----------------------------------------------------------------------
    def newSignal(self, signal, direction, offset, price, volume):
......
        # 开仓
        if offset == OFFSET_OPEN:
            # 检查上一次是否为盈利
            if signal.profitCheck:
                pnl = signal.getLastPnl()
                if pnl > 0:
                    return

            # 买入
            if direction == DIRECTION_LONG:
                # 组合持仓不能超过上限
                if self.totalLong >= MAX_DIRECTION_POS:
                    return

                # 单品种持仓不能超过上限
                if self.unitDict[signal.vtSymbol] >= MAX_PRODUCT_POS:
                    return
            # 卖出
            else:
                if self.totalShort <= -MAX_DIRECTION_POS:
                    return

                if self.unitDict[signal.vtSymbol] <= -MAX_PRODUCT_POS:
                    return

 
 

3.入场信号、逐步建仓、止损、止盈


原版海龟策略提供2个版本的入场和止盈信号,分别是长周期版本和短周期版本的唐奇安通道突破。

  • 短周期信号:入场用是20日唐奇安通道,止盈用是10日唐奇安通道,用20日周期计算ATR值,有上一笔盈利当前信号无效的过滤条件。
  • 短周期信号:入场用是55日唐奇安通道,止盈用是20日唐奇安通道,用20日周期计算ATR值,无上一笔盈利当前信号无效的过滤条件。

逐步建仓的规则是每隔0.5*ATR幅度慢慢加满至4个单位头寸,止损也相应根据0.5*ATR的步进移动。
 

class TurtleSignal(object):
    #----------------------------------------------------------------------
    def __init__(self, portfolio, vtSymbol, 
                 entryWindow, exitWindow, atrWindow,
                 profitCheck=False):
......
    #----------------------------------------------------------------------
    def onBar(self, bar):
        self.bar = bar
        self.am.bar
        if not self.am.inited:
            return

        self.generateSignal(bar)
        self.calculateIndicator()       
    #----------------------------------------------------------------------
    def generateSignal(self, bar):
        """        
        判断交易信号
        """
        # 如果指标尚未初始化,则忽略
        if not self.longEntry1:
            return

        # 优先检查平仓
        if self.unit > 0:
            longExit = max(self.longStop, self.exitDown)

            if bar.low <= longExit:
                self.sell(longExit)
                return
        elif self.unit < 0:
            shortExit = min(self.shortStop, self.exitUp)
            if bar.high >= shortExit:
                self.cover(shortExit)
                return

        # 没有仓位或者持有多头仓位的时候,可以做多(加仓)
        if self.unit >= 0:
            trade = False

            if bar.high >= self.longEntry1 and self.unit < 1:
                self.buy(self.longEntry1, 1)
                trade = True

            if bar.high >= self.longEntry2 and self.unit < 2:
                self.buy(self.longEntry2, 1)
                trade = True

            if bar.high >= self.longEntry3 and self.unit < 3:
                self.buy(self.longEntry3, 1)
                trade = True

            if bar.high >= self.longEntry4 and self.unit < 4:
                self.buy(self.longEntry4, 1)
                trade = True

            if trade:
                return

        # 没有仓位或者持有空头仓位的时候,可以做空(加仓)
        if self.unit <= 0:
            if bar.low <= self.shortEntry1 and self.unit > -1:
                self.short(self.shortEntry1, 1)

            if bar.low <= self.shortEntry2 and self.unit > -2:
                self.short(self.shortEntry2, 1)

            if bar.low <= self.shortEntry3 and self.unit > -3:
                self.short(self.shortEntry3, 1)

            if bar.low <= self.shortEntry4 and self.unit > -4:
                self.short(self.shortEntry4, 1)

    #----------------------------------------------------------------------
    def calculateIndicator(self):
        """计算技术指标"""
        self.entryUp, self.entryDown = self.am.donchian(self.entryWindow)
        self.exitUp, self.exitDown = self.am.donchian(self.exitWindow)

        # 有持仓后,ATR波动率和入场位等都不再变化
        if not self.unit:
            self.atrVolatility = self.am.atr(self.atrWindow)

            self.longEntry1 = self.entryUp
            self.longEntry2 = self.entryUp + self.atrVolatility * 0.5
            self.longEntry3 = self.entryUp + self.atrVolatility * 1
            self.longEntry4 = self.entryUp + self.atrVolatility * 1.5
            self.longStop = 0

            self.shortEntry1 = self.entryDown
            self.shortEntry2 = self.entryDown - self.atrVolatility * 0.5
            self.shortEntry3 = self.entryDown - self.atrVolatility * 1
            self.shortEntry4 = self.entryDown - self.atrVolatility * 1.5
            self.shortStop = 0

......
class TurtlePortfolio(object):
......
    #----------------------------------------------------------------------
    def init(self, portfolioValue, vtSymbolList, sizeDict):
        """"""
        self.portfolioValue = portfolioValue
        self.sizeDict = sizeDict

        for vtSymbol in vtSymbolList:
            signal1 = TurtleSignal(self, vtSymbol, 20, 10, 20, True)
            signal2 = TurtleSignal(self, vtSymbol, 55, 20, 20, False)

            l = self.signalDict[vtSymbol]
            l.append(signal1)
            l.append(signal2) 
            self.unitDict[vtSymbol] = 0
            self.posDict[vtSymbol] = 0

    #----------------------------------------------------------------------
    def onBar(self, bar):
        """"""
        for signal in self.signalDict[bar.vtSymbol]:
            signal.onBar(bar)

作者: lijiang ;来源:维恩的派论坛 ;应用版本 vn.demo (2016年版本)

实现步骤


  1. 在MainWindow主窗口下,增加价格图的窗口布局。
  2. 添加PriceWidget模块,定义相关参数,变量。
  3. 调用函数self.__connectMongo(),连接数据库;
  4. 初始化界面,并下载历史数据。
  5. 注册事件监听,更新实时数据,onBar()里调用画图函数。
  6. 画图模块,TICK图比较简单,K图是从pyqtgraph里直接copy过来的模块,关键点在p.drawLine。

 

启动vn.demo/ctpdemo/demoMain.py,点击选择系统、登录账号,在代码框敲入合约代码+enter键。
效果图如下:
 
enter image description here

 

代码实现如下:

# encoding: UTF-8

"""
该文件中包含的是交易平台的上层UI部分,
通过图形界面调用中间层的主动函数,并监控相关数据更新。

Monitor主要负责监控数据,有部分包含主动功能。
Widget主要用于调用主动功能,有部分包含数据监控。
"""

from __future__ import division

import time
import sys
import shelve
from collections import OrderedDict

import sip
from PyQt4 import QtCore, QtGui

import pyqtgraph as pg
import numpy as np
from eventEngine import *
from pymongo import MongoClient
from pymongo.errors import *
from datetime import datetime, timedelta


########################################################################
class LogMonitor(QtGui.QTableWidget):
    """用于显示日志"""
    signal = QtCore.pyqtSignal(type(Event()))

    #----------------------------------------------------------------------
    def __init__(self, eventEngine, parent=None):
        """Constructor"""
        super(LogMonitor, self).__init__(parent)
        self.__eventEngine = eventEngine

        self.initUi()
        self.registerEvent()

    #----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""
        self.setWindowTitle(u'日志')

        self.setColumnCount(2)                     
        self.setHorizontalHeaderLabels([u'时间', u'日志'])

        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QtGui.QTableWidget.NoEditTriggers) # 设为不可编辑状态

        # 自动调整列宽
        self.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
        self.horizontalHeader().setResizeMode(1, QtGui.QHeaderView.Stretch)        

    #----------------------------------------------------------------------
    def registerEvent(self):
        """注册事件监听"""
        # Qt图形组件的GUI更新必须使用Signal/Slot机制,否则有可能导致程序崩溃
        # 因此这里先将图形更新函数作为Slot,和信号连接起来
        # 然后将信号的触发函数注册到事件驱动引擎中
        self.signal.connect(self.updateLog)
        self.__eventEngine.register(EVENT_LOG, self.signal.emit)

    #----------------------------------------------------------------------
    def updateLog(self, event):
        """更新日志"""
        # 获取当前时间和日志内容
        t = time.strftime('%H:%M:%S',time.localtime(time.time()))   
        log = event.dict_['log']                                    

        # 在表格最上方插入一行
        self.insertRow(0)              

        # 创建单元格
        cellTime = QtGui.QTableWidgetItem(t)    
        cellLog = QtGui.QTableWidgetItem(log)

        # 将单元格插入表格
        self.setItem(0, 0, cellTime)            
        self.setItem(0, 1, cellLog)


########################################################################
class AccountMonitor(QtGui.QTableWidget):
    """用于显示账户"""
    signal = QtCore.pyqtSignal(type(Event()))

    dictLabels = OrderedDict()
    dictLabels['AccountID'] = u'投资者账户'
    dictLabels['PreBalance'] = u'昨结'
    dictLabels['CloseProfit'] = u'平仓盈亏'
    dictLabels['PositionProfit'] = u'持仓盈亏'
    dictLabels['Commission'] = u'手续费'
    dictLabels['CurrMargin'] = u'当前保证金'
    dictLabels['Balance'] = u'账户资金'
    dictLabels['Available'] = u'可用资金'
    dictLabels['WithdrawQuota'] = u'可取资金'
    dictLabels['FrozenCash'] = u'冻结资金'
    dictLabels['FrozenMargin'] = u'冻结保证金'
    dictLabels['FrozenCommission'] = u'冻结手续费'
    dictLabels['Withdraw'] = u'出金'
    dictLabels['Deposit'] = u'入金'

    #----------------------------------------------------------------------
    def __init__(self, eventEngine, parent=None):
        """Constructor"""
        super(AccountMonitor, self).__init__(parent)
        self.__eventEngine = eventEngine

        self.dictAccount = {}       # 用来保存账户对应的单元格

        self.initUi()
        self.registerEvent()

    #----------------------------------------------------------------------
    def initUi(self):
        """"""
        self.setWindowTitle(u'账户')

        self.setColumnCount(len(self.dictLabels))
        self.setHorizontalHeaderLabels(self.dictLabels.values())

        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QtGui.QTableWidget.NoEditTriggers) # 设为不可编辑状态  

    #----------------------------------------------------------------------
    def registerEvent(self):
        """"""
        self.signal.connect(self.updateAccount)
        self.__eventEngine.register(EVENT_ACCOUNT, self.signal.emit)

    #----------------------------------------------------------------------
    def updateAccount(self, event):
        """"""
        data = event.dict_['data']
        accountid = data['AccountID']

        # 如果之前已经收到过这个账户的数据, 则直接更新
        if accountid in self.dictAccount:
            d = self.dictAccount[accountid]

            for label, cell in d.items():
                cell.setText(str(data[label]))
        # 否则插入新的一行,并更新
        else:
            self.insertRow(0)
            d = {}

            for col, label in enumerate(self.dictLabels.keys()):
                cell = QtGui.QTableWidgetItem(str(data[label]))
                self.setItem(0, col, cell)
                d[label] = cell

            self.dictAccount[accountid] = d


########################################################################
class TradeMonitor(QtGui.QTableWidget):
    """用于显示成交记录"""
    signal = QtCore.pyqtSignal(type(Event()))

    dictLabels = OrderedDict()
    dictLabels['InstrumentID'] = u'合约代码'
    dictLabels['ExchangeID'] = u'交易所'
    dictLabels['Direction'] = u'方向'   
    dictLabels['OffsetFlag'] = u'开平'
    dictLabels['TradeID'] = u'成交编号'
    dictLabels['TradeTime'] = u'成交时间'
    dictLabels['Volume'] = u'数量'
    dictLabels['Price'] = u'价格'
    dictLabels['OrderRef'] = u'报单号'
    dictLabels['OrderSysID'] = u'报单系统号'

    dictDirection = {}
    dictDirection['0'] = u'买'
    dictDirection['1'] = u'        卖'
    dictDirection['2'] = u'ETF申购'
    dictDirection['3'] = u'ETF赎回'
    dictDirection['4'] = u'ETF现金替代'
    dictDirection['5'] = u'债券入库'
    dictDirection['6'] = u'债券出库'
    dictDirection['7'] = u'配股'
    dictDirection['8'] = u'转托管'
    dictDirection['9'] = u'信用账户配股'
    dictDirection['A'] = u'担保品买入'
    dictDirection['B'] = u'担保品卖出'
    dictDirection['C'] = u'担保品转入'
    dictDirection['D'] = u'担保品转出'
    dictDirection['E'] = u'融资买入'
    dictDirection['F'] = u'融资卖出'
    dictDirection['G'] = u'卖券还款'
    dictDirection['H'] = u'买券还券'
    dictDirection['I'] = u'直接还款'
    dictDirection['J'] = u'直接换券'
    dictDirection['K'] = u'余券划转'
    dictDirection['L'] = u'OF申购'    
    dictDirection['M'] = u'OF赎回'
    dictDirection['N'] = u'SF拆分'
    dictDirection['O'] = u'SF合并'
    dictDirection['P'] = u'备兑'
    dictDirection['Q'] = u'证券冻结/解冻'
    dictDirection['R'] = u'行权'      

    dictOffset = {}
    dictOffset['0'] = u'开仓'
    dictOffset['1'] = u'        平仓'
    dictOffset['2'] = u'    强平'
    dictOffset['3'] = u'        平今'
    dictOffset['4'] = u'    平昨'
    dictOffset['5'] = u'    强减'
    dictOffset['6'] = u'    本地强平'

    #----------------------------------------------------------------------
    def __init__(self, eventEngine, parent=None):
        """Constructor"""
        super(TradeMonitor, self).__init__(parent)
        self.__eventEngine = eventEngine

        self.initUi()
        self.registerEvent()    

    #----------------------------------------------------------------------
    def initUi(self):
        """"""
        self.setWindowTitle(u'成交')

        self.setColumnCount(len(self.dictLabels))
        self.setHorizontalHeaderLabels(self.dictLabels.values())

        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QtGui.QTableWidget.NoEditTriggers) # 设为不可编辑状态  

    #----------------------------------------------------------------------
    def registerEvent(self):
        """"""
        self.signal.connect(self.updateTrade)
        self.__eventEngine.register(EVENT_TRADE, self.signal.emit)

    #----------------------------------------------------------------------
    def updateTrade(self, event):
        """"""
        data = event.dict_['data']

        self.insertRow(0)

        for col, label in enumerate(self.dictLabels.keys()):
            if label == 'Direction':
                try:
                    value = self.dictDirection[data[label]]
                except KeyError:
                    value = u'未知类型'
            elif label == 'OffsetFlag':
                try:
                    value = self.dictOffset[data[label]]
                except KeyError:
                    value = u'未知类型'
            else:
                value = str(data[label])

            cell = QtGui.QTableWidgetItem(value)
            self.setItem(0, col, cell)


########################################################################
class PositionMonitor(QtGui.QTableWidget):
    """用于显示持仓"""
    signal = QtCore.pyqtSignal(type(Event()))

    dictLabels = OrderedDict()
    dictLabels['InstrumentID'] = u'合约代码'
    dictLabels['PosiDirection'] = u'方向'  
    dictLabels['Position'] = u'持仓'
    dictLabels['PositionCost'] = u'持仓成本'
    dictLabels['PositionProfit'] = u'持仓盈亏'

    dictPosiDirection = {}
    dictPosiDirection['1'] = u'    净'
    dictPosiDirection['2'] = u'多'
    dictPosiDirection['3'] = u'        空'

    #----------------------------------------------------------------------
    def __init__(self, eventEngine, parent=None):
        """Constructor"""
        super(PositionMonitor, self).__init__(parent)
        self.__eventEngine = eventEngine

        self.dictPosition = {}      # 用来保存持仓对应的单元格

        self.initUi()
        self.registerEvent()

    #----------------------------------------------------------------------
    def initUi(self):
        """"""
        self.setWindowTitle(u'持仓')

        self.setColumnCount(len(self.dictLabels))
        self.setHorizontalHeaderLabels(self.dictLabels.values())

        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QtGui.QTableWidget.NoEditTriggers) # 设为不可编辑状态  

    #----------------------------------------------------------------------
    def registerEvent(self):
        """"""
        self.signal.connect(self.updatePosition)
        self.__eventEngine.register(EVENT_POSITION, self.signal.emit)

    #----------------------------------------------------------------------
    def updatePosition(self, event):
        """"""
        data = event.dict_['data']

        # 过滤返回值为空的情况
        if data['InstrumentID']:
            posid = data['InstrumentID'] + '.' + data['PosiDirection']

            # 如果之前已经收到过这个账户的数据, 则直接更新
            if posid in self.dictPosition:
                d = self.dictPosition[posid]

                for label, cell in d.items():
                    if label == 'PosiDirection':
                        try:
                            value = self.dictPosiDirection[data[label]]
                        except KeyError:
                            value = u'未知类型'
                    else:
                        value = str(data[label])    
                    cell.setText(value)
            # 否则插入新的一行,并更新
            else:
                self.insertRow(0)
                d = {}

                for col, label in enumerate(self.dictLabels.keys()):
                    if label == 'PosiDirection':
                        try:
                            value = self.dictPosiDirection[data[label]]
                        except KeyError:
                            value = u'未知类型'
                    else:
                        value = str(data[label])
                    cell = QtGui.QTableWidgetItem(value)
                    self.setItem(0, col, cell)
                    d[label] = cell

                self.dictPosition[posid] = d


########################################################################
class OrderMonitor(QtGui.QTableWidget):
    """用于显示所有报单"""
    signal = QtCore.pyqtSignal(type(Event()))

    dictLabels = OrderedDict()
    dictLabels['OrderRef'] = u'报单号'
    dictLabels['InsertTime'] = u'委托时间'
    dictLabels['InstrumentID'] = u'合约代码'
    dictLabels['Direction'] = u'方向'
    dictLabels['CombOffsetFlag'] = u'开平'
    dictLabels['LimitPrice'] = u'价格'
    dictLabels['VolumeTotalOriginal'] = u'委托数量'
    dictLabels['VolumeTraded'] = u'成交数量'
    dictLabels['StatusMsg'] = u'状态信息'
    dictLabels['OrderSysID'] = u'系统编号'


    dictDirection = {}
    dictDirection['0'] = u'买'
    dictDirection['1'] = u'       卖'
    dictDirection['2'] = u'ETF申购'
    dictDirection['3'] = u'ETF赎回'
    dictDirection['4'] = u'ETF现金替代'
    dictDirection['5'] = u'债券入库'
    dictDirection['6'] = u'债券出库'
    dictDirection['7'] = u'配股'
    dictDirection['8'] = u'转托管'
    dictDirection['9'] = u'信用账户配股'
    dictDirection['A'] = u'担保品买入'
    dictDirection['B'] = u'担保品卖出'
    dictDirection['C'] = u'担保品转入'
    dictDirection['D'] = u'担保品转出'
    dictDirection['E'] = u'融资买入'
    dictDirection['F'] = u'融资卖出'
    dictDirection['G'] = u'卖券还款'
    dictDirection['H'] = u'买券还券'
    dictDirection['I'] = u'直接还款'
    dictDirection['J'] = u'直接换券'
    dictDirection['K'] = u'余券划转'
    dictDirection['L'] = u'OF申购'    
    dictDirection['M'] = u'OF赎回'
    dictDirection['N'] = u'SF拆分'
    dictDirection['O'] = u'SF合并'
    dictDirection['P'] = u'备兑'
    dictDirection['Q'] = u'证券冻结/解冻'
    dictDirection['R'] = u'行权'          

    dictOffset = {}
    dictOffset['0'] = u'开仓'
    dictOffset['1'] = u'        平仓'
    dictOffset['2'] = u'        强平'
    dictOffset['3'] = u'        平今'
    dictOffset['4'] = u'        平昨'
    dictOffset['5'] = u'        强减'
    dictOffset['6'] = u'        本地强平'

    #----------------------------------------------------------------------
    def __init__(self, eventEngine, mainEngine, parent=None):
        """Constructor"""
        super(OrderMonitor, self).__init__(parent)
        self.__eventEngine = eventEngine
        self.__mainEngine = mainEngine

        self.dictOrder = {}     # 用来保存报单号对应的单元格对象
        self.dictOrderData = {}     # 用来保存报单数据

        self.initUi()
        self.registerEvent()

    #----------------------------------------------------------------------
    def initUi(self):
        """"""
        self.setWindowTitle(u'报单')

        self.setColumnCount(len(self.dictLabels))
        self.setHorizontalHeaderLabels(self.dictLabels.values())

        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QtGui.QTableWidget.NoEditTriggers) # 设为不可编辑状态

    #----------------------------------------------------------------------
    def registerEvent(self):
        """"""
        self.signal.connect(self.updateOrder)
        self.__eventEngine.register(EVENT_ORDER, self.signal.emit)

        self.itemDoubleClicked.connect(self.cancelOrder)

    #----------------------------------------------------------------------
    def updateOrder(self, event):
        """"""
        data = event.dict_['data']
        orderref = data['OrderRef']

        self.dictOrderData[orderref] = data

        # 如果之前已经收到过这个账户的数据, 则直接更新
        if orderref in self.dictOrder:
            d = self.dictOrder[orderref]

            for label, cell in d.items():
                if label == 'Direction':
                    try:
                        value = self.dictDirection[data[label]]
                    except KeyError:
                        value = u'未知类型'
                elif label == 'CombOffsetFlag':
                    try:
                        value = self.dictOffset[data[label]]
                    except KeyError:
                        value = u'未知类型'
                elif label == 'StatusMsg':
                    value = data[label].decode('gbk')
                else:
                    value = str(data[label])    

                cell.setText(value)
        # 否则插入新的一行,并更新
        else:
            self.insertRow(0)
            d = {}

            for col, label in enumerate(self.dictLabels.keys()):
                if label == 'Direction':
                    try:
                        value = self.dictDirection[data[label]]
                    except KeyError:
                        value = u'未知类型'
                elif label == 'CombOffsetFlag':
                    try:
                        value = self.dictOffset[data[label]]
                    except KeyError:
                        value = u'未知类型'
                elif label == 'StatusMsg':
                    value = data[label].decode('gbk')       
                else:
                    value = str(data[label])        

                cell = QtGui.QTableWidgetItem(value)
                self.setItem(0, col, cell)
                d[label] = cell

                cell.orderref = orderref    # 动态绑定报单号到单元格上

            self.dictOrder[orderref] = d

    #----------------------------------------------------------------------
    def cancelOrder(self, cell):
        """双击撤单"""
        orderref = cell.orderref
        order = self.dictOrderData[orderref]

        # 撤单前检查报单是否已经撤销或者全部成交
        if not (order['OrderStatus'] == '0' or order['OrderStatus'] == '5'):
            self.__mainEngine.cancelOrder(order['InstrumentID'],
                                          order['ExchangeID'],
                                          orderref,
                                          order['FrontID'],
                                          order['SessionID'])

    #----------------------------------------------------------------------
    def cancelAll(self):
        """全撤"""
        for order in self.dictOrderData.values():
            if not (order['OrderStatus'] == '0' or order['OrderStatus'] == '5'):
                self.__mainEngine.cancelOrder(order['InstrumentID'],
                                              order['ExchangeID'],
                                              order['OrderRef'],
                                              order['FrontID'],
                                              order['SessionID'])   


########################################################################
class MarketDataMonitor(QtGui.QTableWidget):
    """用于显示行情"""
    signal = QtCore.pyqtSignal(type(Event()))

    dictLabels = OrderedDict()
    dictLabels['Name'] = u'合约名称'
    dictLabels['InstrumentID'] = u'合约代码'
    dictLabels['ExchangeInstID'] = u'合约交易所代码'

    dictLabels['BidPrice1'] = u'买一价'
    dictLabels['BidVolume1'] = u'买一量'
    dictLabels['AskPrice1'] = u'卖一价'
    dictLabels['AskVolume1'] = u'卖一量'    

    dictLabels['LastPrice'] = u'最新价'
    dictLabels['Volume'] = u'成交量'

    dictLabels['UpdateTime'] = u'更新时间'


    #----------------------------------------------------------------------
    def __init__(self, eventEngine, mainEngine, parent=None):
        """Constructor"""
        super(MarketDataMonitor, self).__init__(parent)
        self.__eventEngine = eventEngine
        self.__mainEngine = mainEngine

        self.dictData = {}

        self.initUi()
        self.registerEvent()

    #----------------------------------------------------------------------
    def initUi(self):
        """"""
        self.setWindowTitle(u'行情')

        self.setColumnCount(len(self.dictLabels))
        self.setHorizontalHeaderLabels(self.dictLabels.values())

        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QtGui.QTableWidget.NoEditTriggers) # 设为不可编辑状态

    #----------------------------------------------------------------------
    def registerEvent(self):
        """"""
        self.signal.connect(self.updateData)
        self.__eventEngine.register(EVENT_MARKETDATA, self.signal.emit)

    #----------------------------------------------------------------------
    def updateData(self, event):
        """"""
        data = event.dict_['data']
        instrumentid = data['InstrumentID']

        # 如果之前已经收到过这个账户的数据, 则直接更新
        if instrumentid in self.dictData:
            d = self.dictData[instrumentid]

            for label, cell in d.items():
                if label != 'Name':
                    value = str(data[label])    
                else:
                    value = self.getName(data['InstrumentID'])
                cell.setText(value)
        # 否则插入新的一行,并更新
        else:
            row = self.rowCount()
            self.insertRow(row)
            d = {}

            for col, label in enumerate(self.dictLabels.keys()):
                if label != 'Name':
                    value = str(data[label])                    
                    cell = QtGui.QTableWidgetItem(value)
                    self.setItem(row, col, cell)
                    d[label] = cell
                else:
                    name = self.getName(data['InstrumentID'])               
                    cell = QtGui.QTableWidgetItem(name) 
                    self.setItem(row, col, cell)
                    d[label] = cell

            self.dictData[instrumentid] = d

    #----------------------------------------------------------------------
    def getName(self, instrumentid):
        """获取名称"""
        instrument = self.__mainEngine.selectInstrument(instrumentid)
        if instrument:
            return instrument['InstrumentName'].decode('GBK')
        else:
            return ''


########################################################################
class LoginWidget(QtGui.QDialog):
    """登录"""

    #----------------------------------------------------------------------
    def __init__(self, mainEngine, parent=None):
        """Constructor"""
        super(LoginWidget, self).__init__()
        self.__mainEngine = mainEngine

        self.initUi()
        self.loadData()

    #----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""
        self.setWindowTitle(u'登录')

        # 设置组件
        labelUserID = QtGui.QLabel(u'账号:')
        labelPassword = QtGui.QLabel(u'密码:')
        labelMdAddress = QtGui.QLabel(u'行情服务器:')
        labelTdAddress = QtGui.QLabel(u'交易服务器:')
        labelBrokerID = QtGui.QLabel(u'经纪商代码')

        self.editUserID = QtGui.QLineEdit()
        self.editPassword = QtGui.QLineEdit()
        self.editMdAddress = QtGui.QLineEdit()
        self.editTdAddress = QtGui.QLineEdit()
        self.editBrokerID = QtGui.QLineEdit()

        self.editUserID.setMinimumWidth(200)
        self.editPassword.setEchoMode(QtGui.QLineEdit.Password)

        buttonLogin = QtGui.QPushButton(u'登录')
        buttonCancel = QtGui.QPushButton(u'取消')
        buttonLogin.clicked.connect(self.login)
        buttonCancel.clicked.connect(self.close)

        # 设置布局
        buttonHBox = QtGui.QHBoxLayout()
        buttonHBox.addStretch()
        buttonHBox.addWidget(buttonLogin)
        buttonHBox.addWidget(buttonCancel)

        grid = QtGui.QGridLayout()
        grid.addWidget(labelUserID, 0, 0)
        grid.addWidget(labelPassword, 1, 0)
        grid.addWidget(labelMdAddress, 2, 0)
        grid.addWidget(labelTdAddress, 3, 0)
        grid.addWidget(labelBrokerID, 4, 0)
        grid.addWidget(self.editUserID, 0, 1)
        grid.addWidget(self.editPassword, 1, 1)
        grid.addWidget(self.editMdAddress, 2, 1)
        grid.addWidget(self.editTdAddress, 3, 1)
        grid.addWidget(self.editBrokerID, 4, 1)
        grid.addLayout(buttonHBox, 5, 0, 1, 2)

        self.setLayout(grid)

    #----------------------------------------------------------------------
    def login(self):
        """登录"""
        userid = str(self.editUserID.text())
        password = str(self.editPassword.text())
        mdAddress = str(self.editMdAddress.text())
        tdAddress = str(self.editTdAddress.text())
        brokerid = str(self.editBrokerID.text())

        self.__mainEngine.login(userid, password, brokerid, mdAddress, tdAddress)
        self.close()

    #----------------------------------------------------------------------
    def loadData(self):
        """读取数据"""
        f = shelve.open('setting.vn')

        try:
            setting = f['login']
            userid = setting['userid']
            password = setting['password']
            mdAddress = setting['mdAddress']
            tdAddress = setting['tdAddress']
            brokerid = setting['brokerid']

            self.editUserID.setText(userid)
            self.editPassword.setText(password)
            self.editMdAddress.setText(mdAddress)
            self.editTdAddress.setText(tdAddress)
            self.editBrokerID.setText(brokerid)
        except KeyError:
            pass

        f.close()

    #----------------------------------------------------------------------
    def saveData(self):
        """保存数据"""
        setting = {}
        setting['userid'] = str(self.editUserID.text())
        setting['password'] = str(self.editPassword.text())
        setting['mdAddress'] = str(self.editMdAddress.text())
        setting['tdAddress'] = str(self.editTdAddress.text())
        setting['brokerid'] = str(self.editBrokerID.text())

        f = shelve.open('setting.vn')
        f['login'] = setting
        f.close()   

    #----------------------------------------------------------------------
    def closeEvent(self, event):
        """关闭事件处理"""
        # 当窗口被关闭时,先保存登录数据,再关闭
        self.saveData()
        event.accept()      


########################################################################
class ControlWidget(QtGui.QWidget):
    """调用查询函数"""

    #----------------------------------------------------------------------
    def __init__(self, mainEngine, parent=None):
        """Constructor"""
        super(ControlWidget, self).__init__()
        self.__mainEngine = mainEngine

        self.initUi()

    #----------------------------------------------------------------------
    def initUi(self):
        """"""
        self.setWindowTitle(u'测试')

        buttonAccount = QtGui.QPushButton(u'查询账户')
        buttonInvestor = QtGui.QPushButton(u'查询投资者')
        buttonPosition = QtGui.QPushButton(u'查询持仓')

        buttonAccount.clicked.connect(self.__mainEngine.getAccount)
        buttonInvestor.clicked.connect(self.__mainEngine.getInvestor)
        buttonPosition.clicked.connect(self.__mainEngine.getPosition)

        hBox = QtGui.QHBoxLayout()
        hBox.addWidget(buttonAccount)
        hBox.addWidget(buttonInvestor)
        hBox.addWidget(buttonPosition)

        self.setLayout(hBox)


########################################################################
class TradingWidget(QtGui.QWidget):
    """交易"""
    signal = QtCore.pyqtSignal(type(Event()))

    dictDirection = OrderedDict()
    dictDirection['0'] = u'买'
    dictDirection['1'] = u'卖'  

    dictOffset = OrderedDict()
    dictOffset['0'] = u'开仓'
    dictOffset['1'] = u'平仓' 
    dictOffset['3'] = u'平今'

    dictPriceType = OrderedDict()
    dictPriceType['1'] = u'任意价'
    dictPriceType['2'] = u'限价'
    dictPriceType['3'] = u'最优价'
    dictPriceType['4'] = u'最新价'

    # 反转字典
    dictDirectionReverse = {value:key for key,value in dictDirection.items()}
    dictOffsetReverse = {value:key for key, value in dictOffset.items()}
    dictPriceTypeReverse = {value:key for key, value in dictPriceType.items()}

    #----------------------------------------------------------------------
    def __init__(self, eventEngine, mainEngine, orderMonitor, parent=None):
        """Constructor"""
        super(TradingWidget, self).__init__()
        self.__eventEngine = eventEngine
        self.__mainEngine = mainEngine
        self.__orderMonitor = orderMonitor

        self.instrumentid = ''

        self.initUi()
        self.registerEvent()

    #----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""
        self.setWindowTitle(u'交易')

        # 左边部分
        labelID = QtGui.QLabel(u'代码')
        labelName =  QtGui.QLabel(u'名称')
        labelDirection = QtGui.QLabel(u'委托类型')
        labelOffset = QtGui.QLabel(u'开平')
        labelPrice = QtGui.QLabel(u'价格')
        labelVolume = QtGui.QLabel(u'数量')
        labelPriceType = QtGui.QLabel(u'价格类型')

        self.lineID = QtGui.QLineEdit()
        self.lineName = QtGui.QLineEdit()

        self.comboDirection = QtGui.QComboBox()
        self.comboDirection.addItems(self.dictDirection.values())

        self.comboOffset = QtGui.QComboBox()
        self.comboOffset.addItems(self.dictOffset.values())

        self.spinPrice = QtGui.QDoubleSpinBox()
        self.spinPrice.setDecimals(4)
        self.spinPrice.setMinimum(0)
        self.spinPrice.setMaximum(10000)

        self.spinVolume = QtGui.QSpinBox()
        self.spinVolume.setMinimum(0)
        self.spinVolume.setMaximum(1000000)

        self.comboPriceType = QtGui.QComboBox()
        self.comboPriceType.addItems(self.dictPriceType.values())

        gridleft = QtGui.QGridLayout()
        gridleft.addWidget(labelID, 0, 0)
        gridleft.addWidget(labelName, 1, 0)
        gridleft.addWidget(labelDirection, 2, 0)
        gridleft.addWidget(labelOffset, 3, 0)
        gridleft.addWidget(labelPrice, 4, 0)
        gridleft.addWidget(labelVolume, 5, 0)
        gridleft.addWidget(labelPriceType, 6, 0)
        gridleft.addWidget(self.lineID, 0, 1)
        gridleft.addWidget(self.lineName, 1, 1)
        gridleft.addWidget(self.comboDirection, 2, 1)
        gridleft.addWidget(self.comboOffset, 3, 1)
        gridleft.addWidget(self.spinPrice, 4, 1)
        gridleft.addWidget(self.spinVolume, 5, 1)
        gridleft.addWidget(self.comboPriceType, 6, 1)   

        # 右边部分
        labelBid1 = QtGui.QLabel(u'买一')
        labelBid2 = QtGui.QLabel(u'买二')
        labelBid3 = QtGui.QLabel(u'买三')
        labelBid4 = QtGui.QLabel(u'买四')
        labelBid5 = QtGui.QLabel(u'买五')

        labelAsk1 = QtGui.QLabel(u'卖一')
        labelAsk2 = QtGui.QLabel(u'卖二')
        labelAsk3 = QtGui.QLabel(u'卖三')
        labelAsk4 = QtGui.QLabel(u'卖四')
        labelAsk5 = QtGui.QLabel(u'卖五')

        self.labelBidPrice1 = QtGui.QLabel()
        self.labelBidPrice2 = QtGui.QLabel()
        self.labelBidPrice3 = QtGui.QLabel()
        self.labelBidPrice4 = QtGui.QLabel()
        self.labelBidPrice5 = QtGui.QLabel()
        self.labelBidVolume1 = QtGui.QLabel()
        self.labelBidVolume2 = QtGui.QLabel()
        self.labelBidVolume3 = QtGui.QLabel()
        self.labelBidVolume4 = QtGui.QLabel()
        self.labelBidVolume5 = QtGui.QLabel()   

        self.labelAskPrice1 = QtGui.QLabel()
        self.labelAskPrice2 = QtGui.QLabel()
        self.labelAskPrice3 = QtGui.QLabel()
        self.labelAskPrice4 = QtGui.QLabel()
        self.labelAskPrice5 = QtGui.QLabel()
        self.labelAskVolume1 = QtGui.QLabel()
        self.labelAskVolume2 = QtGui.QLabel()
        self.labelAskVolume3 = QtGui.QLabel()
        self.labelAskVolume4 = QtGui.QLabel()
        self.labelAskVolume5 = QtGui.QLabel()   

        labelLast = QtGui.QLabel(u'最新')
        self.labelLastPrice = QtGui.QLabel()
        self.labelReturn = QtGui.QLabel()

        self.labelLastPrice.setMinimumWidth(60)
        self.labelReturn.setMinimumWidth(60)

        gridRight = QtGui.QGridLayout()
        gridRight.addWidget(labelAsk5, 0, 0)
        gridRight.addWidget(labelAsk4, 1, 0)
        gridRight.addWidget(labelAsk3, 2, 0)
        gridRight.addWidget(labelAsk2, 3, 0)
        gridRight.addWidget(labelAsk1, 4, 0)
        gridRight.addWidget(labelLast, 5, 0)
        gridRight.addWidget(labelBid1, 6, 0)
        gridRight.addWidget(labelBid2, 7, 0)
        gridRight.addWidget(labelBid3, 8, 0)
        gridRight.addWidget(labelBid4, 9, 0)
        gridRight.addWidget(labelBid5, 10, 0)

        gridRight.addWidget(self.labelAskPrice5, 0, 1)
        gridRight.addWidget(self.labelAskPrice4, 1, 1)
        gridRight.addWidget(self.labelAskPrice3, 2, 1)
        gridRight.addWidget(self.labelAskPrice2, 3, 1)
        gridRight.addWidget(self.labelAskPrice1, 4, 1)
        gridRight.addWidget(self.labelLastPrice, 5, 1)
        gridRight.addWidget(self.labelBidPrice1, 6, 1)
        gridRight.addWidget(self.labelBidPrice2, 7, 1)
        gridRight.addWidget(self.labelBidPrice3, 8, 1)
        gridRight.addWidget(self.labelBidPrice4, 9, 1)
        gridRight.addWidget(self.labelBidPrice5, 10, 1) 

        gridRight.addWidget(self.labelAskVolume5, 0, 2)
        gridRight.addWidget(self.labelAskVolume4, 1, 2)
        gridRight.addWidget(self.labelAskVolume3, 2, 2)
        gridRight.addWidget(self.labelAskVolume2, 3, 2)
        gridRight.addWidget(self.labelAskVolume1, 4, 2)
        gridRight.addWidget(self.labelReturn, 5, 2)
        gridRight.addWidget(self.labelBidVolume1, 6, 2)
        gridRight.addWidget(self.labelBidVolume2, 7, 2)
        gridRight.addWidget(self.labelBidVolume3, 8, 2)
        gridRight.addWidget(self.labelBidVolume4, 9, 2)
        gridRight.addWidget(self.labelBidVolume5, 10, 2)

        # 发单按钮
        buttonSendOrder = QtGui.QPushButton(u'发单')
        buttonCancelAll = QtGui.QPushButton(u'全撤')

        # 整合布局
        hbox = QtGui.QHBoxLayout()
        hbox.addLayout(gridleft)
        hbox.addLayout(gridRight)

        vbox = QtGui.QVBoxLayout()
        vbox.addLayout(hbox)
        vbox.addWidget(buttonSendOrder)
        vbox.addWidget(buttonCancelAll)

        self.setLayout(vbox)

        # 关联更新
        buttonSendOrder.clicked.connect(self.sendOrder)
        buttonCancelAll.clicked.connect(self.__orderMonitor.cancelAll)
        self.lineID.returnPressed.connect(self.updateID)

    #----------------------------------------------------------------------
    def updateID(self):
        """合约变化"""
        instrumentid = str(self.lineID.text())

        # 获取合约
        instrument = self.__mainEngine.selectInstrument(instrumentid)
        if instrument:
            self.lineName.setText(instrument['InstrumentName'].decode('GBK'))

            # 清空价格数量
            self.spinPrice.setValue(0)
            self.spinVolume.setValue(0)

            # 清空行情显示
            self.labelBidPrice1.setText('')
            self.labelBidPrice2.setText('')
            self.labelBidPrice3.setText('')
            self.labelBidPrice4.setText('')
            self.labelBidPrice5.setText('')
            self.labelBidVolume1.setText('')
            self.labelBidVolume2.setText('')
            self.labelBidVolume3.setText('')
            self.labelBidVolume4.setText('')
            self.labelBidVolume5.setText('')    
            self.labelAskPrice1.setText('')
            self.labelAskPrice2.setText('')
            self.labelAskPrice3.setText('')
            self.labelAskPrice4.setText('')
            self.labelAskPrice5.setText('')
            self.labelAskVolume1.setText('')
            self.labelAskVolume2.setText('')
            self.labelAskVolume3.setText('')
            self.labelAskVolume4.setText('')
            self.labelAskVolume5.setText('')
            self.labelLastPrice.setText('')
            self.labelReturn.setText('')

            # 重新注册事件监听
            self.__eventEngine.unregister(EVENT_MARKETDATA_CONTRACT+self.instrumentid, self.signal.emit)
            self.__eventEngine.register(EVENT_MARKETDATA_CONTRACT+instrumentid, self.signal.emit)

            # 订阅合约
            self.__mainEngine.subscribe(instrumentid, instrument['ExchangeID'])

            # 更新目前的合约
            self.instrumentid = instrumentid

    #----------------------------------------------------------------------
    def updateMarketData(self, event):
        """更新行情"""
        data = event.dict_['data']

        if data['InstrumentID'] == self.instrumentid:
            self.labelBidPrice1.setText(str(data['BidPrice1']))
            self.labelAskPrice1.setText(str(data['AskPrice1']))
            self.labelBidVolume1.setText(str(data['BidVolume1']))
            self.labelAskVolume1.setText(str(data['AskVolume1']))

            if data['BidVolume2']:
                self.labelBidPrice2.setText(str(data['BidPrice2']))
                self.labelBidPrice3.setText(str(data['BidPrice3']))
                self.labelBidPrice4.setText(str(data['BidPrice4']))
                self.labelBidPrice5.setText(str(data['BidPrice5']))

                self.labelAskPrice2.setText(str(data['AskPrice2']))
                self.labelAskPrice3.setText(str(data['AskPrice3']))
                self.labelAskPrice4.setText(str(data['AskPrice4']))
                self.labelAskPrice5.setText(str(data['AskPrice5']))

                self.labelBidVolume2.setText(str(data['BidVolume2']))
                self.labelBidVolume3.setText(str(data['BidVolume3']))
                self.labelBidVolume4.setText(str(data['BidVolume4']))
                self.labelBidVolume5.setText(str(data['BidVolume5']))

                self.labelAskVolume2.setText(str(data['AskVolume2']))
                self.labelAskVolume3.setText(str(data['AskVolume3']))
                self.labelAskVolume4.setText(str(data['AskVolume4']))
                self.labelAskVolume5.setText(str(data['AskVolume5']))   

            self.labelLastPrice.setText(str(data['LastPrice']))
            rt = (data['LastPrice']/data['PreClosePrice'])-1
            self.labelReturn.setText(('%.2f' %(rt*100))+'%')

    #----------------------------------------------------------------------
    def registerEvent(self):
        """注册事件监听"""
        self.signal.connect(self.updateMarketData)

    #----------------------------------------------------------------------
    def sendOrder(self):
        """发单"""
        instrumentid = str(self.lineID.text())

        instrument = self.__mainEngine.selectInstrument(instrumentid)
        if instrument:
            exchangeid = instrument['ExchangeID']
            direction = self.dictDirectionReverse[unicode(self.comboDirection.currentText())]
            offset = self.dictOffsetReverse[unicode(self.comboOffset.currentText())]
            price = float(self.spinPrice.value())
            volume = int(self.spinVolume.value())
            pricetype = self.dictPriceTypeReverse[unicode(self.comboPriceType.currentText())]
            self.__mainEngine.sendOrder(instrumentid, exchangeid, price, pricetype, volume ,direction, offset)


########################################################################
class AboutWidget(QtGui.QDialog):
    """显示关于信息"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        super(AboutWidget, self).__init__(parent)

        self.initUi()

    #----------------------------------------------------------------------
    def initUi(self):
        """"""
        self.setWindowTitle(u'关于')

        text = u"""
            vn.py框架Demo  

            完成日期:2015/4/17 

            作者:用Python的交易员

            License:MIT

            主页:vnpy.org

            Github:github.com/vnpy/vnpy

            QQ交流群:262656087




            开发环境

            操作系统:Windows 7 专业版 64位

            Python发行版:Python 2.7.6 (Anaconda 1.9.2 Win-32)

            图形库:PyQt4 4.11.3 Py2.7-x32

            交易接口:vn.lts/vn.ctp

            事件驱动引擎:vn.event

            开发环境:WingIDE 5.0.6

            EXE打包:Nuitka 0.5.12.1 Python2.7 32 bit MSI
            """

        label = QtGui.QLabel()
        label.setText(text)
        label.setMinimumWidth(450)

        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(label)

        self.setLayout(vbox)

########################################################################
class PriceWidget(QtGui.QWidget):
    """用于显示价格走势图"""
    signal = QtCore.pyqtSignal(type(Event()))

    # tick图的相关参数、变量
    listlastPrice = np.empty(1000)

    fastMA = 0
    midMA = 0
    slowMA = 0
    listfastMA = np.empty(1000)
    listmidMA = np.empty(1000)
    listslowMA = np.empty(1000)
    tickFastAlpha = 0.0333    # 快速均线的参数,30
    tickMidAlpha = 0.0167     # 中速均线的参数,60
    tickSlowAlpha = 0.0083    # 慢速均线的参数,120

    ptr = 0
    ticktime = None  # tick数据时间

    # K线图EMA均线的参数、变量
    EMAFastAlpha = 0.0167    # 快速EMA的参数,60
    EMASlowAlpha = 0.0083  # 慢速EMA的参数,120
    fastEMA = 0        # 快速EMA的数值
    slowEMA = 0        # 慢速EMA的数值
    listfastEMA = []
    listslowEMA = []

    # K线缓存对象
    barOpen = 0
    barHigh = 0
    barLow = 0
    barClose = 0
    barTime = None
    barOpenInterest = 0
    num = 0

    # 保存K线数据的列表对象
    listBar = []
    listClose = []
    listHigh = []
    listLow = []
    listOpen = []
    listOpenInterest = []

    # 是否完成了历史数据的读取
    initCompleted = False
    # 初始化时读取的历史数据的起始日期(可以选择外部设置)
    startDate = None
    symbol = 'SR701'

    class CandlestickItem(pg.GraphicsObject):
        def __init__(self, data):
            pg.GraphicsObject.__init__(self)
            self.data = data  ## data must have fields: time, open, close, min, max
            self.generatePicture()

        def generatePicture(self):
            ## pre-computing a QPicture object allows paint() to run much more quickly,
            ## rather than re-drawing the shapes every time.
            self.picture = QtGui.QPicture()
            p = QtGui.QPainter(self.picture)
            p.setPen(pg.mkPen(color='w', width=0.4))  # 0.4 means w*2
            # w = (self.data[1][0] - self.data[0][0]) / 3.
            w = 0.2
            for (t, open, close, min, max) in self.data:
                p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max))
                if open > close:
                    p.setBrush(pg.mkBrush('g'))
                else:
                    p.setBrush(pg.mkBrush('r'))
                p.drawRect(QtCore.QRectF(t-w, open, w*2, close-open))
            p.end()

        def paint(self, p, *args):
            p.drawPicture(0, 0, self.picture)

        def boundingRect(self):
            ## boundingRect _must_ indicate the entire area that will be drawn on
            ## or else we will get artifacts and possibly crashing.
            ## (in this case, QPicture does all the work of computing the bouning rect for us)
            return QtCore.QRectF(self.picture.boundingRect())

    #----------------------------------------------------------------------
    def __init__(self, eventEngine, mainEngine, parent=None):
        """Constructor"""
        super(PriceWidget, self).__init__(parent)

        self.__eventEngine = eventEngine
        self.__mainEngine = mainEngine
        # MongoDB数据库相关
        self.__mongoConnected = False
        self.__mongoConnection = None
        self.__mongoTickDB = None

        # 调用函数
        self.__connectMongo()
        self.initUi(startDate=None)
        self.registerEvent()

    #----------------------------------------------------------------------
    def initUi(self, startDate=None):
        """初始化界面"""
        self.setWindowTitle(u'Price')

        self.vbl_1 = QtGui.QVBoxLayout()
        self.initplotTick()  # plotTick初始化

        self.vbl_2 = QtGui.QVBoxLayout()
        self.initplotKline()  # plotKline初始化
        self.initplotTendency()  # plot分时图的初始化

        # 整体布局
        self.hbl = QtGui.QHBoxLayout()
        self.hbl.addLayout(self.vbl_1)
        self.hbl.addLayout(self.vbl_2)
        self.setLayout(self.hbl)

        self.initHistoricalData()  # 下载历史数据

    #----------------------------------------------------------------------
    def initplotTick(self):
        """"""
        self.pw1 = pg.PlotWidget(name='Plot1')
        self.vbl_1.addWidget(self.pw1)
        self.pw1.setRange(xRange=[-360, 0])
        self.pw1.setLimits(xMax=5)
        self.pw1.setDownsampling(mode='peak')
        self.pw1.setClipToView(True)

        self.curve1 = self.pw1.plot()
        self.curve2 = self.pw1.plot()
        self.curve3 = self.pw1.plot()
        self.curve4 = self.pw1.plot()

    #----------------------------------------------------------------------
    def initplotKline(self):
        """Kline"""
        self.pw2 = pg.PlotWidget(name='Plot2')  # K线图
        self.vbl_2.addWidget(self.pw2)
        self.pw2.setDownsampling(mode='peak')
        self.pw2.setClipToView(True)

        self.curve5 = self.pw2.plot()
        self.curve6 = self.pw2.plot()

        self.candle = self.CandlestickItem(self.listBar)
        self.pw2.addItem(self.candle)
        ## Draw an arrowhead next to the text box
        # self.arrow = pg.ArrowItem()
        # self.pw2.addItem(self.arrow)

    #----------------------------------------------------------------------
    def initplotTendency(self):
        """"""
        self.pw3 = pg.PlotWidget(name='Plot3')
        self.vbl_2.addWidget(self.pw3)
        self.pw3.setDownsampling(mode='peak')
        self.pw3.setClipToView(True)
        self.pw3.setMaximumHeight(200)
        self.pw3.setXLink('Plot2')   # X linked with Plot2

        self.curve7 = self.pw3.plot()

    #----------------------------------------------------------------------
    def initHistoricalData(self,startDate=None):
        """初始历史数据"""

        td = timedelta(days=1)     # 读取3天的历史TICK数据

        if startDate:
            cx = self.loadTick(self.symbol, startDate-td)
        else:
            today = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
            cx = self.loadTick(self.symbol, today-td)

        if cx:
            for data in cx:
                tick = Tick(data['InstrumentID'])

                tick.openPrice = data['OpenPrice']
                tick.highPrice = data['HighestPrice']
                tick.lowPrice = data['LowestPrice']
                tick.lastPrice = data['LastPrice']

                tick.volume = data['Volume']
                tick.openInterest = data['OpenInterest']

                tick.upperLimit = data['UpperLimitPrice']
                tick.lowerLimit = data['LowerLimitPrice']

                tick.time = data['UpdateTime']
                tick.ms = data['UpdateMillisec']

                tick.bidPrice1 = data['BidPrice1']
                tick.bidPrice2 = data['BidPrice2']
                tick.bidPrice3 = data['BidPrice3']
                tick.bidPrice4 = data['BidPrice4']
                tick.bidPrice5 = data['BidPrice5']

                tick.askPrice1 = data['AskPrice1']
                tick.askPrice2 = data['AskPrice2']
                tick.askPrice3 = data['AskPrice3']
                tick.askPrice4 = data['AskPrice4']
                tick.askPrice5 = data['AskPrice5']

                tick.bidVolume1 = data['BidVolume1']
                tick.bidVolume2 = data['BidVolume2']
                tick.bidVolume3 = data['BidVolume3']
                tick.bidVolume4 = data['BidVolume4']
                tick.bidVolume5 = data['BidVolume5']

                tick.askVolume1 = data['AskVolume1']
                tick.askVolume2 = data['AskVolume2']
                tick.askVolume3 = data['AskVolume3']
                tick.askVolume4 = data['AskVolume4']
                tick.askVolume5 = data['AskVolume5']

                self.onTick(tick)

        self.initCompleted = True    # 读取历史数据完成
        # pprint('load historic data completed')

    #----------------------------------------------------------------------
    def plotTick(self):
        """画tick图"""
        if self.initCompleted:
            self.curve1.setData(self.listlastPrice[:self.ptr])
            self.curve2.setData(self.listfastMA[:self.ptr], pen=(255, 0, 0), name="Red curve")
            self.curve3.setData(self.listmidMA[:self.ptr], pen=(0, 255, 0), name="Green curve")
            self.curve4.setData(self.listslowMA[:self.ptr], pen=(0, 0, 255), name="Blue curve")
            self.curve1.setPos(-self.ptr, 0)
            self.curve2.setPos(-self.ptr, 0)
            self.curve3.setPos(-self.ptr, 0)
            self.curve4.setPos(-self.ptr, 0)

    #----------------------------------------------------------------------
    def plotKline(self):
        """K线图"""
        if self.initCompleted:
            # 均线
            self.curve5.setData(self.listfastEMA, pen=(255, 0, 0), name="Red curve")
            self.curve6.setData(self.listslowEMA, pen=(0, 255, 0), name="Green curve")

            # 画K线
            self.pw2.removeItem(self.candle)
            self.candle = self.CandlestickItem(self.listBar)
            self.pw2.addItem(self.candle)
            self.plotText()   # 显示开仓信号位置

    #----------------------------------------------------------------------
    def plotTendency(self):
        """"""
        if self.initCompleted:
            self.curve7.setData(self.listOpenInterest, pen=(255, 255, 255), name="White curve")

    #----------------------------------------------------------------------
    def plotText(self):
        lenClose = len(self.listClose)

        if lenClose >= 5:                                       # Fractal Signal
            if self.listClose[-1] > self.listClose[-2] and self.listClose[-3] > self.listClose[-2] and self.listClose[-4] > self.listClose[-2] and self.listClose[-5] > self.listClose[-2] and self.listfastEMA[-1] > self.listslowEMA[-1]:
                ## Draw an arrowhead next to the text box
                # self.pw2.removeItem(self.arrow)
                self.arrow = pg.ArrowItem(pos=(lenClose-1, self.listLow[-1]), angle=90, brush=(255, 0, 0))
                self.pw2.addItem(self.arrow)
            elif self.listClose[-1] < self.listClose[-2] and self.listClose[-3] < self.listClose[-2] and self.listClose[-4] < self.listClose[-2] and self.listClose[-5] < self.listClose[-2] and self.listfastEMA[-1] < self.listslowEMA[-1]:
                ## Draw an arrowhead next to the text box
                # self.pw2.removeItem(self.arrow)
                self.arrow = pg.ArrowItem(pos=(lenClose-1, self.listHigh[-1]), angle=-90, brush=(0, 255, 0))
                self.pw2.addItem(self.arrow)

    #----------------------------------------------------------------------
    def updateMarketData(self, event):
        """更新行情"""
        data = event.dict_['data']
        symbol = data['InstrumentID']
        tick = Tick(symbol)
        tick.openPrice = data['OpenPrice']
        tick.highPrice = data['HighestPrice']
        tick.lowPrice = data['LowestPrice']
        tick.lastPrice = data['LastPrice']

        tick.volume = data['Volume']
        tick.openInterest = data['OpenInterest']

        tick.upperLimit = data['UpperLimitPrice']
        tick.lowerLimit = data['LowerLimitPrice']

        tick.time = data['UpdateTime']
        tick.ms = data['UpdateMillisec']

        tick.bidPrice1 = data['BidPrice1']
        tick.bidPrice2 = data['BidPrice2']
        tick.bidPrice3 = data['BidPrice3']
        tick.bidPrice4 = data['BidPrice4']
        tick.bidPrice5 = data['BidPrice5']

        tick.askPrice1 = data['AskPrice1']
        tick.askPrice2 = data['AskPrice2']
        tick.askPrice3 = data['AskPrice3']
        tick.askPrice4 = data['AskPrice4']
        tick.askPrice5 = data['AskPrice5']

        tick.bidVolume1 = data['BidVolume1']
        tick.bidVolume2 = data['BidVolume2']
        tick.bidVolume3 = data['BidVolume3']
        tick.bidVolume4 = data['BidVolume4']
        tick.bidVolume5 = data['BidVolume5']

        tick.askVolume1 = data['AskVolume1']
        tick.askVolume2 = data['AskVolume2']
        tick.askVolume3 = data['AskVolume3']
        tick.askVolume4 = data['AskVolume4']
        tick.askVolume5 = data['AskVolume5']

        self.onTick(tick)  # tick数据更新

        # # 将数据插入MongoDB数据库,实盘建议另开程序记录TICK数据
        # self.__recordTick(data)

    #----------------------------------------------------------------------
    def onTick(self, tick):
        """tick数据更新"""
        from datetime import time

        # 首先生成datetime.time格式的时间(便于比较),从字符串时间转化为time格式的时间
        hh, mm, ss = tick.time.split(':')
        self.ticktime = time(int(hh), int(mm), int(ss), microsecond=tick.ms)

        # 计算tick图的相关参数
        if self.ptr == 0:
            self.fastMA = tick.lastPrice
            self.midMA = tick.lastPrice
            self.slowMA = tick.lastPrice
        else:
            self.fastMA = (1-self.tickFastAlpha) * self.fastMA + self.tickFastAlpha * tick.lastPrice
            self.midMA = (1-self.tickMidAlpha) * self.midMA + self.tickMidAlpha * tick.lastPrice
            self.slowMA = (1-self.tickSlowAlpha) * self.slowMA + self.tickSlowAlpha * tick.lastPrice
        self.listlastPrice[self.ptr] = tick.lastPrice
        self.listfastMA[self.ptr] = self.fastMA
        self.listmidMA[self.ptr] = self.midMA
        self.listslowMA[self.ptr] = self.slowMA

        self.ptr += 1
        # pprint("----------")
        # pprint(self.ptr)
        if self.ptr >= self.listlastPrice.shape[0]:
            tmp = self.listlastPrice
            self.listlastPrice = np.empty(self.listlastPrice.shape[0] * 2)
            self.listlastPrice[:tmp.shape[0]] = tmp

            tmp = self.listfastMA
            self.listfastMA = np.empty(self.listfastMA.shape[0] * 2)
            self.listfastMA[:tmp.shape[0]] = tmp

            tmp = self.listmidMA
            self.listmidMA = np.empty(self.listmidMA.shape[0] * 2)
            self.listmidMA[:tmp.shape[0]] = tmp

            tmp = self.listslowMA
            self.listslowMA = np.empty(self.listslowMA.shape[0] * 2)
            self.listslowMA[:tmp.shape[0]] = tmp

        # K线数据
        # 假设是收到的第一个TICK
        if self.barOpen == 0:
            # 初始化新的K线数据
            self.barOpen = tick.lastPrice
            self.barHigh = tick.lastPrice
            self.barLow = tick.lastPrice
            self.barClose = tick.lastPrice
            self.barTime = self.ticktime
            self.barOpenInterest = tick.openInterest
            self.onBar(self.num, self.barOpen, self.barClose, self.barLow, self.barHigh, self.barOpenInterest)
        else:
            # 如果是当前一分钟内的数据
            if self.ticktime.minute == self.barTime.minute:
                if self.ticktime.second >= 30 and self.barTime.second < 30: # 判断30秒周期K线
                    # 先保存K线收盘价
                    self.num += 1
                    self.onBar(self.num, self.barOpen, self.barClose, self.barLow, self.barHigh, self.barOpenInterest)
                    # 初始化新的K线数据
                    self.barOpen = tick.lastPrice
                    self.barHigh = tick.lastPrice
                    self.barLow = tick.lastPrice
                    self.barClose = tick.lastPrice
                    self.barTime = self.ticktime
                    self.barOpenInterest = tick.openInterest
                # 汇总TICK生成K线
                self.barHigh = max(self.barHigh, tick.lastPrice)
                self.barLow = min(self.barLow, tick.lastPrice)
                self.barClose = tick.lastPrice
                self.barTime = self.ticktime
                self.listBar.pop()
                self.listfastEMA.pop()
                self.listslowEMA.pop()
                self.listOpen.pop()
                self.listClose.pop()
                self.listHigh.pop()
                self.listLow.pop()
                self.listOpenInterest.pop()
                self.onBar(self.num, self.barOpen, self.barClose, self.barLow, self.barHigh, self.barOpenInterest)
            # 如果是新一分钟的数据
            else:
                # 先保存K线收盘价
                self.num += 1
                self.onBar(self.num, self.barOpen, self.barClose, self.barLow, self.barHigh, self.barOpenInterest)
                # 初始化新的K线数据
                self.barOpen = tick.lastPrice
                self.barHigh = tick.lastPrice
                self.barLow = tick.lastPrice
                self.barClose = tick.lastPrice
                self.barTime = self.ticktime
                self.barOpenInterest = tick.openInterest

    #----------------------------------------------------------------------
    def onBar(self, n, o, c, l, h, oi):
        self.listBar.append((n, o, c, l, h))
        self.listOpen.append(o)
        self.listClose.append(c)
        self.listHigh.append(h)
        self.listLow.append(l)
        self.listOpenInterest.append(oi)

        #计算K线图EMA均线
        if self.fastEMA:
            self.fastEMA = c*self.EMAFastAlpha + self.fastEMA*(1-self.EMAFastAlpha)
            self.slowEMA = c*self.EMASlowAlpha + self.slowEMA*(1-self.EMASlowAlpha)
        else:
            self.fastEMA = c
            self.slowEMA = c
        self.listfastEMA.append(self.fastEMA)
        self.listslowEMA.append(self.slowEMA)

        # 调用画图函数
        self.plotTick()      # tick图
        self.plotKline()     # K线图
        self.plotTendency()  # K线副图,持仓量

    #----------------------------------------------------------------------
    def __connectMongo(self):
        """连接MongoDB数据库"""
        try:
            self.__mongoConnection = MongoClient()
            self.__mongoConnected = True
            self.__mongoTickDB = self.__mongoConnection['TickDB']
        except ConnectionFailure:
            pass

    #----------------------------------------------------------------------
    def __recordTick(self, data):
        """将Tick数据插入到MongoDB中"""
        if self.__mongoConnected:
            symbol = data['InstrumentID']
            data['date'] = self.today
            self.__mongoTickDB[symbol].insert(data)

    #----------------------------------------------------------------------
    def loadTick(self, symbol, startDate, endDate=None):
        """从MongoDB中读取Tick数据"""
        if self.__mongoConnected:
            collection = self.__mongoTickDB[symbol]

            # 如果输入了读取TICK的最后日期
            if endDate:
                cx = collection.find({'date': {'$gte': startDate, '$lte': endDate}})
            else:
                cx = collection.find({'date': {'$gte': startDate}})
            return cx
        else:
            return None

    #----------------------------------------------------------------------
    def registerEvent(self):
        """注册事件监听"""
        self.signal.connect(self.updateMarketData)
        self.__eventEngine.register(EVENT_MARKETDATA, self.signal.emit)


########################################################################
class MainWindow(QtGui.QMainWindow):
    """主窗口"""
    signalInvestor = QtCore.pyqtSignal(type(Event()))
    signalLog = QtCore.pyqtSignal(type(Event()))

    #----------------------------------------------------------------------
    def __init__(self, eventEngine, mainEngine):
        """Constructor"""
        super(MainWindow, self).__init__()
        self.__eventEngine = eventEngine
        self.__mainEngine = mainEngine

        self.initUi()
        self.registerEvent()

    #----------------------------------------------------------------------
    def initUi(self):
        """"""
        # 设置名称
        self.setWindowTitle(u'欢迎使用vn.py框架Demo')
        # 布局设置
        self.logM = LogMonitor(self.__eventEngine, self)
        self.accountM = AccountMonitor(self.__eventEngine, self)
        self.positionM = PositionMonitor(self.__eventEngine, self)
        self.tradeM = TradeMonitor(self.__eventEngine, self)
        self.orderM = OrderMonitor(self.__eventEngine, self.__mainEngine, self)
        self.marketdataM = MarketDataMonitor(self.__eventEngine, self.__mainEngine, self)
        self.tradingW = TradingWidget(self.__eventEngine, self.__mainEngine, self.orderM, self)
        self.PriceW = PriceWidget(self.__eventEngine, self.__mainEngine, self)

        righttab = QtGui.QTabWidget()
        righttab.addTab(self.accountM, u'账户')
        righttab.addTab(self.positionM, u'持仓')

        lefttab = QtGui.QTabWidget()
        lefttab.addTab(self.logM, u'日志')
        lefttab.addTab(self.orderM, u'报单')
        lefttab.addTab(self.tradeM, u'成交')

        mkttab = QtGui.QTabWidget()
        mkttab.addTab(self.PriceW, u'Price')
        mkttab.addTab(self.marketdataM, u'行情')


        self.tradingW.setMaximumWidth(400)
        tradingVBox = QtGui.QVBoxLayout()
        tradingVBox.addWidget(self.tradingW)
        tradingVBox.addStretch()

        upHBox = QtGui.QHBoxLayout()
        upHBox.addLayout(tradingVBox)
        upHBox.addWidget(mkttab)

        downHBox = QtGui.QHBoxLayout()
        downHBox.addWidget(lefttab)
        downHBox.addWidget(righttab)

        vBox = QtGui.QVBoxLayout()
        vBox.addLayout(upHBox)
        vBox.addLayout(downHBox)

        centralwidget = QtGui.QWidget()
        centralwidget.setLayout(vBox)
        self.setCentralWidget(centralwidget)

        # 设置状态栏
        self.bar = self.statusBar()
        self.bar.showMessage(u'启动Demo')

        # 设置菜单栏
        actionLogin = QtGui.QAction(u'登录', self)
        actionLogin.triggered.connect(self.openLoginWidget)
        actionExit = QtGui.QAction(u'退出', self)
        actionExit.triggered.connect(self.close)

        actionAbout = QtGui.QAction(u'关于', self)
        actionAbout.triggered.connect(self.openAboutWidget)     

        menubar = self.menuBar()
        sysMenu = menubar.addMenu(u'系统')
        sysMenu.addAction(actionLogin)
        sysMenu.addAction(actionExit)

        helpMenu = menubar.addMenu(u'帮助')
        helpMenu.addAction(actionAbout)

    #----------------------------------------------------------------------
    def registerEvent(self):
        """"""
        self.signalInvestor.connect(self.updateInvestor)
        self.signalLog.connect(self.updateLog)

        self.__eventEngine.register(EVENT_INVESTOR, self.signalInvestor.emit)
        self.__eventEngine.register(EVENT_LOG, self.signalLog.emit)

    #----------------------------------------------------------------------
    def updateInvestor(self, event):
        """"""
        data = event.dict_['data']

        self.setWindowTitle(u'欢迎使用vn.py框架Demo  ' + data['InvestorName'].decode('GBK'))

    #----------------------------------------------------------------------
    def updateLog(self, event):
        """"""
        log = event.dict_['log']

        self.bar.showMessage(log)

    #----------------------------------------------------------------------
    def openLoginWidget(self):
        """打开登录"""
        try:
            self.loginW.show()
        except AttributeError:
            self.loginW = LoginWidget(self.__mainEngine, self)
            self.loginW.show()

    #----------------------------------------------------------------------
    def openAboutWidget(self):
        """打开关于"""
        try:
            self.aboutW.show()
        except AttributeError:
            self.aboutW = AboutWidget(self)
            self.aboutW.show()

    #----------------------------------------------------------------------
    def closeEvent(self, event):
        """退出事件处理"""
        reply = QtGui.QMessageBox.question(self, u'退出',
                                           u'确认退出?', QtGui.QMessageBox.Yes | 
                                           QtGui.QMessageBox.No, QtGui.QMessageBox.No)

        if reply == QtGui.QMessageBox.Yes:
            self.__mainEngine.exit()
            event.accept()
        else:
            event.ignore()

class Tick:
    """Tick数据对象"""

    #----------------------------------------------------------------------
    def __init__(self, symbol):
        """Constructor"""
        self.symbol = symbol        # 合约代码

        self.openPrice = 0          # OHLC
        self.highPrice = 0
        self.lowPrice = 0
        self.lastPrice = 0

        self.volume = 0             # 成交量
        self.openInterest = 0       # 持仓量

        self.upperLimit = 0         # 涨停价
        self.lowerLimit = 0         # 跌停价

        self.time = ''              # 更新时间和毫秒
        self.ms = 0

        self.bidPrice1 = 0          # 深度行情
        self.bidPrice2 = 0
        self.bidPrice3 = 0
        self.bidPrice4 = 0
        self.bidPrice5 = 0

        self.askPrice1 = 0
        self.askPrice2 = 0
        self.askPrice3 = 0
        self.askPrice4 = 0
        self.askPrice5 = 0

        self.bidVolume1 = 0
        self.bidVolume2 = 0
        self.bidVolume3 = 0
        self.bidVolume4 = 0
        self.bidVolume5 = 0

        self.askVolume1 = 0
        self.askVolume2 = 0
        self.askVolume3 = 0
        self.askVolume4 = 0
        self.askVolume5 = 0

海龟策略文档


在运行策略回测前,至少要对策略代码有一个整体的概念,便于以后的策略调试以及改进。
v1.9.1提供了两个版本的海龟策略,分别是基于ctaTemple开发的,针对单标的的简化版,以及针对多标的的完整版。
这里只介绍完整版的海龟策略,该模块在"examples\TurtleStrategy"文件夹下,打开如图2-4所示。在这里只需关注4个文件
 

enter image description here
 

  • setting.csv:用于设置策略回测所用到的数据,包括期货品种、合约规模、最小价格变动、手续费、滑点。
  • turtleStrategy.py:具体的海龟策略
  • turtleEngine.py:海龟策略回测引擎
  • run.ipynb:在Jupyter Notebook上回测显示

现在只关注turleStrategy.py这个文件。海龟策略由3个类构成,分别是TurtleResult、TurtleSignal、TurtlePortfolio。阅读顺序并不是简单的从上到下,而是具有镶嵌结构。
所以呢,刚阅读时候容易一脸懵逼,一脸懵逼。。。
 
enter image description here
 
下面整理了策略代码的结构图,如图,箭头方向代表先定义,后调用。
 
enter image description here
 
 

a.TurtleResult类


用于计算单笔开平仓交易盈亏,是海龟策略中判断“若上一笔盈利当前信号无效”的基础

  • __init__(self):初始化单位头寸,开仓均价,平仓均价和单笔开平仓交易盈亏数
  • open(self,price,change):先计算开仓累计成本,然后统计开仓平均成本
  • close(self,price):缓存平仓均价,统计单笔开平仓交易盈亏

 
 

b.TurtleSignal类


用于产生海龟策略交易信号,包括入场,止损,止盈委托价格与目标仓位

  • __init__(self,porfolio,vtSymbol,entryWindow,entryDev,exitWindow,exitDev,protfolioCheck=False):初始化海龟信号的策略参数(默认不检查上一笔盈亏,默认缓存60根K线)
  • onBar(self,bar):缓存足够K线后,开始计算相关技术指标,判断交易信号
  • generateSignal:负责交易信号的判断,平仓信号与开仓信号是分开的:优先检查平仓,没有仓位或者持有多头仓位的时候,在设置好入场位做多或加仓;没有仓位或者持有空头仓位的时候,在设置好入场位做空或者加仓
  • calculateIndicator:负责计算指标的产生,包括计算入场和止盈离场的唐奇安通道上下轨,判断到有单位持仓后,计算ATR指标并且设定随后8个入场位置(做多4个和做空4个),同时初始化离场价格。
  • newSignal(self, direction, offset, price, volume):定义海龟投资组合的发单委托,分别是多空方向、开仓平仓、停止单价格、合约手数。
  • buy(self,price,volume):先传入计算好的停止单价格,缓存开仓委托的价格和手数,发出投资组合的多开委托,基于最后一次加仓价格计算止损离场位置。
  • sell(self,price,volume):先传入计算好的停止单价格,缓存平仓委托的价格,发出投资组合空平的委托
  • short(self,price,volume):先传入计算好的停止单价格,缓存开仓委托的价格和手数,发出投资组合的空开委托,基于最后一次加仓价格计算止损离场位置。
  • cover(self,price,volume):先传入计算好的停止单价格,缓存平仓委托的价格,发出投资组合多平的委托。
  • open(self,price,change):计算累计开仓手数/单位头寸,调用TurtleResult类定义的open函数计算开仓平均成本。
  • close(self,price):调用TurtleResult类定义的close函数计算单笔开平仓交易盈亏。创建列表专门缓存开平仓交易盈亏。
  • getLastPnl(self):在开平仓交易盈亏列表中获取上一笔交易的盈亏
  • calculateTradePrice(self,direction,price):设置停止单价格,要求买入时,停止单成交的最优价格不能低于当前K线开盘价;卖出时,停止单成交的最优价格不能高于当前K线开盘价

 
 

c.TurlePortofolio类


根据账户资金和品种合约规模,产生具体交易委托

  • __init__(self,engine):初始化海龟投资组合的组合市值(即账户资金)和多空头持仓,创建多个字典分别缓存海龟信号、每个品种持仓情况、交易中的信号、合约大小、单位头寸规模、真实持仓量。
  • init(self,portafolio,vtSymbolList,sizeDict):传入组合市值和合约大小字典,调用TurtleSignal类来产生短周期版本和长周期版的交易信号(包括入场,止盈,止损),同时缓存到信号字典中。
  • onBar(self,bar):根据信号字典产生具体交易委托
  • newSignal(self,signal,direction,offset,price,volume):先计算单位头寸规模,然后若委托指令是开仓需要检查上一次是否盈利,若无盈利发出买入/卖空委托;若委托指令是平仓,需要注意平仓量不能超过空头持仓。同时注意单品种和组合持仓都不能超过上限。
  • sendOrder(self, vtSymbol, direction, offset, price, volume, multiplier):计算单品种持仓和整体持仓,向回测引擎中发单记录

作者:爱谁谁 ;来源:维恩的派论坛
原文标题《新手瞎掰掰之回测引擎代码分析流程图》
 
关于ctaBacktesting.py的流程图
 
enter image description here

理想解决方案


 
上一篇介绍了海龟策略在实现中遇到的困难。

本章主要讲其解决方案,那就是vn.py啦!

vn.py1.9.1新增完整的投资组合级别的海龟策略实现,经过多次测试发现,这一次海龟策略本地化实现的完成度很高。其投资组合回测资金曲线如下。
投资品种选择了12个,分别是:

  • 上期所的铝、铜、螺纹钢、锌
  • 郑商所的普麦、一号棉花
  • 大商所的玉米、铁矿石、焦煤、焦炭、豆粕、聚氯乙烯。

回测时间是2014--2018,百分比最大回撤是-29.46%,年化收益45.11%,夏普比率达1.5。从资金曲线的形态上看,虽然时不时发生回撤,但是整体趋势是向上的。故从回测上看,已经非常符合原版海龟策略的特点了。
(因为考虑到海龟策略是日K线中低频策略,故手续费和滑点在回测中都设置成零,以方便操作。但就算加上手续费和滑点,资金曲线的形态不会发生变化,夏普比率也仅仅是稍微减少,这在以后章节详细说明。)
 
enter image description here

 
 

操作步骤


下面介绍如何通过vn.py实现完整的海龟策略。

1. 工具准备

 

  • RQData:RQData 是RiceQuant(米筐科技)提供的商用版金融数据工具包, 支持 python, matlab, excel 插件等多种访问方式。它集成了简单高效的API接口,用户可快速调用丰富整齐的量化金融数据,最大限度地免除了数据搜索、清洗的烦恼,加速投研及投资的决策周期。RQData 期货数据终端提供7天免费试用。其地址如下:RQData 期货数据终端
     
  • vn.py:vn.py是基于Python语言的量化交易系统,其独特的事件驱动引擎和逐条数据回放的回测设计杜绝未来函数的可能性。同时回测引擎和实盘引擎设计采用了完全兼容的API函数,用户可以使用同一套策略代码来实现回测研究和执行实盘交易。(v1.9.1版本以后提供海龟策略模块,老版本建议升级,升级方法:先卸载vn.py,命令是 pip uninstall vnpy,然后安装新版本)其地址如下:vn.py 1.9.1

 
 

2数据准备

 

1)安装RQData

Window系统RQData安装,输入下面命令即可。

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --extra-index-url https://rquser:ricequant99@py.r
icequant.com/simple/ rqdatac==1.0.0a29

 

2)下载数据

在"examples\DataService\RqdataDataService"文件夹内的config.json上填写RQData的账号和密码,保存退出后启动downloadData.py下载全期货品种的日线级别数据。启动操作是按住"Shift"并且鼠标点击右键,选择“在此处打开命令窗口”,然后输入下面命令即可。

python downloadData.py

 
本次投资组合回测时间区间是2014年1月1日以后的数据,故在该回测上会剔除一下比较新的品种,在下面的代码中以注释出来。
其代码如下

"""
立即下载数据到数据库中,用于手动执行更新操作。
"""
from dataService import *
if __name__ == '__main__':

    #中金所
    downloadDailyBarBySymbol('IF99')
    downloadDailyBarBySymbol('IC99')    #中证500 从2015开始
    downloadDailyBarBySymbol('IH99')    #上证50 从2015开始

    #上期所
    downloadDailyBarBySymbol('CU99')
    downloadDailyBarBySymbol('AL99')
    downloadDailyBarBySymbol('ZN99')
    downloadDailyBarBySymbol('PB99')
    downloadDailyBarBySymbol('NI99')    #镍 从2015开始
    downloadDailyBarBySymbol('AU99')
    downloadDailyBarBySymbol('SN99')    #锡 从2015开始
    downloadDailyBarBySymbol('AG99')
    downloadDailyBarBySymbol('RB99')
    downloadDailyBarBySymbol('WR99')
    downloadDailyBarBySymbol('HC99')   #热轧卷板 从2014 3月开始
    downloadDailyBarBySymbol('SC99')   #燃油 从2018开始
    downloadDailyBarBySymbol('BU99')
    downloadDailyBarBySymbol('RU99')


    #大商所
    downloadDailyBarBySymbol('C99')
    downloadDailyBarBySymbol('CS99')    #玉米淀粉 从2014年12月
    downloadDailyBarBySymbol('A99')
    downloadDailyBarBySymbol('B99')
    downloadDailyBarBySymbol('M99')
    downloadDailyBarBySymbol('Y99')
    downloadDailyBarBySymbol('P99')
    downloadDailyBarBySymbol('FB99')
    downloadDailyBarBySymbol('BB99')
    downloadDailyBarBySymbol('JD99')
    downloadDailyBarBySymbol('L99')
    downloadDailyBarBySymbol('V99')
    downloadDailyBarBySymbol('PP99')   #聚丙烯 从2014年2月
    downloadDailyBarBySymbol('J99')
    downloadDailyBarBySymbol('JM99')
    downloadDailyBarBySymbol('I99')

    #郑商所
    downloadDailyBarBySymbol('TA99')
    downloadDailyBarBySymbol('MA99')   #甲醇 从2014年6月
    downloadDailyBarBySymbol('FG99')
    downloadDailyBarBySymbol('SF99')
    downloadDailyBarBySymbol('SM99')
    downloadDailyBarBySymbol('ZC99')    #动力煤 从2015年
    downloadDailyBarBySymbol('WH99')
    downloadDailyBarBySymbol('PM99')
    downloadDailyBarBySymbol('CF99')
    downloadDailyBarBySymbol('SR99')
    downloadDailyBarBySymbol('OI99')
    downloadDailyBarBySymbol('RI99')
    downloadDailyBarBySymbol('RS99')
    downloadDailyBarBySymbol('RM99')
    downloadDailyBarBySymbol('JR99')
    downloadDailyBarBySymbol('LR99')   #晚籼稻 从2014年7月
    downloadDailyBarBySymbol('CY99')   #棉花 从2017年
    downloadDailyBarBySymbol('AP99')   #苹果  从2017年

 
数据成功下载界面如图所示
enter image description here

 

3)数据说明

 
RqData分3种数据,分别是以88或888结尾的代表主力连续合约数据和以99结尾的指数连续合约,下面以IF合约为例子进行详细说明。

  • IF88:主力连续合约,由IF股指期货不同时期主力合约接续而成,仅仅是合约量价数据的简单拼接,未做平滑处理
  • IF888:主力连续合约,对价格拼接进行了"平滑"处理,即以主力合约切换前一天(T-1日)新、旧两个主力合约收盘价做差,之后将 T-1 日及以前的主力连续合约的所有价格水平整体加上或减去该价差,以"整体抬升"或"整体下降"主力合约的价格水平,成交量、持仓量均不作调整,成交额统一设置为0
  • IF99:指数连续合约,由IF股指期货全部可交易合约以累计持仓量为权重加权平均得到的
     

(主力合约定义:合约首次上市时,以当日收盘同品种持仓量最大者作为从第二个交易日开始的主力合约。当同品种其他合约持仓量在收盘后超过当前主力合约1.1倍时,从第二个交易日开始进行主力合约的切换。日内不会进行主力合约的切换。)

作者:码农的量化之路 ;文章来源:维恩的派论坛
 
最近参加了一个知乎收费讲座,将CTA策略的创新,尤其提到了策略开发的技术路线,包括机器学习在策略开放中使用的一些情况,收获很大,总结出来方便自己未来查阅,也为大家提供一些思路。另外,本文只是我的总结, 建议感兴趣的还是听知乎博主的课。

备注:CTA时量化策略的一个大类,主要是通过预测趋势来赚钱,跟其相对应的大类还有统计套利策略和高频策略。
知乎博主介绍:博主付超,是国内一家私募公司CTA策略部门的负责人,从事CTA策略开发6年。由于策略的私密性,无法讲解细节,只能提供方向。

 
 

传统CTA的特点


  1. β偏好性,主要利用趋势的肥尾特点,通过追随趋势获取利润。喜欢波动较大或者急涨急跌行情
  2. 多策略多周期
  3. 国内传统的CTA策略夏普一般在2以内,国外因为市场更成熟,一般在1.5以内
  4. 以长周期为主(作者认为长周期一般指的是平均持仓周期是几天以上的),原因时长周期趋势肥尾的特点才比较明显。通过平时亏小钱,赚大钱

我自己的思考:
包括我在内,业余的量化交易员常用的以技术指标为主的策略开发,应该就属于这种。但是如果将技术指标最为X,未来收益作为Y,放到机器学习的模型中进行训练,得到的一个新的指标或者说因子,则属于后面说的新型CTA策略。

 
 

新型CTA的特点


新型的CTA是传统CTA策略的包含关系,是对传统CTA策略的一个创新,比如利用高频策略的盘口预测和统计套利的多空对冲策略等。

  1. 除了传统CTA策略的β策略外,还包含了α策略。(β策略的本质时利用投资者回避风险的特点,亏欠的人更容易回避风险或者犯错。而α策略本质上是一种预测,主要是概率统计来发现交易机会。无论时β策略还是α策略,在市场波动率较大的情况下,一般绩效也越高,因为波动大,大家犯错的可能业变大。)
  2. 夏普一般在2以上,博主公司的策略基本都在3以上
  3. 短中长期的策略都可以做,尤其时短周期策略,因为预测周期越短,相对数据量就越大,统计学习的算法预测精度就越高(α策略)

我自己的思考:
新型CTA策略最大的特点是加入了机器学习进行预测,比如利用机器学习得到的有效信号或者说因子。即使使用现成的技术指标,也应该纳入到统计学习或者机器学习的框架来寻找映射函数

 
 

CTA的未来和创新


  1. 横向的资源整合。把不同的策略大类的特点结合起来,使用高频的逻辑处理CTA和统计套利的下单(作者指的是可以利用机器学习进行下单预测,减少滑点)。CTA可以参考统计套利的套利,做一些多空对冲。
  2. 纵向的资源整合。对一个资产管理公司来说,已经包括策略研发,还有募资和IT的支持。IT支持作者举了一个例子,就是资金量较大后,需要利用高频下单的逻辑减少滑点,需要有IT的支持
  3. 流水线作业。例如策略研发,可以封装成单独做信号(因子)、单独做策略组合的、单独做下单的几个部门,每个部门都可以做到各自的极致。4.数据为王。数据指的是2个方面,第一个是数据量越大越好,数据量越大,统计就越健壮。第二是预测周期越短越好,同样数据量的情况下,预测周期越短,样本数量就越大。
  4. 机器学习。机器学习在CTA中的应用是由浅入深的过程。一谈到机器学习,大家都马上会想到深度学习,但是作者建议大家先从简单到复杂,不要一上来就深度学习。

我自己的思考:
作者说的未来和创新,结合上下文,其实时他自己已经在做的。对我来说最有价值的是,明确了后续的技术发展方向。还是要走统计学习的方向。当然,机器学习只是工具,还是需要人能够发现潜在的规律,然后利用机器学习,得到一个可能的盈利因子。

 
 

机器学习在量化交易领域的4个特点


  1. 从线性模型到非线性模型(前一项相对于后一项更复杂,但是可能会有更好的预测效果,下同)线性模型相对比较简单,就是对一些因子进行多元线性回归等。)
  2. 从无监督学习到有监督学习(有监督模型相对于无监督模型而言,需要挑选样本进行训练,而挑选样本需要本身对市场比较了解,有很大的经验性。作者认为初学者最好从无监督学习开始。)
  3. 从分类的学习到回归的学习(分类实际上是最极端的回归,将回归分成2类或者3类,比如-1(预测正确)/1(预测错误)。为什么说先做分类再做回归,主要也是从易到难的一个过程。为什么要用回归,主要是回归做出来的策略效果更好)
  4. 从决策树模型到神经网络模型(偏深度学习)(作者没有细说。只是说神经网络模型的坑特别多)

我自己的思考:
到最后,量化交易实际就是拼技术和智力,有同样交易经验的,是否精通统计学习或机器学习,对策略的研发能力会由天壤之别。

------------------------------------------------------------------------------分割线--------------------------------------------------------------------
问答精选
 
问题1:量化策略开发的常识是啥
 
回答:交易任何时候都是一种预测,你在T时刻可以利用T时刻之前的所有数据来预测T+N期的收益率,而T+N期的收益可以看成Y,T时刻之前的所有量价数据都可以看成X,X可以采用各种量价的组合,世面上能够找到的技术指标,就是最简单的因子,可以作为X,然后通过统计学习/机器学习的思路找到一种X到Y的比较好的映射关系

海龟本地化实现困境


海龟策略如此出名,但当前国内还没有发现能够完全复制海龟策略的(至少从网络上公开的资料来看)。普遍存在的问题如下:

  • 海龟策略运行的是日线级别数据,但还是要求K线内成交,即前一日确定第二天的入场、出场、止损位置,当价格到达指定位置时,立即发出交易委托;而不是基于股票多因子框架下以收盘价成交。
  • 原版策略的是期货指数的投资组合数据;但是国内回测要么在股票上跑,要么是基金上跑,并且都只是针对单标的回测。
  • 在期货上运行策略意味着既能做多也能做空;但是对于股票品种,做空的难度很大,即融券难。其难度表现在资金门槛高,能融的券商少,融券需要付出高昂的利息等等。
  • 原版策略入场信号和止盈是短周期版本和长周期版本结合的;国内状况是由于考虑到短周期版本实现困难(难点在于实现过滤条件:若上一次突破是盈利性突破,则当前入场信号无效),故清一色是长周期版本

由于出现上面的问题,在回测效果图就能看出明显的区别。原版策略尽管有比较大的回撤,但是整体上资金曲线是向上的,但是国内复制的海龟策略回测结果显得有点差强人意,要么是回撤大,夏普低;要么是曲线显得非常奇怪。回测效果如图所示。

 
enter image description here
enter image description here
enter image description here
enter image description here
enter image description here

 
从本质上看,海龟策略是一个高风险高收益的,基于投资组合的中低频趋势跟踪策略。故不是非常复杂,但是本地化复制完成度这么低,究其原因是诚意不足罢了:

  • 散户资金量不足,编程水平不高,没有能力实现;
  • 私募机构则不愿意承担这么大的回撤风险,怕吓跑客户,故不愿意研究;
  • 自营机构或许已经通过海龟策略赚大钱,但不愿意公布。
© 2015-2022 微信 18391752892
备案服务号:沪ICP备18006526号

沪公网安备 31011502017034号

【用户协议】
【隐私政策】
【免责条款】