我非常喜欢Ernie Chan写的量化交易的书籍:《Quantitative Trading》,《Algorithmic Trading》和《Machine Trading》。书中有一些很棒的见解,但是我最喜欢的是对各种策略进行简单而透彻的讲解,以及可以用来研究和交易的量化工具。Ernie明确指出,书中的示例无法用于实盘交易,但它们无疑为后来者提供了指引。
《Machine Trading》介绍了一种基于自回归模型的外汇日内交易策略,它的净值曲线非常吸引人,所以我决定深入研究一下。
本文来自《数据黑客》,登录官网可阅读更多精彩资讯和文章。
自回归模型
自回归(Autoregressive)模型是时间序列模型,其中:
- 预测变量是时间序列的过去值
- 目标变量是时间序列的未来值
如果使用时间序列的一阶滞后y(t-1)作为预测变量,则AR模型被称为AR(1),如下所示:
y
t
=
β
0
+
β
1
y
t
−
1
+
ϵ
t
(
β
为
回
归
系
数
)
y_t = \beta_0 + \beta_1 y {t-1} + \epsilon_t (\beta为回归系数)
yt=β0+β1yt−1+ϵt(β为回归系数)
如果使用二阶滞后y(t-1), y(t-2)作为预测变量,将其称为AR(2)模型,如下所示:
y t = β 0 + β 1 y t − 1 + β 2 y t − 2 + ϵ t y_t = \beta_0 + \beta_1 y_{t-1} + \beta_2 y_{t-2} + \epsilon_t yt=β0+β1yt−1+β2yt−2+ϵt
Ernie的AR模型
Ernie表示:
时序模型在缺乏基本信息的市场或对于短期预测不是特别有用的市场最有用。货币市场符合这个要求。
Ernie利用贝叶斯信息准则(BIC)找到最优的滞后阶数p,建立了澳元/美元分钟价格的AR§模型。他使用2007年7月24日至2014年8月12日的样本数据拟合AR(10)模型:
用2014年8月12日至2015年8月3日的样本外数据进行回溯检验(剔除成本),净值曲线如下:
更深入地思考模型
如果要用AR§模型来预测价格,我们要提出什么假设?
本质上,在包含可交易信息的范围内,过去的价格与未来的价格相关。
第一部分似乎完全合理。毕竟,预测明天价格的好起点就是今天的价格。然而对于实盘交易,这样天真的预测不会有太大帮助。另一方面,如果我们可以预测收益率,那将是一件有用的事情!
大多数量化指标会告诉您,价格水平不能包含有用的预测性信息,相反,您需要专注于体现这些价格的过程,即收益。 建立基于价格的时间序列模型感觉有点像进行技术分析,尽管使用的工具比趋势线更有趣。
如果要使用AR模型,一个合理的起点就是弄清楚价格波动是否遵循AR过程。为此,我们使用R语言做研究。
与预期相符,分钟价格序列确实具有很强的自相关性。我使用了2009年至2020年的分钟数据,如上图所示,一分钟前的价格看起来很像当前的价格。
我们可以使用偏自相关函数( pacf
)来判断AR模型的滞后阶数,以下是偏自相关图:
结果非常有趣,如果价格序列是随机游走的,可以预期偏自相关图的滞后不会很明显,但这里存在明显的自相关现象。
以下代码创建了随机游走的价格序列,以及偏自相关图:
# random walk for comparison
n <- 10000
random_walk <- cumsum(c(100, rnorm(n)))
data.frame(x = 1:(n+1), y = random_walk) %>%
ggplot(aes(x = x, y = y)) +
geom_line() +
theme_bw()
p <- pacf(random_walk, plot = FALSE)
plot(p[2:20])
随机游走的价格序列:
随机游走序列的偏自相关图:
回到真实价格序列的偏自相关图,Ernie选择AR(10)模型是合理的,但从PACF来看,滞后阶数选择15也没有问题。
查看PACF曲线如何随时间变化会很有趣。以下代码按年分割样本,并计算每年的偏自相关图:
# annual pacfs
annual_partials <- audusd %>%
mutate(year = year(timestamp)) %>%
group_by(year) %>%
# create a column of date-close dataframes called data
nest(-year) %>%
mutate(
# calculate acf for each date-close dataframe in data column
pacf_res = purrr::map(data, ~ pacf(.x$close, plot = F)),
# extract acf values from pacf_res object and drop redundant dimensions of returned lists
pacf_vals = purrr::map(pacf_res, ~ drop(.x$acf))
) %>%
# promote the column of lists such that we have one row per lag per year
unnest(pacf_vals) %>%
group_by(year) %>%
mutate(lag = seq(0, n() - 1))
signif <- function(x) {
qnorm((1 + 0.95)/2)/sqrt(sum(!is.na(x)))
}
signif_levels <- audusd %>%
mutate(year = year(timestamp)) %>%
group_by(year) %>%
summarise(significance = signif(close))
annual_partials %>%
filter(lag > 0, lag <= 20) %>%
ggplot(aes(x = lag, y = pacf_vals)) +
geom_segment(aes(xend = lag, yend = 0)) +
geom_point(size = 1, colour = "steelblue") +
geom_hline(yintercept = 0) +
facet_wrap(~year, ncol = 3) +
geom_hline(data = signif_levels, aes(yintercept = significance), colour = 'red', linetype = 'dashed')
偏自相关系数看起来很稳定,但是请记住,这里使用价格而不是收益率,我们看到的是当前价格与历史价格相关。
接下来看一下分钟收益率的自相关(ACF)图:
ret_acf <- acf(audusd %>%
mutate(returns = (close - dplyr::lag(close))/dplyr::lag(close)) %>%
select(returns) %>%
na.omit() %>%
pull(),
lags = 20, plot = FALSE
)
plot(ret_acf[2:20], main = 'ACF of minutely returns')
上图显示分钟收益率存在显著的负相关,我认为可以做出以下假设:
- 最近十分钟的价格没有什么特别的。
- 这不是我们可以交易的指标,特别是在零售外汇交易的世界中。
在下结论之前先进行一些模拟:
- 用AR(10)模型拟合大量数据。
- 基于AR(10)模型创建交易策略,并用样本外数据回溯检验。
接下来使用R来拟合AR(10)模型,用Zorro做回溯检验。
首先,用R拟合AR(10)模型(AUD/USD价格存储在数据框中,索引为timestamp
):
# fit an AR model
ar <- arima(
audusd %>% filter(timestamp < "2014-01-01") %>% select(close) %>% pull(),
order = c(10, 0, 0)
)
AR(10)模型的估计系数:
ar$coef
# ar1 ar2 ar3 ar4 ar5 ar6 ar7 ar8 ar9 ar10
# 0.9741941564 0.0228922865 0.0019821879 -0.0073977641 0.0045880720 0.0072364966 -0.0047513598 0.0003852733 -0.0048944003 0.0057283039
# intercept
# 0.6692288336
创建自定义函数,根据AR模型生成预测:
# fit an AR model and return the step-ahead prediction
# can fit a new model or return predictions given an existing set of coeffs and new data
# params:
# series: data to use to fit model or to predict on
# fixed: either NA or a vector of coeffs of same length as number of model parameters
# usage:
# fit a new model an return next prediction: series should consist of the data to be fitted, fixed should be NA
# fit_ar_predict(audusd$close[1:100000], order = c(10, 0, 0))
# predict using an existing set of coeffs: series and fixed should be same length as number of model parameters
# fit_ar_predict(audusd$close[100001:100010], order = c(10, 0, 0), fixed = ar$coef)
fit_ar_predict <- function(series, order = 10, fixed = NA) {
if(sum(is.na(fixed)) == 0) {
# make predictions using static coefficients
# arima(series, c(1, 0, 0)) fits an AR(1)
predict(arima(series, order = c(order, 0, 0), fixed = fixed), 1)$pred[1]
} else {
# fit a new model
predict(arima(series, order = c(order, 0, 0)), 1)$pred[1]
}
}
回溯检验的Zorro代码:
#include <r.h>
function run()
{
set(PLOTNOW);
setf(PlotMode, PL_FINE);
StartDate = 2014;
EndDate = 2015;
BarPeriod = 10;
LookBack = 10;
MaxLong = MaxShort = 1;
MonteCarlo = 0;
if(is(INITRUN))
{
// start R and source the kalman iterator function
if(!Rstart("ar.R", 2))
{
print("Error - can't start R session!");
quit();
}
}
asset("AUD/USD");
Spread = Commission = RollLong = RollShort = Slippage = 0;
// generate reverse price series (the order of Zorro series is opposite what you'd expect)
vars closes = rev(series(priceClose()));
// model parameters
int order = 10;
var coeffs[11] = {0.9793975109, 0.0095665978, 0.0025503174, 0.0013394797, 0.0060263045, -0.0023060104, -0.0022220192, 0.0006940781, 0.0011942208, 0.0037558386, 0.9509437891}; //note 1 extra coeff - intercept
if(!is(LOOKBACK)) {
// send function argument values to R
Rset("order", order);
Rset("series", closes, order);
Rset("fixed", coeffs, order+1);
// compute AR prediction and trade
var pred = Rd("fit_ar_predict(series = series, order = order, fixed = fixed)");
printf("\nCurrent: %.5f\nPrediction: %.5f", priceClose(), pred);
if(pred > priceClose())
enterLong();
else if(pred < priceClose())
enterShort();
}
}
由于每隔一分钟调用一次R函数进行预测,因此回溯检验要花一些时间。结果如下:
这与Ernie的回测结果(剔除成本)相当一致。不幸的是,该策略的交易频率太高,盈利无法弥补交易成本。从Zorro的回溯结果看到,每笔交易的平均盈利仅为0.1点。
能否降低交易频率?
交易成本是一个主要问题,所以从交易频率开始优化。
如果在更长的时间框架上看到偏自相关的证据,就可以降低交易次数,并持有头寸更长的时间。
首先,尝试10分钟K线:
# 10-minute partials
partial <- pacf(
audusd %>%
mutate(minute = minute(timestamp)) %>%
filter(minute %% 10 == 0) %>%
select(close) %>%
pull(),
lags = 20, plot = FALSE)
plot(partial[2:20], main = 'Ten minutely PACF')
然后是小时图K线:
# hourly partials
partial <- pacf(
audusd %>%
mutate(minute = minute(timestamp)) %>%
filter(minute == 0) %>%
select(close) %>%
pull(),
lags = 20, plot = FALSE)
plot(partial[2:20], main = 'Hourly PACF')
最后是日K线:
# daily partials
partial <- pacf(
audusd %>%
mutate(hour = hour(timestamp)) %>%
filter(hour == 0) %>%
select(close) %>%
pull(),
lags = 20, plot = FALSE)
plot(partial[2:20], main = 'Daily PACF')
随着使用更长的时间框架,价格序列的偏自相关性越来越弱。
我们可以用10分钟K线拟合AR(10)模型。遵循上面相同的过程,结果如下:
我们成功地将每笔交易的平均盈利提高到0.3个点,但仍远不能覆盖成本。
结论
将自回归模型应用到外汇交易是一个有趣的练习,从中得到一个意外的结论:即价格序列并不完全遵循随机游走。分析表明,外汇市场存在短期的均值回归,但利润太少以至于无法覆盖交易成本。
数据黑客:专注金融大数据,聚合全网最好的资讯和教程,提供开源数据接口。
我们聚合全网最优秀的资讯和教程:
- 金融大数据
- 机器学习/深度学习
- 量化交易
- 数据工程
- 编程语言,Python,R,Julia,Scala,SQL
我们提供开源数据接口:
- 下载国内和国外海量金融数据
- API接口,将数据整合到您的平台