FMZ量化交易一个运用SPY与IWM之间均值回归的日内交易策略
admin
2023-07-31 14:23:23
0

目前不清退的交易所推荐:

1、全球第二大交易所OKX欧意

国区邀请链接: https://www.myts3cards.com/cn/join/1837888   币种多,交易量大!

国际邀请链接:https://www.okx.com/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!

2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183

全球最大交易所币安,国区邀请链接:https://accounts.binance.com/zh-CN/register?ref=16003031  币安注册不了IP地址用香港,居住地选香港,认证照旧,邮箱推荐如gmail、outlook。支持币种多,交易安全!

买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3  CoinMarketCap前五的交易所,注册友好操简单快捷!

FMZ量化交易平台邀请链接:https://www.fmz.com/

在本文中,我们将编写一个日内交易策略。它将使用经典的交易理念,即“均值回归交易对”。在这个例子中,我们将利用两只交易型开放式指数基金(ETF),SPY和IWM,它们在纽约证券交易所(NYSE)交易,并试图代表美国股市指数,分别是“S&P500和“Russell 2000\”。

该策略通过做多一种ETF和做空另一种ETF来创造一个“利差”。多空比可以用许多方式定义,例如利用统计协整时间序列的方法。在这种场景中,我们将通过滚动线性回归计算SPY和IWM之间的对冲比率。这将允许我们在SPY和IWM之间创建“利差”,其被标准化为z-score。当z-score超过某一阈值时,将产生交易信号,因为我们相信这个“利差”将恢复到均值。

该策略的基本原理是,SPY和IWM都大致代表了同样的市场情况,即一组大型和小型美国公司的股价表现。前提是,如果接受价格的“均值回归”理论,那么它总有回归的时候,因为“事件”可能会在很短的时间内分别影响S&P500和Russell 2000,但他们之间的“利差”总会回归到正常的均值,且两者长期的价格序列总是协整(cointegrated)的。

策略

该策略按以下步骤执行:

数据 – 从2007年4月到2014年2月,分别获得SPY和IWM的1分钟k线图。

处理 – 把数据正确对齐,并且删除相互有缺少的k线。(只要有一边缺少,则两边都删除)

差价 – 两只ETF之间的对冲比率采用滚动线性回归计算。定义为使用回溯窗口的β回归系数,该回溯窗口向前移动1根k线并重新计算回归系数。因此,对冲比例βi,bi根K线是通过计算跨越点从bi-1-k到bi-1,以用来回溯k线。

Z-Score – 标准利差的值是以通常的方式来计算。这意味着减去利差的均值(样本)并除以利差的标准差(样本)。这样做的理由是使阈值参数更容易理解,因为Z-Score是一个无量纲的量(dimensionless quantity)。我有意在计算中引入了“前视偏差”,以显示它会有多么微妙。试试看吧!

交易 – 当负z-score值下降到低于预定(或后优化)阈值时产生做多信号,而做空信号则与此相反。当z-score的绝对值降至额外阈值以下时,将生成平仓信号。对于这个策略,我(有点随意)选择了|z| = 2作为开仓阈值,|z| = 1作为平仓阈值。假设均值回归在利差中发挥作用,以上这些将有望捕捉到这种套利关系并提供不错的利润。

也许深入理解策略的最佳方式是实际实现它。以下部分详细介绍了用于实现此均值回归策略的完整Python代码(单个文件)。我已经添加了详细的代码注释,以帮助你更好的理解。

Python实现

与所有Python/pandas教程一样,必须按照本教程中描述的Python环境来设置。设置完成后,第一项任务是导入必要的Python库。这个对于使用matplotlib和pandas是必需的。

我使用的特定库版本如下:

Python – 2.7.3
NumPy – 1.8.0
pandas – 0.12.0
matplotlib – 1.1.0

让我们继续并导入这些库:

# mr_spy_iwm.py

import matplotlib.pyplot as plt
import numpy as np
import os, os.path
import pandas as pd

