[Mquant] 6: Constructing spread arbitrage (2)


1. Review of the previous section

[Mquant] 5: Constructing Spread Arbitrage (1)Introduces the principle of spread arbitrage and the concept of intertemporal arbitrage. At the same time, certain risks in spread arbitrage were mentioned, and the principles and classification of intertemporal arbitrage, investment research analysis, and spread characteristic analysis methods were introduced.

2. Contents of this section

This article will lead readers to gradually build a complete practical spread arbitrage strategy from scratch. This will be a step-by-step process designed to help readers understand the core concepts and implementation steps of spread arbitrage.

First, we will focus on the construction process of the spread arbitrage strategy. This will involve selecting markets and trading varieties suitable for spread arbitrage, determining suitable arbitrage timing and conditions, and establishing corresponding trading rules and execution strategies. Use some commonly used technical tools and indicators to help readers conduct spread analysis and identify arbitrage opportunities.

In the process of strategy construction, this article will provide detailed examples and case analysis so that readers can better understand and apply the knowledge they have learned. At the same time, the author will also emphasize the importance of risk management, share some effective risk control methods, and provide practical suggestions and techniques.

Finally, this article will guide readers to conduct simulated trading and real trading operations to verify and optimize the constructed spread arbitrage strategy. Through actual trading practice, readers will be able to better understand market dynamics and actual execution challenges, and gradually improve their trading skills and experience.

3. Statistical arbitrage

In statistical arbitrage, commonly used methods includePairs Trading, mean reversion Strategy (Mean Reversion), Cointegration Trading (Cointegration Trading), etc. These methods are based on statistical principles and use statistical indicators such as prices or relative spreads between assets, mean deviations, or cointegration relationships to determine trading signals and execution strategies.

In practice, some strategies within statistical arbitrage can contain elements of spread arbitrage, such as pairs trading and mean reversion strategies that often involve the exploitation of price differences. At the same time, some methods and tools of statistical arbitrage, such as the analysis and model construction of cointegration relationships, can also provide theoretical support and auxiliary analysis for spread arbitrage.

3.1 Constructing a mean reversion strategy

This article sets out to construct a mean reversion strategy. Through a combination of theory and practice, it helps readers quickly construct a set of their own trading strategies. Constructing a mean reversion strategy involves the following steps:

  1. Select an asset: First, select the asset or market you are interested in, which can be stocks, futures, foreign exchange, etc. Make sure the assets you choose have obvious price fluctuations and mean reversion trends.

  2. Determine the mean value: Calculate the mean value of an asset using historical data. A common approach is to use a moving average (such as a simple moving average or an exponentially weighted moving average) to estimate the mean of an asset's price.

  3. Calculate deviation: Calculate the deviation of asset price from the mean. Standard deviation, percentiles, or other statistical measures can be used to measure how far prices deviate.

  4. Determine trading signals: Determine trading signals based on deviation. When the price deviates from the mean by more than a certain threshold, a trading signal is generated. For example, when prices deviate from the mean by more than one standard deviation, the price can be considered excessively deviated, generating a contrarian trading signal.

  5. Determine trading rules: Define specific trading rules, including entry points, exit points, and stop loss points. For example, when the price deviates from the mean to a certain extent, enter a reverse position; when the price returns to near the mean, close the position and make a profit.

  6. Risk management: Develop effective risk management strategies, including setting stop loss points, controlling position size, and diversifying investments. Ensure risks are controllable and consider factors such as transaction costs and liquidity.

  7. Backtesting and optimization: Use historical data for backtesting to evaluate the performance of the strategy and make necessary optimizations. Adjust parameters and trading rules to improve the profitability and stability of your strategy.

  8. Live trading: After backtesting and optimization, apply the strategy to live trading. Always closely monitor market conditions and strategy performance and make adjustments and optimizations as needed.

4. Practice

4.1 Select transaction target

The core of choosing a trading target is to ensure that the selected assets have obvious price fluctuations and mean reversion trends.

  1. Use statistical indicators to evaluate price volatility and mean reversion trends. Commonly used indicators include standard deviation, mean absolute deviation (Mean Absolute Deviation), volatility, etc. Higher volatility and clear mean reversion characteristics may indicate that the asset is suitable for a mean reversion strategy.

  2. Cointegration analysis: For multiple related assets, cointegration analysis can be performed. Cointegration relationship means that the prices of a group of assets have a stable linear relationship in the long term. If there is a cointegration relationship between assets and mean reversion occurs when prices deviate from the cointegration relationship, then these assets may be suitable for a mean reversion strategy.

  3. Historical data analysis: Evaluate price volatility and mean reversion trends by analyzing the historical price data of assets. Observe the range, frequency and magnitude of price fluctuations, and whether prices tend to revert to the mean.

Based on the content of the previous section[Mquant] 5: Constructing spread arbitrage (1), in this chapter we have another look at the btc data A series of statistical analyzes were conducted to illustrate for what period the mean reversion strategy is suitable and what indicators are used to quantify it.

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)

Insert image description here
The mean reversion strategy is enabled when the volatility is greater than 0.337 or the absolute deviation is greater than 94.8.

4.2 Determine trading rules

Insert image description here
To determine the mean, commonly used methods can be MA moving average, machine learning to fit the regression curve of the past period, or Bollinger Bands. To determine the trading signal, the core of the mean reversion strategy is to believe that the price difference of the same product will eventually return to value, so open a position when the price difference is large and close the position when the price difference is small.

4.3 Strategy code

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"])

Insert image description here

Judging from the current backtest curve, this strategy performs well, but the handling fees and slippage are not calculated. In order to more accurately backtest the performance of the strategy we constructed, we will use Veighna’s own spread trading backtest engine below. Conduct backtesting.

4.4 Strategy backtesting

  1. Create the boll_spread_strategy.py strategy file under strategies in the vnpy_spreadtrading (location: External Libraries\site-packages) directory
    Insert image description here
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. Continue to open 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()

Insert image description here
Insert image description here
For this strategy, the reason we lose money is that the handling fee is too high. Since our strategy uses market orders, the handling fees charged by the crypto market for market orders are much higher than for limit orders. Therefore, the handling fee of this strategy is about 4/10,000. If the handling fee can be reduced to 1/10,000, plus a 50% rebate, it can reach 0.5/10,000, then the Sharpe ratio of this strategy can reach 7.83.
Insert image description here
The strategy can also perform parameter optimization to find parameter plain areas, and then select better parameters to run simulation disk verification.

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. Real offer trading

Obviously, our above program cannot directly implement real-time transactions, but if our handling fee is very low, such as one ten thousandth, or if we encounter extreme market conditions, the price difference between the spot and futures prices of certain currencies will be very large. , such as luna some time ago, and mask during this period, as long as we set up the program well, we can still earn stable funds under extreme market conditions. This is also the reason why we learn quantitative trading. Only by making money stably for a long time can we Be invincible in the market. In the next chapter, I will lead you to optimize strategies, backtest, and implement real-time deployment.

Guess you like

Origin blog.csdn.net/m0_58598240/article/details/134259418