VeighNa量化社区
你的开源社区量化交易平台
Member
avatar
加入于:
帖子: 187
声望: 59

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

 
在微信公众号 “量化投资与机器学习”, 看到一个推文“我就不用AI、ML模型预测股价,来点不一样的“, 链接如下:
我就不用AI、ML模型预测股价,来点不一样的!

交易思维是基于历史数据中,一组数据比如100天中,K线中最高点或者最低点相对于开始价位价差点差,再利用numpy的函数numpy.percentile(), 计算在比如95%机会,最高点或者最低点的点差数字。如果点差是5个点,就可以认为下一根K线也有95%概率有5个点受益。

尝试在VNPY实现。

思路整理:

  • 入场:如果最近N(30)个D分钟k线,通过下面代码计算,分析对于概率prb比如90%,如果存在一个点差大于TickValueLimit一个值TickValue,说明过去N个分钟,有P的概率,bar开始下单,在bar中有最高点或者最低点获得TickValue。那么在下个bar开始时候,买入。
  • 出场,如果到达持有价格POSprice +/- TickValue, 则卖出;重新进行入场分析。如果这个bar中间没到达目标价格,在bar结束时候分析是否还满足入场条件,如果继续满足则持有,否则平仓,如果是反向,则反向开单。
  • 止损,如果在持有时候,下跌到反向POSPrice +/- Multiple * TickValue 价格时候,平仓。Multiple 随着时间增加逐渐减少。

 
 
回测设置

  • RqData日线级别数据:IF99
  • 手续费:万0.3
  • 合约规模:300
  • 滑点:0.2
  • 最小价格变化:0.2
  • 本金:100万

 
 

回测效果
enter image description here

 
 
代码如下:

# encoding: UTF-8
from __future__ import division

from vnpy.trader.vtConstant import EMPTY_STRING, EMPTY_FLOAT, OFFSET_OPEN,OFFSET_CLOSE
from vnpy.trader.app.ctaStrategy.ctaTemplate import (CtaTemplate,
                                                     BarGenerator,
                                                     ArrayManager)
import numpy as np
from datetime import datetime, time


########################################################################
class PercentileStrategy(CtaTemplate):
    """MACD策略Demo"""
    className = 'PercentileStrategy'
    author = u'BillyZhang'
    fixedSize = 1
    # 策略参数
    calWindow = 15
    percentile = 95
    tickValueLimit = 5
    Multiple = 0.8


    # 策略变量
    p = 0
    tickValue = 0
    tradeSign = 0
    tickValueHigh = 0
    tickValueLow = 0


    longStop = 0  # 多头止损
    shortStop = 0  # 空头止损
    margin = 0
    lowerLimit = 0
    upperLimit = 50000

    # 时间
    initDays = 0
    DAY_START = time(9, 10)  # 日盘启动和停止时间
    DAY_END = time(14, 55)
    NIGHT_START = time(21, 10)  # 夜盘启动和停止时间
    NIGHT_END = time(10, 55)

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

    # 变量列表,保存了变量的名称
    varList = ['inited',
               'trading',
               'pos',
               'longStop',
               'shortStop',
               'posPrice',
               'lowerLimit',
               'p',
               'tickValue',
               'tradeSign',
               'tickValueHigh',
               'tickValueLow'
                ]

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

    # ----------------------------------------------------------------------
    def __init__(self, ctaEngine, setting):
        """Constructor"""
        super(PercentileStrategy, self).__init__(ctaEngine, setting)
        self.am = ArrayManager(size = self.calWindow)


        # 注意策略类中的可变对象属性(通常是list和dict等),在策略初始化时需要重新创建,
        # 否则会出现多个策略实例之间数据共享的情况,有可能导致潜在的策略逻辑错误风险,
        # 策略类中的这些可变对象属性可以选择不写,全都放在__init__下面,写主要是为了阅读
        # 策略时方便(更多是个编程习惯的选择)

    # ----------------------------------------------------------------------
    def onInit(self):
        """初始化策略(必须由用户继承实现)"""
        self.writeCtaLog(u'%s策略初始化' % self.name)

        initData = self.loadBar(self.initDays)
        for bar in initData:
            self.onBar(bar)
        self.putEvent()

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

        # 当前无仓位,发送开仓委托
        # 持有多头仓位

        self.putEvent()

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

    # ----------------------------------------------------------------------
    def onTick(self, tick):
        """收到行情TICK推送(必须由用户继承实现)"""
        if self.lowerLimit == 0 or self.upperLimit == 0:
            self.lowerLimit = tick.lowerLimit
            self.upperLimit = tick.upperLimit
        self.bg.updateTick(tick)

    # ----------------------------------------------------------------------
    def onBar(self, bar):
        """收到Bar推送(必须由用户继承实现)"""
        #如果是当然最后5分钟,略过

        am = self.am
        am.updateBar(bar)
        if not am.inited:
            return
        # currentTime = datetime.now().time()
        currentTime = time(9,20)
        #计算p,和tickValue
        MaxHigh = am.high / am.open
        MaxLow = am.low / am.open
        MaxClose = am.close / am.open
        lpHigh = np.percentile(MaxHigh, 100 - self.percentile)
        lpLow = np.percentile(MaxLow,  self.percentile)

        self.tickValueHigh = abs(bar.open - bar.open*lpHigh)
        self.tickValueLow = abs(bar.open - bar.open * lpLow)

        if self.tickValueHigh > self.tickValueLow and self.tickValueHigh > self.tickValueLimit:
            self.tradeSign = 1
        elif self.tickValueHigh < self.tickValueLow and self.tickValueLow > self.tickValueLimit:
            self.tradeSign = -1
        else:
            self.tradeSign = 0

        # 平当日仓位, 如果当前时间是结束前日盘15点28分钟,或者夜盘10点58分钟,如果有持仓,平仓。
        if ((currentTime >= self.DAY_START and currentTime <= self.DAY_END) or
            (currentTime >= self.NIGHT_START and currentTime <= self.NIGHT_END)):
            if self.pos == 0:
                if self.tradeSign == 0:
                    pass
                elif self.tradeSign == 1 and bar.close > self.lowerLimit:
                    self.buy(bar.close + 5,self.fixedSize,False)
                elif self.tradeSign == -1 and bar.close < self.upperLimit:
                    self.short(bar.close - 5,self.fixedSize,False)
            elif self.pos > 0:
                if self.tradeSign == 1 or self.tradeSign == 0:
                    pass
                elif self.tradeSign == -1:
                    self.sell(bar.close-5, abs(self.pos), False)
            elif self.pos < 0:
                if self.tradeSign == -1 or self.tradeSign == 0:
                    pass
                elif self.tradeSign ==1:
                    self.cover(bar.close+5, abs(self.pos), False)

        else:
            if self.pos > 0:
                self.sell(bar.close-5, abs(self.pos), False)
            elif self.pos < 0:
                self.cover(bar.close+5, abs(self.pos), False)
            elif self.pos == 0:
                return

    # ----------------------------------------------------------------------
    def onOrder(self, order):
        """收到委托变化推送(必须由用户继承实现)"""
        # 对于无需做细粒度委托控制的策略,可以忽略onOrder
        pass

    # ----------------------------------------------------------------------
    def onTrade(self, trade):
        # 发出状态更新事件
        """收到成交推送(必须由用户继承实现)"""
        # 对于无需做细粒度委托控制的策略,可以忽略onOrder
        if trade.offset == OFFSET_OPEN:
            self.posPrice = trade.price
            if self.tradeSign == 1:
                self.sell(self.posPrice + self.tickValueHigh,abs(self.pos),False)
                self.sell(self.posPrice - self.Multiple*self.tickValueHigh, abs(self.pos), True)
            elif self.tradeSign == -1:
                self.cover(self.posPrice - self.tickValueLow, abs(self.pos), False)
                self.cover(self.posPrice + self.Multiple*self.tickValueLow, abs(self.pos),True)
        elif trade.offset == OFFSET_CLOSE:
            self.cancelAll()
            self.tradeSign = 0
            # 同步数据到数据库
        self.saveSyncData()

    # ----------------------------------------------------------------------
    def onStopOrder(self, so):
        """停止单推送"""
        pass
