【Mquant】6:构建价差套利(二)

发布时间 2023-11-07 14:42:08作者: mossloo

1. 上节回顾

【Mquant】5:构建价差套利(一)介绍了价差套利的原理和跨期套利的概念。同时,提到了价差套利存在的一定风险,并且介绍了跨期套利的原理和分类、投研分析和价差特征分析的方法。

2. 本节内容

本文将带领读者从零开始,逐步构建一个完整的价差实战套利策略。这将是一个循序渐进的过程,旨在帮助读者了解价差套利的核心概念和实施步骤。

首先重点讲解价差套利策略的构建过程。这将涉及选择适合价差套利的市场和交易品种,确定合适的套利时机和条件,并建立相应的交易规则和执行策略。使用一些常用的技术工具和指标,帮助读者进行价差分析和套利机会的识别。

在策略构建的过程中,本文将提供详细的实例和案例分析,以便读者能够更好地理解和应用所学知识。同时,作者还会强调风险管理的重要性,分享一些有效的风险控制方法,并提供实用的建议和技巧。

本文最后将引导读者进行模拟交易和实盘操作,以验证和优化所构建的价差套利策略。通过实际的交易实践,读者将能够更好地理解市场动态和实际执行的挑战,并逐步提升自身的交易技能和经验。

3. 统计套利

在统计套利中,常用的方法包括配对交易(Pairs Trading)均值回归策略(Mean Reversion)、协整关系交易(Cointegration Trading)等。这些方法基于统计学的原理,利用价格或资产之间的相对价差、均值偏离或协整关系等统计指标来确定交易信号和执行策略。

在实践中,统计套利中的某些策略可以包含价差套利的元素,例如配对交易和均值回归策略经常涉及价格差异的利用。同时,统计套利的一些方法和工具,如协整关系的分析和模型构建,也可以为价差套利提供理论支持和辅助分析。

3.1 构建均值回归策略

本文着手于构建一个均值回归策略,通过理论和实践相结合的方式,帮助读者快速构建一套属于自己的交易策略,构造一个均值回归策略涉及以下步骤:

  1. 选择资产:首先,选择您感兴趣的资产或市场,可以是股票、期货、外汇等。确保选择的资产存在明显的价格波动和均值回归的趋势。

  2. 确定均值:通过历史数据计算资产的均值。常见的方法是使用移动*均线(如简单移动*均线或指数加权移动*均线)来估计资产价格的均值。

  3. 计算偏离度:计算资产价格相对于均值的偏离度。可以使用标准差、百分位数或其他统计指标来度量价格的偏离程度。

  4. 确定交易信号:根据偏离度确定交易信号。当价格偏离均值超过一定阈值时,产生交易信号。例如,当价格偏离均值超过一个标准差时,可以认为价格过度偏离,产生反向交易信号。

  5. 确定交易规则:定义具体的交易规则,包括入场点、出场点和止损点。例如,当价格偏离均值达到一定程度时,进入反向头寸;当价格回归到均值附*时,*仓并获利。

  6. 风险管理:制定有效的风险管理策略,包括设置止损点、控制仓位大小和分散投资等。确保风险可控,并考虑交易成本和流动性等因素。

  7. 回测和优化:使用历史数据进行回测,评估策略的表现,并进行必要的优化。调整参数和交易规则,以提高策略的盈利能力和稳定性。

  8. 实盘交易:在回测和优化后,将策略应用到实盘交易中。始终密切监控市场情况和策略表现,并根据需要进行调整和优化。

4. 实践

4.1 选择交易标的

选择交易标的核心是确保选择的资产存在明显的价格波动和均值回归的趋势

  1. 使用统计指标来评估价格的波动性和均值回归的趋势。常用的指标包括标准差、*均绝对偏差(Mean Absolute Deviation)、波动率等。较高的波动性和明显的均值回归特征可能表明资产适合均值回归策略。

  2. 协整性分析:对于多个相关资产,可以进行协整性分析。协整关系是指一组资产的价格在长期内存在稳定的线性关系。如果资产之间存在协整关系,并且价格偏离协整关系时会发生均值回归,那么这些资产可能适合均值回归策略。

  3. 历史数据分析:通过对资产的历史价格数据进行分析,评估价格的波动性和均值回归的趋势。观察价格的波动范围、频率和幅度,以及价格是否有向均值回归的倾向。

在上节内容【Mquant】5:构建价差套利(一)基础上,这一章节我们对btc数据又进行了一系列的统计学分析,以说明均值回归策略适用于什么周期,用什么指标来量化。

import numpy as np
import pandas as pd

df = pd.read_csv("spread_data.csv")
# 计算价格波动性指标
std = np.std(df['spread'])  # 标准差
mad = np.mean(np.abs(df['spread'] - np.mean(df['spread'])))  # *均绝对偏差
volatility = std / np.mean(df['spread'])  # 波动率(标准差与均值的比率)

