504 1

[源码分享] 交易回测系列一:技术信号回测 [分享]

  • 0关注
  • 2粉丝

等待验证会员

初中生

95%

还不是VIP/贵宾

-

威望
0
论坛币
10 个
通用积分
1.0010
学术水平
2 点
热心指数
2 点
信用等级
2 点
经验
312 点
帖子
11
精华
0
在线时间
12 小时
注册时间
2018-11-12
最后登录
2019-4-1

DolphinDB智臾科技 发表于 2018-11-26 17:38:43 |显示全部楼层
本系列文章将会介绍如何使用DolphinDB进行交易回测。本文以移动平均线指标为例,介绍如何在DolphinDB中实现技术信号回测。移动平均线指标(Moving average,简称MA)属于趋势指标。在金融分析领域,移动平均线是不可缺少的指标工具。除了指示趋势,均线指标还能避免由于股价下跌错失清仓的机会,减少收益的损失,及时止损,也能避免股价上涨错失买入的实际,从而获得更高的收益。

回测过程中,我们考虑两种情况:不止损回测和止损回测。
数据表需要包含以下字段:
股票代码:sym
日期:date
收盘价格:close


1. 定义MA信号
当短期均线大于长期均线时,我们认为这是一个MA交易信号。

  1. def maSignal(x, shortHorizon, longHorizon){
  2.         signal = mavg(x, shortHorizon) > mavg(x, longHorizon)
  3.         signal[0:min(x.size(), longHorizon - 1)] = NULL
  4.         return signal
  5. }
复制代码

2. 不止损回测
我们定义的交易算法如下:

假设前一天的MA信号为prevSignal,当天的MA信号为signal。
(1)如果prevSignal=false,signal=true,那么买入多头头寸(long position)。
(2)如果prevSignal=true,signal=false,那么卖出空头头寸(short position)。
(3)如果不符合以上两种情况,则保持与前一天相同的头寸。
  1. def backtest(t){
  2.         t2 = select sym,date,close,prev(close) as prevClose,signal, prev(signal) as prevSignal from t context by sym
  3.         update t2 set position=iif(prevSignal==false and signal==true, 1 ,iif(prevSignal==true and signal==false, -1, int())).prev().ffill() context by sym
  4.         return select sym,date,close,signal,position,position*(close - prevClose) as pnl from t2 where isValid(position)
  5. }
复制代码
DolphinDB函数说明:
iif(condition, trueResult, falseResult):如果满足条件condition,则返回trueResult,否则返回falseResult。它相当于if...else语句,但是语法上更加简洁。

int():返回int类型的NULL值。
prev(x):把向量中的所有元素向右移动一个位置。
ffill(x):使用NULL值前的非NULL元素填充向量中的NULL值。
isValid():检查每个元素是否为NULL。如果为NULL,返回0,否则返回1。

backtest 函数说明:
回测时首先整理数据,使用prev()函数把前一天的收盘价格prevClose和前一天的MA信号prevSignal与当天的数据对齐,便于计算。

接着,按照我们定义的交易算法,计算每个股票的头寸position。position=1表示买入,position=-1表示卖出,position=NULL表示保持不变。
最后,使用position*(close - prevClose)计算盈亏pnl。

3. 止损回测
3.1 判断止损点

首先,定义函数stoploss判断是否需要止损。该函数返回布尔类型的向量。
  1. def stoploss(ret, threshold){
  2.         cumret = cumprod(1+ret)
  3.         drawDown = 1 - cumret / cumret.cummax()
  4.         firstCutIndex = at(drawDown >= threshold).first() + 1
  5.         indicator = take(false, ret.size())
  6.         if(isValid(firstCutIndex) and firstCutIndex < ret.size())
  7.                 indicator[firstCutIndex:] = true
  8.         return indicator
  9. }
复制代码
DolphinDB内置函数说明:
cumprod:计算累计乘积。
cummax:计算累计最大值。
at(x):x是布尔表达式,找出符合条件x的元素的位置。
first:返回第一个元素。
take(X, k):返回包含k个x的向量。
stoploss 函数说明:
首先计算累计回报率cumret,接着计算当前回报率和累计最大回报率的回撤drawdown,当回撤drawdown大于等于预设阈值threshold时,则认为应当止损,并记录止损的起始位置firstCutIndex(由于到股市收盘时才知道是否需要止损或止盈,所以firstCutIndex要加1)。止损信号indicator的所有元素一开始设定为全是false。如果止损的起始位置firstCutIndex不为NULL,且不超过当前的数据量,则把止损信号indicator中从firstCutIndex开始到最后的所有元素设为true,表示从firstCutIndex开始,都应当止损。

3.2 止损回测
回测时,将止损前后的盈亏进行对比 。
  1. def backtest_stoploss(t, thresholdDrawDown){
  2.         t2 = select sym,date,close,prev(close) as prevClose,signal, prev(signal) as prevSignal from t context by sym
  3.         update t2 set position=iif(prevSignal==false and signal==true, 1 ,iif(prevSignal==true and signal==false, -1, int())).prev().ffill() context by sym
  4.         update t2 set pnl = position*(close - prevClose), ret = (close - prevClose)/prevClose
  5.         update t2 set stoplossInd = segmentby(stoploss{,thresholdDrawDown}, ret, position) context by sym
  6.         return select sym,date,close,signal,position,stoplossInd,pnl * stoplossInd as pnl, pnl as nostoplossPnl from t2 where isValid(position)
  7. }