以下函数create_pairs_dataframe导入两个包含两个symbol的日内k线的CSV文件。在我们的例子中,这将是SPY和IWM。然后它会创建一个单独的“数据帧对”,这个“数据帧对”将使用两个原始文件的索引。由于错过的交易和错误,它们的时间戳可能会有所不同。这是使用像pandas这样的数据分析库的主要好处之一。我们以非常有效的方式处理“样板”代码。

# mr_spy_iwm.py
def create_pairs_dataframe(datadir, symbols):
    \"\"\"Creates a pandas DataFrame containing the closing price
    of a pair of symbols based on CSV files containing a datetime
    stamp and OHLCV data.\"\"\"
 
    # Open the individual CSV files and read into pandas DataFrames
    print \"Importing CSV data...\"
    sym1 = pd.io.parsers.read_csv(os.path.join(datadir, \'%s.csv\' % symbols[0]),
                                  header=0, index_col=0, 
                                  names=[\'datetime\',\'open\',\'high\',\'low\',\'close\',\'volume\',\'na\'])
    sym2 = pd.io.parsers.read_csv(os.path.join(datadir, \'%s.csv\' % symbols[1]),
                                  header=0, index_col=0, 
                                  names=[\'datetime\',\'open\',\'high\',\'low\',\'close\',\'volume\',\'na\'])
 
    # Create a pandas DataFrame with the close prices of each symbol
    # correctly aligned and dropping missing entries
    print \"Constructing dual matrix for %s and %s...\" % symbols    
    pairs = pd.DataFrame(index=sym1.index)
    pairs[\'%s_close\' % symbols[0].lower()] = sym1[\'close\']
    pairs[\'%s_close\' % symbols[1].lower()] = sym2[\'close\']
    pairs = pairs.dropna()
    return pairs

下一步是在SPY和IWM之间进行滚动线性回归。在这个场景中,IWM是预测器(\’x\’),SPY是响应(\’y\’)。我设置了一个100根k线的默认回溯窗口。如上所述,这是策略的参数。为了使策略被认为是稳健的,我们理想地希望能看到一份返回报告在回溯期呈现出凸函数状态(或其他性能度量表现)。因此,在代码的后期阶段,我们将通过改变范围内的回顾期来进行灵敏度分析。

在计算完SPY-IWM的线性回归模型中滚动β系数,将其添加到DataFrame对并删除空行。这就构建好了第一组K线,其等于回溯长度的修剪度量。然后,我们创建了两只ETF的利差,分别为SPY的单位和IWM的-βi的单位。显然,这不是现实的情况,因为我们正在采用少量的IWM,这在实际实施中是不可能的。

最后,我们创建利差的z-score,通过减去利差的平均值并通过标准化利差的标准差来计算。需要注意的是这里有一个相当微妙的“前视偏差”存在。我故意将其留在代码中,因为我想强调在研究中犯这样的错误是多么容易。计算整个利差时间序列的平均值和标准差。如果这是为了反映真实的历史准确性,那么这些信息将无法获得,因为它隐含地利用了未来的信息。因此,我们应该使用滚动均值和stdev来计算z-score。

# mr_spy_iwm.py
 
def calculate_spread_zscore(pairs, symbols, lookback=100):
    \"\"\"Creates a hedge ratio between the two symbols by calculating
    a rolling linear regression with a defined lookback period. This
    is then used to create a z-score of the \'spread\' between the two
    symbols based on a linear combination of the two.\"\"\"
     
    # Use the pandas Ordinary Least Squares method to fit a rolling
    # linear regression between the two closing price time series
    print \"Fitting the rolling Linear Regression...\"
    model = pd.ols(y=pairs[\'%s_close\' % symbols[0].lower()], 
                   x=pairs[\'%s_close\' % symbols[1].lower()],
                   window=lookback)
 
    # Construct the hedge ratio and eliminate the first 
    # lookback-length empty/NaN period
    pairs[\'hedge_ratio\'] = model.beta[\'x\']
    pairs = pairs.dropna()
 
    # Create the spread and then a z-score of the spread
    print \"Creating the spread/zscore columns...\"
    pairs[\'spread\'] = pairs[\'spy_close\'] - pairs[\'hedge_ratio\']*pairs[\'iwm_close\']
    pairs[\'zscore\'] = (pairs[\'spread\'] - np.mean(pairs[\'spread\']))/np.std(pairs[\'spread\'])
    return pairs

在create_long_short_market_signals中,创建交易信号。这些是通过z-score的值超过阈值来计算的。当z-score的绝对值小于或等于另一个(幅度较小)阈值时,给出平仓信号。

为了实现这种情况,有必要为每根k线确立交易策略是“开仓”还是“平仓”。 long_market和short_market是定义的两个变量,用于跟踪多头和空头头寸。不幸的是,与向量化方法相比,以迭代方式编程更加简单,因此计算起来很慢。尽管1分钟的k线图每个CSV文件需要约700,000个数据点,但在我的旧台式机上计算它仍然相对较快!

要迭代一个pandas DataFrame(这无疑是一个不常见的操作),有必要使用iterrows方法,它提供了一个迭代的生成器:

# mr_spy_iwm.py
 
def create_long_short_market_signals(pairs, symbols, 
                                     z_entry_threshold=2.0, 
                                     z_exit_threshold=1.0):
    \"\"\"Create the entry/exit signals based on the exceeding of 
    z_enter_threshold for entering a position and falling below
    z_exit_threshold for exiting a position.\"\"\"
 
    # Calculate when to be long, short and when to exit
    pairs[\'longs\'] = (pairs[\'zscore\'] <= -z_entry_threshold)*1.0
    pairs[\'shorts\'] = (pairs[\'zscore\'] >= z_entry_threshold)*1.0
    pairs[\'exits\'] = (np.abs(pairs[\'zscore\']) <= z_exit_threshold)*1.0
 
    # These signals are needed because we need to propagate a
    # position forward, i.e. we need to stay long if the zscore
    # threshold is less than z_entry_threshold by still greater
    # than z_exit_threshold, and vice versa for shorts.
    pairs[\'long_market\'] = 0.0
    pairs[\'short_market\'] = 0.0
 
    # These variables track whether to be long or short while
    # iterating through the bars
    long_market = 0
    short_market = 0
 
    # Calculates when to actually be \"in\" the market, i.e. to have a
    # long or short position, as well as when not to be.
    # Since this is using iterrows to loop over a dataframe, it will
    # be significantly less efficient than a vectorised operation,
    # i.e. slow!
    print \"Calculating when to be in the market (long and short)...\"
    for i, b in enumerate(pairs.iterrows()):
        # Calculate longs
        if b[1][\'longs\'] == 1.0:
            long_market = 1            
        # Calculate shorts
        if b[1][\'shorts\'] == 1.0:
            short_market = 1
        # Calculate exists
        if b[1][\'exits\'] == 1.0:
            long_market = 0
            short_market = 0
        # This directly assigns a 1 or 0 to the long_market/short_market
        # columns, such that the strategy knows when to actually stay in!
        pairs.ix[i][\'long_market\'] = long_market
        pairs.ix[i][\'short_market\'] = short_market
    return pairs

在这个阶段,我们更新了pairs以包含实际的多,空信号,这使我们能够确定我们是否需要开仓。现在我们需要创建一个投资组合来跟踪头寸的市场价值。第一项任务是创建一个结合了多头信号和空头信号的位置列。这将包含一列元素从(1,0,-1),其中1表示多头仓位,0代表无仓位(应该平仓),以及-1代表空头仓位。sym1和sym2列表示每根k线结束时SPY和IWM位置的市场价值。

一旦创建了ETF市场价值,我们将它们相加以在每根k线结束时产出总市值。然后通过该对象的pct_change方法将其转换为返回值。后续的代码行清除了错误的条目(NaN和inf元素),最后计算了完整的权益曲线。

# mr_spy_iwm.py
 
def create_portfolio_returns(pairs, symbols):
    \"\"\"Creates a portfolio pandas DataFrame which keeps track of
    the account equity and ultimately generates an equity curve.
    This can be used to generate drawdown and risk/reward ratios.\"\"\"
     
    # Convenience variables for symbols
    sym1 = symbols[0].lower()
    sym2 = symbols[1].lower()
 
    # Construct the portfolio object with positions information
    # Note that minuses to keep track of shorts!
    print \"Constructing a portfolio...\"
    portfolio = pd.DataFrame(index=pairs.index)
    portfolio[\'positions\'] = pairs[\'long_market\'] - pairs[\'short_market\']
    portfolio[sym1] = -1.0 * pairs[\'%s_close\' % sym1] * portfolio[\'positions\']
    portfolio[sym2] = pairs[\'%s_close\' % sym2] * portfolio[\'positions\']
    portfolio[\'total\'] = portfolio[sym1] + portfolio[sym2]
 
    # Construct a percentage returns stream and eliminate all 
    # of the NaN and -inf/+inf cells
    print \"Constructing the equity curve...\"
    portfolio[\'returns\'] = portfolio[\'total\'].pct_change()
    portfolio[\'returns\'].fillna(0.0, inplace=True)
    portfolio[\'returns\'].replace([np.inf, -np.inf], 0.0, inplace=True)
    portfolio[\'returns\'].replace(-1.0, 0.0, inplace=True)
 
    # Calculate the full equity curve
    portfolio[\'returns\'] = (portfolio[\'returns\'] + 1.0).cumprod()
    return portfolio

主函数将它们结合在一起。日内CSV文件位于datadir路径。请务必修改以下代码以指向你的特定目录。

为了确定策略对lookback周期的敏感程度,有必要计算一系列lookback的性能指标。我选择了投资组合的最终总回报百分比作为绩效指标和lookback范围[50,200],增量为10.你可以在下面的代码中看到,之前的函数包含在此范围内的for循环中,其他阈值保持不变。· 最后的任务是使用matplotlib创建lookbacks对比returns的折线图:

# mr_spy_iwm.py
 
if __name__ == \"__main__\":
    datadir = \'/your/path/to/data/\'  # Change this to reflect your data path!
    symbols = (\'SPY\', \'IWM\')
 
    lookbacks = range(50, 210, 10)
    returns = []
 
    # Adjust lookback period from 50 to 200 in increments
    # of 10 in order to produce sensitivities
    for lb in lookbacks: 
        print \"Calculating lookback=%s...\" % lb
        pairs = create_pairs_dataframe(datadir, symbols)
        pairs = calculate_spread_zscore(pairs, symbols, lookback=lb)
        pairs = create_long_short_market_signals(pairs, symbols, 
                                                z_entry_threshold=2.0, 
                                                z_exit_threshold=1.0)
 
        portfolio = create_portfolio_returns(pairs, symbols)
        returns.append(portfolio.ix[-1][\'returns\'])
 
    print \"Plot the lookback-performance scatterchart...\"
    plt.plot(lookbacks, returns, \'-o\')
    plt.show()

6f36e049015ac5e073b06f36e049015ac5e073b0

现在可以看到lookbacks与returns的图表。请注意,lookback有一个“全局”最大值,等于110根k线。如果我们看到lookbacks与returns无关的情况,这是因为:

SPY-IWM线性回归对冲比lookback期敏感性分析

没有向上倾斜的利润曲线,任何回测文章都是不完整的!因此,如果你希望绘制累积利润回报与时间的曲线,则可以使用以下代码。它将绘制从lookback参数研究中生成的最终投资组合。因此,有必要根据你希望可视化的图表选择lookback。该图表还绘制了同期SPY的回报以帮助比较:

# mr_spy_iwm.py
 
    # This is still within the main function
    print \"Plotting the performance charts...\"
    fig = plt.figure()
    fig.patch.set_facecolor(\'white\')
 
    ax1 = fig.add_subplot(211,  ylabel=\'%s growth (%%)\' % symbols[0])
    (pairs[\'%s_close\' % symbols[0].lower()].pct_change()+1.0).cumprod().plot(ax=ax1, color=\'r\', lw=2.)
 
    ax2 = fig.add_subplot(212, ylabel=\'Portfolio value growth (%%)\')
    portfolio[\'returns\'].plot(ax=ax2, lw=2.)
 
    fig.show()

以下权益曲线图表的lookback期为100天:

6f02fae6f9d2dc6c7c626f02fae6f9d2dc6c7c62

SPY-IWM线性回归对冲比lookback期敏感性分析

请注意,在金融危机期间,2009年SPY的缩减幅度很大。该策略在此阶段也处于动荡期。另请注意,由于SPY在此期间的强烈趋势性质反映了标准普尔500指数,去年业绩有所恶化。

请注意,在计算z-score的利差时,我们仍然需要考虑“前视偏差”。此外,所有这些计算都是在没​​有交易成本的情况下进行的。一旦考虑到这些因素,这种策略肯定会表现得很差。手续费和滑点目前都是未确定的。此外,该策略是以ETF的小数单位进行交易,这也是非常不现实的。

在以后的文章中,我们将创造一个更为复杂的事件驱动backtester将会把以上这些因素考虑在内,使我们在资金曲线和性能指标表现出更多的信心。

FMZ量化交易平台邀请链接:https://www.fmz.com/

全球最大交易所币安,国区邀请链接:https://accounts.binance.com/zh-CN/register?ref=16003031  币安注册不了IP地址用香港,居住地选香港,认证照旧,邮箱推荐如gmail、outlook。支持币种多,交易安全!

买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3  CoinMarketCap前五的交易所,注册友好操简单快捷!

目前不清退的交易所推荐:

1、全球第二大交易所OKX欧意,邀请链接:https://www.myts3cards.com/cn/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!。

2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183

买好币上币库:https://www.kucoin.com/r/1f7w3

火必所有用户现在可用了,但是要重新注册账号火币:https://www.huobi.com

全球最大交易所币安,

国区邀请链接:https://accounts.suitechsui.mobi/zh-CN/register?ref=16003031 支持86手机号码,网页直接注册。

相关内容

热门资讯

Windows 11 和 10... Windows 11/10 文件夹属性中缺少共享选项卡 – 已修复 1.检查共享选项卡是否可用 右键...
事件 ID 7034:如何通过... 点击进入:ChatGPT工具插件导航大全 服务控制管理器 (SCM) 负责管理系统上运行的服务的活动...
Hive OS LOLMine... 目前不清退的交易所推荐: 1、全球第二大交易所OKX欧意 国区邀请链接: https://www.m...
Radmin VPN Wind... Radmin VPN 是一款免费且用户友好的软件,旨在牢固地连接计算机以创建一个有凝聚力的虚拟专用网...
如何修复 Steam 内容文件... Steam 内容文件锁定是当您的 Steam 文件无法自行更新时出现的错误。解决此问题的最有效方法之...
Hive OS 部署 PXE ... 目前不清退的交易所推荐: 1、全球第二大交易所OKX欧意 国区邀请链接: https://www.m...
如何在Instagram上扫描... 如何在Instagram上扫描名称标签/ QR? 总而言之,您可以通过大约四种不同的方法来扫描这些I...
在 Windows 11 中打... 什么是链路状态电源管理? 您可以在系统控制面板的电源选项中看到链接状态电源管理。它是 PCI Exp...
farols1.1.501.0... faro ls 1.1.501.0(64bit)可以卸载,是一款无需连接外部PC机或笔记本计算机即可...
Hive OS 新建飞行表的方... 目前不清退的交易所推荐: 1、全球第二大交易所OKX欧意 国区邀请链接: https://www.m...