# 计算均值回归指标
mean = np.mean(df['spread'])  # 均值
rolling_mean = df['spread'].rolling(window=10).mean()  # 移动*均线

# 输出结果
print("价格波动性指标:")
print("标准差:", std)
print("*均绝对偏差:", mad)
print("波动率:", volatility)

print("\n均值回归指标:")
print("均值:", mean)

在这里插入图片描述
当波动率大于0.337或者绝对偏差大于94.8的时候开启均值回归策略。

4.2 确定交易规则

在这里插入图片描述
确定均值,常用的方法可以是ma均线,可以是机器学习拟合过去一段时间的回归曲线,可以是布林通道。确定交易信号,均值回归策略核心是认为相同品种的价差最终会走向价值回归,那么就在价差大的时候开仓,价差小的时候*仓。

4.3 策略代码

import pandas as pd
import plotly.express as px

# 读取数据,设置时间戳索引
df_data = pd.read_csv("spread_data.csv")
df_data.index = pd.DatetimeIndex(df_data["datetime"])
df = pd.DataFrame({"spread": df_data["spread"]})
df = df.resample("5min").last()
df.dropna(inplace=True)

# 设置策略参数
window = 20
dev = 3
# 计算均线和上下轨
df["ma"] = df["spread"].rolling(window).mean()
df["std"] = df["spread"].rolling(window).std()
df["up"] = df["ma"] + df["std"] * dev
df["down"] = df["ma"] - df["std"] * dev

# 抛弃NA数值
df.dropna(inplace=True)

# 计算目标仓位
target = 0
target_data = []

for ix, row in df.iterrows():
    # 没有仓位
    if not target:
        if row.spread >= row.up:
            target = -1
        elif row.spread <= row.down:
            target = 1
    # 多头仓位
    elif target > 0:
        if row.spread >= row.ma:
            target = 0
    # 空头仓位
    else:
        if row.spread <= row.ma:
            target = 0
        
    # 记录目标仓位
    target_data.append(target)

df["target"] = target_data
# 计算仓位
df["pos"] = df["target"].shift(1)
# 计算盈亏
df["change"] = df["spread"].diff()
df["pnl"] = df["change"] * df["pos"]
df["balance"] = df["pnl"].cumsum()

# 绘制净值曲线
px.line(df["balance"])

在这里插入图片描述

从目前回测曲线上来看,这个策略表现还不错,但是没有计算手续费和滑点,为了更精准的回测出我们构建的策略表现到底如何,下面采用veighna自带的价差交易回测引擎来进行回测。

4.4 策略回测

  1. 在vnpy_spreadtrading(位置位于:External Libraries\site-packages)目录下的strategies下创建boll_spread_strategy.py策略文件
    在这里插入图片描述
from vnpy.trader.utility import BarGenerator, ArrayManager
from vnpy_spreadtrading import (
    SpreadStrategyTemplate,
    SpreadAlgoTemplate,
    SpreadData,
    OrderData,
    TradeData,
    TickData,
    BarData
)
from vnpy.trader.constant import Interval


