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

2017-03-21 15:17:13 +08:00
 raquant

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

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

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

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

然而作会一个注定平庸的人,我一直坚持用战术上的勤奋来掩盖自己写不出好策略这一点。比如,我在超过 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 分钟了。收益么,都赚钱啦,不过我也不关心这个。

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

3576 次点击
所在节点    互联网
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/349140

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX