V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
raquant
V2EX  ›  互联网

多个量化平台的回测速度评测

  •  
  •   raquant · 2017-03-21 15:17:13 +08:00 · 3454 次点击
    这是一个创建于 2593 天前的主题,其中的信息可能已经有所发展或是发生改变。

    多个量化平台的回测速度评测

    每个有梦想的人都自己私下里憋着一股劲,默默地努力着,希望它日能冷不妨让大家大吃一惊。本人也不例外,经常混迹于各个量化平台,有灵感了就验证一下,看自己是否能成为下一颗冉冉升起的新星。正如大家所知道的那样,这种事情并没有发生。

    前两天看一篇文章,大意是说大多数人都已彻底丧失了构建投资(机)战略眼光的能力,所以注定平庸。我认同。在任何方面,只要有人说大部分人注定平庸,我都会认同。基于这种认知,所以上面说的新星升起这种事大概将来也不会发生吧。

    你们不同。毕竟能看到这篇文章并点赞的是极少数人,你们既然不属于大多数人,想来比我更有机会冉冉。苟富贵,莫想忘。

    然而作会一个注定平庸的人,我一直坚持用战术上的勤奋来掩盖自己写不出好策略这一点。比如,我在超过 6 个量化平台上写了相同的策略(这是重复劳动?),目的就是想看一下哪个平台跑的更欢快。大家都在达成小目标的路上忙碌着,我想这些粗活我可以适当的干点儿。下面我会贴出其中 4 个平台上的双均线策略代码,大家有兴趣可以自己去它们的主子平台那试一把。在此被点名的量化平台有 JDquant , Joinquant, Raquant, Ricequant ,后序可能会更新更多。

    定场诗唠完,上点实在的。我用来测试的策略是这样的,在大量的股票集上,实现均线策略。确切的说是在 2400 支股票内,搜寻短线超长线的股票买入;搜录长线跌出短线的策略卖出。为什么是 2400 支呢,因为各家的 API 不同,数据也不同,如果直接选可交易的所有股票的全集,一些平台没有相应的接口,即使有相应的接口,大家对停牌,新股等的处理不同,造成最终选中的股票集大小也不同。所以大家都取个交集,定 2400 支了。

    1. 京东量化

    首先出场的是在量化之路上飞奔的 JDquant ,京东量化是强哥在金融方面对抗马爸爸的一颗棋子(为什么辈份不一样?)。本人是京东金融的一名忠实用户,最近在抱怨小白理财怎么没什么可买的了,到处限额。是的,被你发现了,一个整天谈着量化投资,程式化交易的人,居然把钱放到理财里去了,而且是“小白”理财,太丢人了!

    但难道你认为整天在朋友圈里发商业模式、 IPO 、 ABCDEFG 轮融资的人个个都是头上有角的独角兽,或者背后长着翅膀的天使?

    总之,京东金融的进展非常快,京东的量化平台也是。从一开始粗糙,到后来的达到主流水平,到现在的有局部优势,也是良心工厂了。但是,它也是上面我说的没有找到取股票全集方法的平台之一。这并不是什么大问题,问题是我要保证 2400 啊,这样才好对比。所以,我用财务选股的方法把这个问题搞定了,我选资产大于 0 的,肯定海量了吧。

    def choose_stock_finance():
        dataframe = get_fundamentals(
        query(
            fundamentals.equity_valuation_indicator.market_cap_2
        ).filter(
             fundamentals.equity_valuation_indicator.market_cap_2 > 0
        ).limit(
            2400
        ))
        return dataframe.columns.values
    

    你如果读下去,会发现 Ricequant 我也是如法炮制的。

    下面上完整代码:

    import talib
    import numpy as np
    import math
    import pandas
    import time 
    import datetime 
    from functools import reduce 
    
    #init 方法是您的初始化逻辑, context 对象可以在任何函数之间传递
    def init(context): 
        #滑点默认值为 2 ‰
        context.set_slippage(0.002)
        #交易费默认值为 0.25 ‰
        context.set_commission(0.00025)
        context.codes = choose_stock_finance()
        logger.info("Total codes %d" %len(context.codes))
        context.SHORTPERIOD = 5
        context.LONGPERIOD = 20
    
    #每天开盘前进行选股    
    def before_trade(context):
       pass
        
    #日或分钟或实时数据更新,将会调用这个函数
    def handle_data(context,data_dict):
        for stock in context.codes:
            try :
                # 因为策略需要用到均线,所以需要读取历史数据
                prices = get_history(context.LONGPERIOD+1,'1d','close')[stock].values
                # 使用 talib 计算长短两根均线,均线以 array 的格式表达
                short_avg = talib.SMA(prices, context.SHORTPERIOD)
                long_avg = talib.SMA(prices, context.LONGPERIOD)
                logger.info("short: %f, long: %f"% (short_avg[-1],long_avg[-1]))
                if stock=='601318.SH' :
                    draw("short avg", short_avg[-1])
                    draw("long avg", long_avg[-1])
            
                # 计算现在 portfolio 中股票的仓位
                cur_position = context.portfolio.positions[stock].quantity
                # 计算现在 portfolio 中的现金可以购买多少股票
                shares = context.portfolio.cash/data_dict[stock].close
            
                # 如果短均线从上往下跌破长均线,也就是在目前的 bar 短线平均值低于长线平均值,而上一个 bar 的短线平均值高于长线平均值
                if short_avg[-1] - long_avg[-1] < 0 and short_avg[-2] - long_avg[-2] > 0 and cur_position > 0:
                    # 进行清仓
                    order_target_value(stock, 0)
            
                # 如果短均线从下往上突破长均线,为入场信号
                if short_avg[-1] - long_avg[-1] > 0 and short_avg[-2] - long_avg[-2] < 0:
                    order_percent(stock, 0.05)
            except :
                continue
    
    #选股函数
    def choose_stock_finance():
        dataframe = get_fundamentals(
        query(
            fundamentals.equity_valuation_indicator.market_cap_2
        ).filter(
             fundamentals.equity_valuation_indicator.market_cap_2 > 0
        ).limit(
            2400
        ))
        return dataframe.columns.values
    

    当我按下编译按钮之后,这会玩家跑出了 1 ‘ 15 ’‘的成绩,并向我展示了我的策略的回测情况:

    你不要小看我,我是能用最简单的策略写出年化 37.55%收益的男人!巴菲特才多少?而且巴菲特有教过你怎么赚钱的吗,我的策略代码你可以全部拿走,请忽略知识产权问题,分文不取!如果因为这个策略实盘导致破产吃不上饭,请联系我,我会帮你转接社会救济服务站。

    2. Joinquant 聚宽

    国内起步比较早的一家量化平台,我早期经常混迹其中。号称用户超多,但其它几个平台的数据我也看不到,所以也不知道是不是“超”多。首页上有一个醒目的位置,向你展示他们感觉比较得意的策略,运行个一两年,收益过百分之一千也很常见,而且相当数量的代码你可以看到。你可以不知不觉的拿走实盘,你要是这样出了问题,显然不应该联系我。但现面这个策略可以:

    import talib
    
    # 初始化函数,设定要操作的股票、基准等等
    def initialize(context):
        context.SHORTPERIOD = 5
        context.LONGPERIOD = 20
        # 定义一个全局变量, 保存要操作的股票
        # 000001(股票:平安银行)
        g.securities = list(get_all_securities(['stock']).index)[0:2400]
        log.info("total: %d"% len(g.securities));
        # 设定沪深 300 作为基准
        set_benchmark('000300.XSHG')
    
    # 每个单位时间(如果按天回测,则每天调用一次,如果按分钟,则每分钟调用一次)调用一次
    def handle_data(context, data):
        for stock in g.securities :
            try :
                # 因为策略需要用到均线,所以需要读取历史数据
                prices = attribute_history(stock, 21, '1d', ['close'],df=False)['close']
                # 使用 talib 计算长短两根均线,均线以 array 的格式表达
                short_avg = talib.SMA(prices, context.SHORTPERIOD)
                long_avg = talib.SMA(prices, context.LONGPERIOD)
            
                #logger.info("short: %f, long: %f"% (short_avg[-1],long_avg[-1]))
                if stock=='601318.XSHG' :
                   record(short_avg=short_avg[-1],long_avg=long_avg[-1])
            
                # 计算现在 portfolio 中股票的仓位
                cur_position = context.portfolio.positions[stock].total_amount
            
                # 如果短均线从上往下跌破长均线,也就是在目前的 bar 短线平均值低于长线平均值,而上一个 bar 的短线平均值高于长线平均值
                if short_avg[-1] - long_avg[-1] < 0 and short_avg[-2] - long_avg[-2] > 0 and cur_position > 0:
                    # 进行清仓
                    order_target_value(stock, 0)
            
                # 如果短均线从下往上突破长均线,为入场信号
                if short_avg[-1] - long_avg[-1] > 0 and short_avg[-2] - long_avg[-2] < 0:
                    order_value(stock, context.portfolio.total_value*0.05)
            except :
                continue
    

    这会玩家跑出了 3 ’ 08 ‘’的成绩,并向我们展示了回测结果:

    正如你所期待的那样,又双叒叕 赚 钱 啦!赚钱的策略是如此容易写,赔钱的策略将成会稀缺资源。

    3. Raquant 镭矿

    这是一家新兴的量化平台,用户数量暂时较少,至于我为什么要拿它来测评,你看完下面就知道了。

    他们也支持 Java ,而且与京东和大米 Java 版本的风格完全不同,作为一个 Java 程序员,首先我是难以抗拒的,但这并不是我决定把它提上测试表的原因。他们的口号是”镭厉风行,快速验证你的策略“,我只在强哥的地盘上看到有人敢说快,其它都是说数据怎么好,用户怎么多。我因此被镭到了。先看代码吧:

    public class SpeedTest extends Strategy{
        Factor fSmaShort = new SMAFactor(5);
        Factor fSmaLong = new SMAFactor(20);    
        @Override
        public void init(BackTestContext context) throws Exception {
            StockList  list = new StockList().addAll();        
            context.universe.addAll(list.subList(0,2400));
            log.info("Total: "+context.universe.size());
        }
        @Override
        public void handleData(BackTestContext context, BarData piece) throws Exception {
            for(String stock:context.universe) {
                double smaShort1 = fSmaShort.get(stock, -1);
                double smaLong1 = fSmaLong.get(stock, -1);
                double smaShort2 = fSmaShort.get(stock, -2);
                double smaLong2 = fSmaLong.get(stock, -2);
                
                if(stock.equals("sha-601318")) {
                    record("sma long", smaLong1);
                    record("sma short", smaShort1);
                }
                for(Position pos:context.portfolio.getAllPositions()) {
                    if(smaShort1<smaLong1 && smaShort2>smaLong2)
                        orderPercent(pos.getSecurity(), 0);
                }            
                if(smaShort1>smaLong1 && smaShort2<smaLong2) {
                    orderPercent(stock, 5);
                }            
            }        
        }
    }
    

    什么情况,不是说 Java 模拟个脱裤子放屁的程序都要 10000 行代码,建 100 个对象么,怎么代码反而少了这么多?

    原来他们把技术指标封装了,看他们的 API ,应该是把所有的 TA 指标都做进去了,统一新建一个对象,然后 get()就可以了。为什么别的平台不这么做呢,写 SMA 指标的时候还好,写什么 ATR,KSI 以及需要更多输入变量的指标的时候,有没有想过我的感受?我只是想用一下啊,又是取数,又是计算又是定位的,半个屏没有了。

    建议各平台的大大们都这样封装一下吧,有余力地顺遍把 MQ4 的海量指标翻译过来啊,好让我们这些小白直接使用啊。大家就想用个指标还得搞清各种输入输出,人生太短暂。

    看一下结果,这位选手跑出了 10 ”,额,节省一个标点,是的,是 10 秒的成绩。由于它不太耗时,我又多点了几下,它是有机率可以跑进 10 秒的。

    不出所料,又挣到钱了,我不悲不喜,已经麻木。

    到这里,我只想问一下雷矿的团队里是否有印度来的员工,跑个策略都咖喱味十足,跟开了挂似的。为什么要跑进 10 秒,你们的服务器比强哥的好么?

    我猜肯定不是,大概是在后台做了很多工作,把运算批量进行了或并行了之类的。但是,有功夫搞这个没功夫把网站完善一下吗,我进去有点摸不着北。

    4. Ricequant 米矿

    这也是一家专门做量化的网站,摊子铺的很大,在他们还没声称要放弃对 Java 平台支持之前,我在上面写过 Java 策略, Instrument 什么的,事件驱动什么的,我一度怀疑京东是打这获得的“灵感”。其实我骨子里还是个 Java 程序员,但在量化平台的用武之地越来越少了,也许哪天我因此敌视用 Python 做量化的平台,就再也不写了,所以请珍惜你眼下的阅读。

    大米开源了回测引擎,各种宣传各种好,有兴趣的可以去研究一下。你要在此基础上偷摸整出来了另一个回测平台,我可以帮你也测评一下。

    上代码:

    # 可以自己 import 我们平台支持的第三方 python 模块,比如 pandas 、 numpy 等。
    # 可以自己 import 我们平台支持的第三方 python 模块,比如 pandas 、 numpy 等。
    import talib
    import pandas as pd
    import numpy as np
        
    # 在这个方法中编写任何的初始化逻辑。 context 对象将会在你的算法策略的任何方法之间做传递。
    def init(context):
        context.codes = choose_stock_finance()[0:2830]
        logger.info("Total codes %d" %len(context.codes))
        # 设置这个策略当中会用到的参数,在策略中可以随时调用,这个策略使用长短均线,我们在这里设定长线和短线的区间,在调试寻找最佳区间的时候只需要在这里进行数值改动
        context.SHORTPERIOD = 5
        context.LONGPERIOD = 20
    
    # 你选择的证券的数据更新将会触发此段逻辑,例如日或分钟历史数据切片或者是实时数据切片更新
    def handle_bar(context, bar_dict):
        # 开始编写你的主要的算法逻辑
    
        # bar_dict[order_book_id] 可以拿到某个证券的 bar 信息
        # context.portfolio 可以拿到现在的投资组合状态信息
    
        # 使用 order_shares(id_or_ins, amount)方法进行落单
    
        # TODO: 开始编写你的算法吧!
        for stock in context.codes:
            try :
                # 因为策略需要用到均线,所以需要读取历史数据
                prices = history_bars(stock,context.LONGPERIOD+1, '1d', 'close')
                
                # 使用 talib 计算长短两根均线,均线以 array 的格式表达
                short_avg = talib.SMA(prices, context.SHORTPERIOD)
                long_avg = talib.SMA(prices, context.LONGPERIOD)
                logger.info("short: %f, long: %f"% (short_avg[-1],long_avg[-1]))
                if stock=='601318.XSHG' :
                    plot("short avg", short_avg[-1])
                    plot("long avg", long_avg[-1])
            
                # 计算现在 portfolio 中股票的仓位
                cur_position = context.portfolio.positions[stock].quantity
                # 计算现在 portfolio 中的现金可以购买多少股票
                shares = context.portfolio.cash/bar_dict[stock].close
            
                # 如果短均线从上往下跌破长均线,也就是在目前的 bar 短线平均值低于长线平均值,而上一个 bar 的短线平均值高于长线平均值
                if short_avg[-1] - long_avg[-1] < 0 and short_avg[-2] - long_avg[-2] > 0 and cur_position > 0:
                    # 进行清仓
                    order_target_value(stock, 0)
            
                # 如果短均线从下往上突破长均线,为入场信号
                if short_avg[-1] - long_avg[-1] > 0 and short_avg[-2] - long_avg[-2] < 0:
                    # 满仓入股
                    order_percent(stock, 0.05)
            except :
                continue            
    
    def choose_stock_finance():
        fundamental_df = get_fundamentals(
            query(
                fundamentals.income_statement.revenue, fundamentals.eod_derivative_indicator.pe_ratio
            ).order_by(
                fundamentals.eod_derivative_indicator.pe_ratio.desc()
            ).limit(
                2400
            )
        )
        return fundamental_df.columns.values
    

    经过,额,中间我等不下去吃饭去了,回来测完了,我只好重新测。经过 27 ‘ 48 “,额这个数字比较难以启齿,希望我是搞错了。你可以去亲测一下,我还有别的事要做。

    总结

    实际上没什么好总结的,我就是想简单测一下速度,如果你看完上面的内容,心里也都有数了。 Raquant 10 秒,京东 1 分 15 秒,聚宽 3 分 8 秒, Ricequant 基本接近 4 分钟了。收益么,都赚钱啦,不过我也不关心这个。

    友情提示,速度、回测收益什么的并不是最重要的。速度快就是验证你想法的速度快点,假如你有的只是烂策略,速度越快只是意味着在一定时间内,这个平台更能证明你拙劣的想法很多而已。如果因为回测收益高就觉得实盘收益也会高,这跟说五千万将近一个亿没什么区别。(且不说上述这些平台自身也并不是没有问题的)。

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   908 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 21:08 · PVG 05:08 · LAX 14:08 · JFK 17:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.