复制代码
DolphinDB函数说明:
segmentby(func, funcArgs, segment):把funcArgs分成多个组,并把函数func应用到每个组中。segment是一个向量,可以把它看作是分组方案,连续相同的元素为一组。通过下面的例子我们可以更好地理解segmentby:
  1. x=1 2 3 0 3 2 1 4 5
  2. y=1 1 1 -1 -1 -1 1 1 1
  3. segmentby(cumsum,x,y)

  4. 1 3 6 0 3 5 1 5 10
复制代码
上面的例子中,y定义了3个分组:1 1 1、-1 -1 -1 和1 1 1,第一个分组的index是0-2,第二个分组的index是3-5,第三个分组的index是6-9。按照这个规则把x分成3组:1 2 3、0 3 2、1 4 5,并在每个分组中计算累计和。
stoploss{, thresholdDrawDown}这种表达方式是定义一个部分应用,用于固定stoploss的第二个参数thresholdDrawDown。

backtest_stoploss 函数说明:

前三行代码和1.2大致相同,除了计算盈亏pnl之外,还计算了回报率ret,因为stoploss函数需要ret作为输入。接着把每个股票的回报率ret按阶段分组(position中的元素连续多个1表示持续买入,连续多个-1表示持续卖出,连续多个NULL表示持续不变),在每个阶段分组中判断是否需要止损,为每只股票生成止损信号stoplossInd。最后计算止损前后的盈亏,止损前的盈亏为nostoplossPnl,止损后的盈亏为pnl。

4. 统计信息
通常情况下,我们还需要分析盈亏的统计信息。通过下面的自定义函数calcPerformance可以计算盈亏的统计信息,比如累计盈亏cumpnl、平均盈亏avgpnl、盈亏天数days、盈亏的标准差std、最大回撤maxDrawdown等。返回的数据类型是字典。
  1. def calcPerformance(pnl){
  2.         result = dict(STRING, DOUBLE)
  3.         result[`cumpnl]= pnl.sum()
  4.         result[`avgpnl]= pnl.avg()
  5.         result[`days] = pnl.size()
  6.         result[`std]= pnl.std()
  7.         result[`maxDrawdown] = (pnl.cumsum().cummax() - pnl.cumsum()).max()
  8.         return result
  9. }
复制代码

5. 运行实例
我们使用美国股市从1998年到2016年股票的每日交易信息作为数据集来进行测试。数据集共包含3474万条记录。

  1. //数据导入和数据处理,产生stock数据表,包含sym, date, close三个字段
  2. ...

  3. //计算每个股票每天的MA信号
  4. t = select sym,date,close,maSignal(close, 50, 100) as signal from stock context by sym
复制代码

情况一:不止损回测
  1. //不止损回测
  2. positions = backtest(t)

  3. //计算盈亏并绘制盈亏走势图
  4. dailyPnl = select sum(pnl) as pnl from positions group by date order by date
  5. calcPerformance(dailyPnl.pnl)
  6. plot(dailyPnl.pnl.cumsum() as cumulativePnl, dailyPnl.date, "Cumulative Pnl of All Stocks without Stop Loss Control")

  7. //分析每只股票的盈亏信息
  8. select calcPerformance(pnl) as `cumpnl`avgpnl`days`std`maxDrawdown from result group by sym

  9. sym        cumpnl        avgpnl        days        std        maxDrawdown
  10. A        48.75        0.0108        4,513.        1.5895        106.55
  11. AA        7.9625        0.0017        4,624.        1.131        119.75
复制代码

不止损回测.png

情况二:止损回测。我们把预设阈值设为2.5%。


  1. //止损回测
  2. positions = backtst_stoploss(t,0.025)

  3. //计算盈亏并绘制盈亏走势图
  4. dailyPnl = select sum(pnl) as pnl from positions group by date order by date
  5. calcPerformance(dailyPnl.pnl)
  6. plot(dailyPnl.pnl.cumsum() as cumulativePnl, dailyPnl.date, "Cumulative Pnl of All Stocks with Stop Loss Control")

  7. //分析每只股票的盈亏信息
  8. select calcPerformance(pnl) as `cumpnl`avgpnl`days`std`maxDrawdown from result group by sym

  9. sym        cumpnl        avgpnl        days        std        maxDrawdown
  10. A        58.2775        0.0129        4,513.        1.5731        102.125
  11. AA        20.47        0.0044        4,624.        1.1126        110.8125
复制代码

止损回测.png


DolphinDB虽然是一个通用的分布式时序数据库,但因为内置极其高效的多范式编程语言,开发效率非常高。如果回测不用考虑止损,仅用了3行代码计算MA信号,3行代码进行回测。DolphinDB的运行效率更是惊人,对美国股市18年的全部股票按日进行回测,不止损回测执行耗时仅4秒多,止损回测仅7秒多。

本文的目的是从技术上帮助金融工程师使用DolphinDB快速实现交易回测。文中采用的各种参数,譬如长短线时间,止损阈值,数据过滤的方法等等,只是起到演示的作用,并非实践中的最佳参数。

欢迎广大师生下载试用www.dolphindb.com我们的专栏 DolphinDB和量化金融,干货满满













速度即价值
stata SPSS
旋转木马8889 发表于 2018-11-30 10:13:56 |显示全部楼层
谢谢分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 我要注册

京ICP备16021002-2号 京B2-20170662号 京公网安备 11010802022788号 论坛法律顾问:王进律师 知识产权保护声明   免责及隐私声明

GMT+8, 2019-12-16 19:27