乐趣区

关于dolphindb:时序数据库交易回测系列一技术信号回测

本系列文章将会介绍如何应用 DolphinDB 进行交易回测。本文以挪动平均线指标为例,介绍如何在 DolphinDB 中实现技术信号回测。挪动平均线指标(Moving average,简称 MA)属于趋势指标。在金融剖析畛域,挪动平均线是不可短少的指标工具。除了批示趋势,均线指标还能防止因为股价上涨错失清仓的机会,缩小收益的损失,及时止损,也能防止股价上涨错失买入的理论,从而取得更高的收益。

回测过程中,咱们思考两种状况:不止损回测和止损回测。

数据表须要蕴含以下字段:

股票代码:sym

日期:date

收盘价格:close

1. 定义 MA 信号

当短期均线大于长期均线时,咱们认为这是一个 MA 交易信号。

def maSignal(x, shortHorizon, longHorizon){signal = mavg(x, shortHorizon) > mavg(x, longHorizon)
    signal[0:min(x.size(), longHorizon - 1)] = NULL
    return signal
}

2. 不止损回测

咱们定义的交易算法如下:

假如前一天的 MA 信号为 prevSignal,当天的 MA 信号为 signal。

(1)如果 prevSignal=false,signal=true,那么买入多头头寸(long position)。

(2)如果 prevSignal=true,signal=false,那么卖出空头头寸(short position)。

(3)如果不合乎以上两种状况,则放弃与前一天雷同的头寸。

def backtest(t){t2 = select sym,date,close,prev(close) as prevClose,signal, prev(signal) as prevSignal from t context by sym
    update t2 set position=iif(prevSignal==false and signal==true, 1 ,iif(prevSignal==true and signal==false, -1, int())).prev().ffill() context by sym
    return select sym,date,close,signal,position,position*(close - prevClose) as pnl from t2 where isValid(position)
}

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 判断是否须要止损。该函数返回布尔类型的向量。

def stoploss(ret, threshold){cumret = cumprod(1+ret)
    drawDown = 1 - cumret / cumret.cummax()
    firstCutIndex = at(drawDown >= threshold).first() + 1
    indicator = take(false, ret.size())
    if(isValid(firstCutIndex) and firstCutIndex < ret.size())
        indicator[firstCutIndex:] = true
    return indicator
}

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 止损回测

回测时,将止损前后的盈亏进行比照。

def backtest_stoploss(t, thresholdDrawDown){t2 = select sym,date,close,prev(close) as prevClose,signal, prev(signal) as prevSignal from t context by sym
    update t2 set position=iif(prevSignal==false and signal==true, 1 ,iif(prevSignal==true and signal==false, -1, int())).prev().ffill() context by sym
    update t2 set pnl = position*(close - prevClose), ret = (close - prevClose)/prevClose
    update t2 set stoplossInd = segmentby(stoploss{,thresholdDrawDown}, ret, position) context by sym
    return select sym,date,close,signal,position,stoplossInd,pnl * stoplossInd as pnl, pnl as nostoplossPnl from t2 where isValid(position)
}

DolphinDB 函数阐明:

_segmentby(func, funcArgs, segment):_把 funcArgs 分成多个组,并把函数 func 利用到每个组中。segment 是一个向量,能够把它看作是分组计划,间断雷同的元素为一组。通过上面的例子咱们能够更好地了解 segmentby:

x=1 2 3 0 3 2 1 4 5
y=1 1 1 -1 -1 -1 1 1 1
segmentby(cumsum,x,y)

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 等。返回的数据类型是字典。

def calcPerformance(pnl){result = dict(STRING, DOUBLE)
    result[`cumpnl]= pnl.sum()
    result[`avgpnl]= pnl.avg()
    result[`days] = pnl.size()
    result[`std]= pnl.std()
    result[`maxDrawdown] = (pnl.cumsum().cummax() - pnl.cumsum()).max()
    return result
}

5. 运行实例

咱们应用美国股市从 1998 年到 2016 年股票的每日交易信息作为数据集来进行测试。数据集共蕴含 3474 万条记录。

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

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

状况一:不止损回测

// 不止损回测
positions = backtest(t)

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

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

sym    cumpnl    avgpnl    days    std    maxDrawdown
A    48.75    0.0108    4,513.    1.5895    106.55
AA    7.9625    0.0017    4,624.    1.131    119.75
...

不止损回测所有股票的盈亏走势图

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

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

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

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

sym    cumpnl    avgpnl    days    std    maxDrawdown
A    58.2775    0.0129    4,513.    1.5731    102.125
AA    20.47    0.0044    4,624.    1.1126    110.8125
...

止损回测所有股票的盈亏走势图

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

本文的目标是从技术上帮忙金融工程师应用 DolphinDB 疾速实现交易回测。文中采纳的各种参数,譬如长短线工夫,止损阈值,数据过滤的办法等等,只是起到演示的作用,并非实际中的最佳参数。

退出移动版