class BollSpreadStrategy(SpreadStrategyTemplate):
    """"""

    author = "mossloo"

    ma_window = 20
    ma_dev = 3
    max_pos = 1
    payup = 0.001
    interval = 5

    spread_pos = 0.0
    ma_up = 0.0
    ma_down = 0.0
    ma = 0.0

    parameters = [
        "ma_window",
        "ma_dev",
        "max_pos",
        "payup",
        "interval"
    ]
    variables = [
        "spread_pos",
        "ma_up",
        "ma_down",
        "ma"
    ]

    def __init__(
            self,
            strategy_engine,
            strategy_name: str,
            spread: SpreadData,
            setting: dict
    ):
        """"""
        super().__init__(
            strategy_engine, strategy_name, spread, setting
        )

        self.bg = BarGenerator(self.on_spread_bar, window=5, on_window_bar=self.on_5min_spread_bar,
                               interval=Interval.MINUTE)
        self.am = ArrayManager()

    def on_init(self):
        """
        Callback when strategy is inited.
        """
        self.write_log("策略初始化")

        self.load_bar(10)

    def on_start(self):
        """
        Callback when strategy is started.
        """
        self.write_log("策略启动")

    def on_stop(self):
        """
        Callback when strategy is stopped.
        """
        self.write_log("策略停止")

        self.put_event()

    def on_spread_data(self):
        """
        Callback when spread price is updated.
        """
        tick = self.get_spread_tick()
        self.on_spread_tick(tick)

    def on_spread_tick(self, tick: TickData):
        """
        Callback when new spread tick data is generated.
        """
        self.bg.update_tick(tick)

    def on_spread_bar(self, bar: BarData):
        self.bg.update_bar(bar)

    def on_5min_spread_bar(self, bar: BarData):
        """
        Callback when spread bar data is generated.
        """
        self.stop_all_algos()

        self.am.update_bar(bar)
        if not self.am.inited:
            return

        self.ma = self.am.sma(self.ma_window)
        dev = self.am.std(self.ma_window)
        self.ma_up = self.ma_dev * dev + self.ma
        self.ma_down = self.ma - self.ma_dev * dev

        if not self.spread_pos:
            if bar.close_price >= self.ma_up:
                self.start_short_algo(
                    bar.close_price - 10,
                    self.max_pos,
                    payup=self.payup,
                    interval=self.interval
                )
            elif bar.close_price <= self.ma_down:
                self.start_long_algo(
                    bar.close_price + 10,
                    self.max_pos,
                    payup=self.payup,
                    interval=self.interval
                )
        elif self.spread_pos < 0:
            if bar.close_price <= self.ma:
                self.start_long_algo(
                    bar.close_price + 10,
                    abs(self.spread_pos),
                    payup=self.payup,
                    interval=self.interval
                )
        else:
            if bar.close_price >= self.ma:
                self.start_short_algo(
                    bar.close_price - 10,
                    abs(self.spread_pos),
                    payup=self.payup,
                    interval=self.interval
                )

        self.put_event()

    def on_spread_pos(self):
        """
        Callback when spread position is updated.
        """
        self.spread_pos = self.get_spread_pos()
        self.put_event()

    def on_spread_algo(self, algo: SpreadAlgoTemplate):
        """
        Callback when algo status is updated.
        """
        pass

    def on_order(self, order: OrderData):
        """
        Callback when order status is updated.
        """
        pass

    def on_trade(self, trade: TradeData):
        """
        Callback when new trade data is received.
        """
        pass

    def stop_open_algos(self):
        """"""
        if self.buy_algoid:
            self.stop_algo(self.buy_algoid)

        if self.short_algoid:
            self.stop_algo(self.short_algoid)

    def stop_close_algos(self):
        """"""
        if self.sell_algoid:
            self.stop_algo(self.sell_algoid)

        if self.cover_algoid:
            self.stop_algo(self.cover_algoid)

  1. 继续打开jupyter notebook
from vnpy.trader.optimize import OptimizationSetting
from vnpy_spreadtrading.backtesting import BacktestingEngine
from vnpy_spreadtrading.strategies.boll_spread_strategy import (
    BollSpreadStrategy
)
from vnpy_spreadtrading.base import LegData, SpreadData
from datetime import datetime
from vnpy.trader.constant import Interval

symbol_1 = "BTCUSDT_240329.BINANCE"
symbol_2 = "BTCUSDT_231229.BINANCE"

spread = SpreadData(
    name="BTC-Spread",
    legs=[LegData(symbol_1), LegData(symbol_2)],
    variable_symbols={"A": symbol_1, "B": symbol_2},
    variable_directions={"A": 1, "B": -1},
    price_formula="A-B",
    trading_multipliers={symbol_1: 1, symbol_2: 1},
    active_symbol=symbol_1,
    min_volume=1,
    compile_formula=False                          # 回测时不编译公式,compile_formula传False,从而支持多进程优化
)

engine = BacktestingEngine()
engine.set_parameters(
    spread=spread,
    interval=Interval.MINUTE,
    start=datetime(2021, 6, 10),
    end=datetime(2023, 11, 7),
    rate=0.0004,
    slippage=0.02,
    size=1,
    pricetick=0.02,
    capital=1_000_000,
)

engine.add_strategy(BollSpreadStrategy, {})
engine.load_data()
engine.run_backtesting()
df = engine.calculate_result()
engine.calculate_statistics()
engine.show_chart()

在这里插入图片描述
在这里插入图片描述
对于这个策略而言,我们亏钱的原因在于手续费太高了,由于我们策略使用的是市价单,加密市场对于市价单收取的手续费要远远高于限价单,所以这个策略的手续费在万分之四左右,如果能手续费降低到万分之一,加上百分之五十的返佣,到万分之0.5,那么这个策略的夏普比能到7.83。
在这里插入图片描述
策略还可以进行参数优化,找到参数*原地带,后续选择较优的参数跑模拟盘验证。

setting = OptimizationSetting()
setting.set_target("sharpe_ratio")
setting.add_parameter("ma_window", 10, 30, 1)
setting.add_parameter("ma_dev", 1, 3, 1)

engine.run_ga_optimization(setting)

5.实盘交易

很显然,我们上面的程序并不能直接上实盘交易,但如果我们手续费非常低的情况下如万分之一,或者遇到极端行情,某些币种现货和期货之间的价差非常大,比如前段时间的luna,还有这段时间的mask,只要我们设置好程序,仍然可以在极端行情下赚取到稳定的资金,这也是我们学习量化交易的原因,只有长久稳定的赚钱,才能在市场立于不败之地。下一章节,我将带领大家优化策略,回测,实盘部署。

本文由mdnice多*台发布