Administrator
avatar
加入于:
帖子: 4550
声望: 325

这个策略有点意思啊,概率分布是怎么个原理算出来的?

Member
avatar
加入于:
帖子: 1
声望: 0

这个策略是在日线上进行回测还是在分钟线上级进行回测?

Administrator
avatar
加入于:
帖子: 4550
声望: 325

上文有提到是日线级别的回测

Member
avatar
加入于:
帖子: 8
声望: 2

入场用bar没问题,出场误差太大,因为你不知道在T+1行情内high和low的先后

Member
avatar
加入于:
帖子: 107
声望: 2

完全,没有看懂这篇文章

Member
avatar
加入于:
帖子: 8
声望: 0

这是一个日内交易策略,如果用日线的话怎么回测的?

Member
加入于:
帖子: 50
声望: 2

看起来是一个15分钟级别的 日内把

Member
avatar
加入于:
帖子: 8
声望: 0

下面这部分会不会有问题?
if self.tradeSign == 1:
self.sell(self.posPrice + self.tickValueHigh,abs(self.pos),False)
self.sell(self.posPrice - self.Multipleself.tickValueHigh, abs(self.pos), True)
elif self.tradeSign == -1:
self.cover(self.posPrice - self.tickValueLow, abs(self.pos), False)
self.cover(self.posPrice + self.Multiple
self.tickValueLow, abs(self.pos),True)
上面同时下了止盈和止损单,但似乎优先执行止损单,因为按vnpy new_bar的执行逻辑:
self.cross_limit_order()
self.cross_stop_order()
self.strategy.on_bar(bar)
第一步将昨日的开仓单执行后,on_trade就会去下止盈和止损单,第二步self.cross_stop_order()就会立即检查当前bar是否满足止损条件,只有不满足止损条件时,才会在下一个bar检查是否满足止盈条件,如果不满,再检查是否满足止损条件。
这样如果止损条件设置不合适的话很容易就止损了,并且其实很难判断止盈和止损条件哪个先到达。

Member
avatar
加入于:
帖子: 8
声望: 0

loeman wrote:

入场用bar没问题,出场误差太大,因为你不知道在T+1行情内high和low的先后
是指止盈和止损单部分吗?我也觉得是不是有问题

Member
avatar
加入于:
帖子: 12
声望: 0

vnpy能回测你的逻辑吗?没有tick数据,你的最高价和最低价等于无效

© 2015-2022 上海韦纳软件科技有限公司
备案服务号:沪ICP备18006526号

沪公网安备 31011502017034号

【用户协议】
【隐私政策】
【免责条款】