vn.py官网
开源量化社区
Member
avatar
加入于:
帖子: 263
声望: 58

1. PortfolioStrategy自带的策略PairTradingStrategy

这个组合策略实质上是价差交易。
我理解其本意是选择两个相关的合约进行价差交易,它有两个腿:leg1和leg2,将其leg1和leg2按照leg1_ratio和leg2_ratio设定的比例进行配比做出价差K线。
该组合的开仓信号为:
价差K线突破BOLL上轨或者上轨时,卖出leg1,买入leg2
价差K线突破BOLL上轨或者下轨时,买入leg1,卖出leg2
该组合的平仓信号为:
leg1有持多仓并且价差K线在BOLL中轨之上时,分别平仓leg1和leg2
leg1有持空仓并且价差K线在BOLL中轨之下时,分别平仓leg1和leg2

2. 当leg1_ratio和leg2_ratio不相等的时候就错误了:

当leg1_ratio和leg2_ratio相等的时候,一起都没有毛病。
当leg1_ratio和leg2_ratio不相等的时候,就有问题了。你会发现它们开仓的数量总是一样多,这是不对的。

比如leg1为i2109.DCE, leg2为rb2110.DCE。
我们知道i2109的合约乘数是100,atr=2.7,而rb2109的合约乘数是10,atr=7.7,
leg2_ratio/leg1_ratio =(2.7100)/(7.710) ≈ 7/2
那么我们应该配比leg1_ratio=2和leg2_ratio=7,这意味着i2109.DCE开仓2手,就需要反向开仓rb2110.DCE 开仓7手。
因为价差的计算是这样的:

self.current_spread = (
            leg1_bar.close_price * self.leg1_ratio - leg2_bar.close_price * self.leg2_ratio
        )

而实际开仓却是(1手,-1手)或(-1手,1手)的组合,与价差计算不符合。

3. 做如下修改:

代码如下,修改部分见注释:

from typing import List, Dict
from datetime import datetime

import numpy as np

from vnpy.app.portfolio_strategy import StrategyTemplate, StrategyEngine
from vnpy.trader.utility import BarGenerator
from vnpy.trader.object import TickData, BarData


class PairTradingStrategy(StrategyTemplate):
    """"""

    author = "用Python的交易员"

    price_add = 5
    boll_window = 20
    boll_dev = 2
    # fixed_size = 1  # 没有使用,去掉
    leg1_ratio = 1
    leg2_ratio = 1

    leg1_symbol = ""
    leg2_symbol = ""
    current_spread = 0.0
    boll_mid = 0.0
    boll_down = 0.0
    boll_up = 0.0

    parameters = [
        "price_add",
        "boll_window",
        "boll_dev",
        # "fixed_size",   # 没有使用,去掉
        "leg1_ratio",
        "leg2_ratio",
    ]
    variables = [
        "leg1_symbol",
        "leg2_symbol",
        "current_spread",
        "boll_mid",
        "boll_down",
        "boll_up",
    ]

    def __init__(
        self,
        strategy_engine: StrategyEngine,
        strategy_name: str,
        vt_symbols: List[str],
        setting: dict
    ):
        """"""
        super().__init__(strategy_engine, strategy_name, vt_symbols, setting)

        self.bgs: Dict[str, BarGenerator] = {}
        self.targets: Dict[str, int] = {}
        self.last_tick_time: datetime = None

        self.spread_count: int = 0
        self.spread_data: np.array = np.zeros(100)

        # Obtain contract info
        self.leg1_symbol, self.leg2_symbol = vt_symbols

        def on_bar(bar: BarData):
            """"""
            pass

        for vt_symbol in self.vt_symbols:
            self.targets[vt_symbol] = 0
            self.bgs[vt_symbol] = BarGenerator(on_bar)

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

        self.load_bars(1)

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

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

    def on_tick(self, tick: TickData):
        """
        Callback of new tick data update.
        """
        if (
            self.last_tick_time
            and self.last_tick_time.minute != tick.datetime.minute
        ):
            bars = {}
            for vt_symbol, bg in self.bgs.items():
                bars[vt_symbol] = bg.generate()
            self.on_bars(bars)

        bg: BarGenerator = self.bgs[tick.vt_symbol]
        bg.update_tick(tick)

        self.last_tick_time = tick.datetime

    def on_bars(self, bars: Dict[str, BarData]):
        """"""
        self.cancel_all()

        # Return if one leg data is missing
        if self.leg1_symbol not in bars or self.leg2_symbol not in bars:
            return

        # Calculate current spread
        leg1_bar = bars[self.leg1_symbol]
        leg2_bar = bars[self.leg2_symbol]

        # Filter time only run every 5 minutes
        if (leg1_bar.datetime.minute + 1) % 5:
            return

        self.current_spread = (
            leg1_bar.close_price * self.leg1_ratio - leg2_bar.close_price * self.leg2_ratio
        )

        # Update to spread array
        self.spread_data[:-1] = self.spread_data[1:]
        self.spread_data[-1] = self.current_spread

        self.spread_count += 1
        if self.spread_count <= self.boll_window:
            return

        # Calculate boll value
        buf: np.array = self.spread_data[-self.boll_window:]

        std = buf.std()
        self.boll_mid = buf.mean()
        self.boll_up = self.boll_mid + self.boll_dev * std
        self.boll_down = self.boll_mid - self.boll_dev * std

        # Calculate new target position
        leg1_pos = self.get_pos(self.leg1_symbol)

        if not leg1_pos:
            if self.current_spread >= self.boll_up:
                self.targets[self.leg1_symbol] = -1*self.leg1_ratio     # hxxjava add *self.leg1_ratio
                self.targets[self.leg2_symbol] = 1*self.leg2_ratio      # hxxjava add *self.leg2_ratio
            elif self.current_spread <= self.boll_down:
                self.targets[self.leg1_symbol] = 1*self.leg1_ratio      # hxxjava add *self.leg1_ratio
                self.targets[self.leg2_symbol] = -1*self.leg2_ratio     # hxxjava add *self.leg2_ratio
        elif leg1_pos > 0:
            if self.current_spread >= self.boll_mid:
                self.targets[self.leg1_symbol] = 0
                self.targets[self.leg2_symbol] = 0
        else:
            if self.current_spread <= self.boll_mid:
                self.targets[self.leg1_symbol] = 0
                self.targets[self.leg2_symbol] = 0

        # Execute orders
        for vt_symbol in self.vt_symbols:
            target_pos = self.targets[vt_symbol]
            current_pos = self.get_pos(vt_symbol)

            pos_diff = target_pos - current_pos
            volume = abs(pos_diff)
            bar = bars[vt_symbol]

            if pos_diff > 0:
                price = bar.close_price + self.price_add

                if current_pos < 0:
                    self.cover(vt_symbol, price, volume)
                else:
                    self.buy(vt_symbol, price, volume)
            elif pos_diff < 0:
                price = bar.close_price - self.price_add

                if current_pos > 0:
                    self.sell(vt_symbol, price, volume)
                else:
                    self.short(vt_symbol, price, volume)

        self.put_event()

4. 另外:如何设置正相关和负相关的配对?

当leg1与leg2正相关时,leg1_ratio和leg2_ratio同为正整数;
当leg1与leg2负相关时,leg1_ratio为正整数和leg2_ratio同为负整数。

Administrator
avatar
加入于:
帖子: 4887
声望: 284

感谢分享,我们后续来改下

Member
avatar
加入于:
帖子: 1
声望: 1

有道理

© 2015-2019 上海韦纳软件科技有限公司
备案服务号:沪ICP备18006526号-3

沪公网安备 31011502017034号