【一篇文章告诉你网格策略从理论到实盘的所有内容(python实现)】

【一篇文章告诉你网格策略从理论到实盘的所有内容(python实现)】,第1张

一篇文章告诉你网格策略从理论到实盘的所有内容 名词定义什么是网格策略现货网格的基本参数等差网格以及等比网格什么是网格的价格中枢以及目标仓位无常损失的与业绩计算需要“市价补仓”的情况无价格中枢的网格 程序实现保证金的概念维护好价格列表补单机制市价补仓机制程序源码

名词定义

策略逻辑参数

upperPrice 上界lowerPrice 下界currentPrice 当前价格gridStep 网格间隔gridAmount 每网格金额/币数量initPrice 策略中枢价格initAmount 策略投资金额/币数量totalGrids 总网格数量tarPos 目标仓位baseCurrency(基准币 例: ETH/BTC 币对中的 ETH)quoteCurrency (定价币 例: ETH/BTC 币对中的 BTC)

策略业绩计算参数

startPrice 策略开始时的价格initBase 策略开始时 base currency 的数量initQuote 策略开始时 quote currency 的数量finalBase 策略终止时 base currency 的数量finalQuote 策略终止时 quote currency 的数量initDate 策略开始运行的时间unilateral 无常损失开仓量posAvg 持仓均价totalDays 策略运行天数 什么是网格策略

策略使用背景:在大牛市过后,市场逐渐入熊,这个时候就是使用网格策略的最佳时机。

如上图所示,绿色代表买单,红色代表卖单。所谓网格策略就是在现价的基础上,在高于现价的价格上布限价卖单,在低于现价单地方布限价买单。

假设价格下跌至5元,5元价格的买单成交,则在5.1元的网格上补卖单,如上图所示。反之亦如此。
所以当行情震荡时,通过低买高卖,则可以获得利润。

现货网格的基本参数

现货网格的基本参数有

上下界 [1500 ~ 3000]网格间隔. 1%(等比),50(等差)投资金额或币数量. 10000u (投资金额), 100ETH(投资币数量)等差或等比网格 (每个网格价格相差1%)(每个网格价格相差50)网格类型(同金额网格或者同币数网格)

现货网格,默认不带杠杆。在不借币的情况下,网格需要有明确的上下界。以ETHUSD币对为例,目前价格为2000,那么我就假设价格未来在1500至3000内震荡,则3000和1500就为网格的上下界,需要注意的是,当网格到达上界时所有的币都卖成u,当网格到达下界时,则全仓持币。

在确定上下界以后,我们就可以根据网格间隔来计算总网格个数,计算出总网格个数之后,我能就可以计算出每个网格的挂单金额或者币数量。计算方式有两种,分别对应等差网格和等比网格。

等差网格以及等比网格

等差网格的情况
totalGrids = (upperPrice - lowerPrice)/ gridStep
gridAmount = initAmount / totalGrids

等比网格的情况
totalGrids = log(upperPrice/lowerPrice)/log(1 + gridStep)
gridAmount = initAmount / totalGrids

什么是网格的价格中枢以及目标仓位

需要注意的是,因为上下界以及投资金额都已经定死,实际上,我们已经可以计算出在给定的任何价格时base 和quote 的比例,并且,base可以看作是我们的持仓,而quote则可以看作是我们的资金,于是,我们可以算出任意给定价格的持仓比例。

值得注意的是,当价格处于价格中枢时如下图所示,在策略还未盈利的情况下,base与quote的价值是相等的,即处于“半仓”的状态,价值为(initAmount/2),或者说,使得仓位处于半仓状态的价格就是价格中枢,当价格围绕着价格中枢震荡时,是策略最理想的状态,因为没有无常损失。

以下是中枢价格的计算

等差网格的情况
initPrice = (upperPrice + lowerPrice)/2

等比网格的情况
initPrice = exp(0.5 * log(upperPrice*lowerPrice))

以下是目标仓位的计算公式

等差网格的情况
tarPos = (initPrice - currentPrice)/gridStep * gridAmount + initAmount/2

等比网格的情况
tarPos = log(initPrice/currentPrice)/log(1 + gridStep)*gridAmount + initAmount/2

无常损失的与业绩计算

首先来看看业绩的组成部分:

从价格的角度出发:
总业绩 = 币价波动所带来的盈亏 + 交易带来的盈亏 = 当前资产 - 初始资产/ 初始资产 * 100%
这个币价波动所带来的盈亏就是我们所说的无常损失。从策略的角度出发
总业绩 = 无常损失盈亏 + 交易次数(取买卖单成交总次数少的一方)* 网格间隔 * 网格挂单金额
无常损失:从startPrice至currentPrice,假设期间价格毫无波动,策略所做的交易会给总净值带来多少损失,如下图所示:

价格从5.0攀升至5.4元,如果我们不运行策略,则收益率为,(5.4 - 5) / 5 = 8% , 假设我们运行策略,则在价格上升时,我们一直在卖出自己的仓位,到5.4后,根据上图,无常损失等价走势成交了3次卖单,总资产比不运行策略的总资产要低,收益率一定是小于8%的,这就是所谓的“少赚”,而少赚的这一部分钱就是我们所说的无常损失。

如果我们想要衡量策略的性能,我则需要得到去除这个无常损失的业绩,因我们只想知道通过行情震荡我们赚了多少钱。
首先,无常损失开仓量,即价格变动所带来的开仓量为:

等比
unilateral = log(startPrice / currentPrice)/log(1 + gridStep) * gridAmount等差
unilateral = (startPrice - currentPrice)/gridStep * gridAmount

开仓均价为:

等比
posAvg = exp(0.5log(startPricecurrentPrice))等差
posAvg = (startPrice + currentPrice)/2

在这种开仓量的情况下,增加或减少了多少base和quote

base = (initBase + unilateral) * currentPricequote = initQuote - unilateral * posAvgtheroyEquity = base + quote. // 无常损失模拟的当前净值

策略终止真实净值:

finalEquity = finalBase*currentPrice + finalQuote

策略初始净值:

initEquity = initBase*startPrice + initQuote

业绩不包含无常损失

(finalEquity - theroyEquity)/initEquity/totalDays*365

业绩包含无常损失

(finalEquity - initEquity)/initEquity/totalDays*356 需要“市价补仓”的情况

当价格变动过快时,有可能会出现来不及补单的情况,如下图所示:

价格向上插针,然后立马回归,在5.2 与 5.3 的卖单成交了,但是策略没有及时补买单,价格就回归至5.1,这个时候,仓位就和目标仓位有差距,为了弥补这个差距,我们就需要在5.1的时候把缺失的仓位买回来。值得注意的是,市价补仓对策略是有利的,因为市价补仓使我们获得了更有优势的均价。

如上图,若补单及时,则会买回5.3,5.2时的仓位,到达5.1时,持仓均价则为(5.1+5.2+5.3)/3 = 5.2,若没有及时补仓,则在5.1时市价补仓,成本则为5.1,5.1 < 5.2 ,策略获得了更低的持仓成本。 无价格中枢的网格

上述讲到网格策略,价格中枢是固定,此类网格比较局限,因为若想要盈利则价格必须在既定的网格中枢附近波动,并且网格有明确的上下界,突破了上下界后,策略会处于“满仓状态”,无法进行交易。
为了解决此问题,网格策略又延伸出了无价格中枢的网格策略,也叫动态中枢网格策略,其类似于马丁策略,在策略开始时,就买入头寸,按照头寸的方向挂若干张现价单,单量取决于加仓模式,在头寸的反方向则挂上止盈单,每当头寸方向的限价单成交,持仓均价和持仓量发生变化时,就对止盈单进行改单 *** 作。伪代码如下:

在每次止盈单成交后,买入头寸的价格就是新的中枢,这也就时动态中枢网格的由来。值得注意的是,此网格可以选择做双向,也可以只做一个方向,如果标的为现货,做多则为赚取quote,做空则为赚取base。若为U本位合约,做多做空都为赚u。

现货网格做多的情况
现货网格做空的情况

因为动态中枢网格不是本文的重点,程序实现只讲中枢网格,下一篇文章会对动态中枢网格的细节深入讨论。 程序实现

vnpy的具体工作原理可以参考本人的上篇文章:基于VNPY实现网格策略下文只讲策略模块如何编写,从保证机、价格列表,补单机制,市价补仓的角度进行说明。

保证金的概念

想要成功下出限价买单或卖单,就需要有足够的保证金,在现货交易中,以ETHBTC为例子,下买单需要BTC作为保证金,下卖单则需要ETH作为保证金,若保证金不足,交易所会出现insufficient balance的报错。一般情况下,根据公式能够正好从上界到下界布满所有网格(用光所有保证金),但有时候由于计算失精等系统误差,会导致保证金不够用的情况。

所以,为了避免这种情况,我们可以选择只挂现价周围的单子,没有必要从上界到下界全部都挂满。如下图所示:

这样子就能完美避免保证金不足的情况。

维护好价格列表

假设从上界到下界共有1000个网格即1000个价格,那我们就选取现价周围的100个网格作为下单范围,这一百个价格是1000个价格的子集,并且随着现价而移动,一般情况下,我们下50个买单,和50个卖单:

当这100个价格有一边触及边界时,则下单范围不再需要改变:
这时候买卖单数量就不相等,如图所示,大概是30个卖单,70个买单。

补单机制

补单机制非常简单,只需要定期检查(0.5秒一次)在线挂单的价格列表与我们策略本地维护的价格列表,使这两个列表做差集(差集也是一个价格列表),结果就是我们需要补单的价格列表:

市价补仓机制

市价补仓机制需要利用上文所给的公式计算目标仓位tarPos,再用tarPos与现在的持仓做差,若差大于0即是市价卖出。

posDiff = tarPos - pos

流程如下:

程序源码 价格列表维护:
    def update_grid_price(self):

        if not self.last_trade_price:
            tick = self.get_tick(self.vt_symbol)
            if not tick:
                return
            current_price = tick.last_price
        else:
            current_price = self.last_trade_price

        msg = f"开始更新区间"
        self.write_log(msg)


        sideLength = self.side_grid_amount
        midIndex = self.find_current_level(current_price)

        right = len(self.grid_price[midIndex:]) - 1
        left = len(self.grid_price[:midIndex])

        if left <= self.side_grid_amount:
            msg = f"区间已覆盖下界:{self.lower_price}, 不需要更新区间"
            self.write_log(msg)
            if self.grid_price_range:
                return

        if right <= self.side_grid_amount:
            msg = f"区间已覆盖上界:{self.upper_price}, 不需要更新区间"
            self.write_log(msg)
            if self.grid_price_range:
                return


        buy_count = 0
        sell_count = 0
        for ID in list(self.active_orders.keys()):
            order:OrderData = self.active_orders[ID]

            if order.vt_symbol == self.vt_symbol:
                if order.direction == Direction.LONG:
                    buy_count+=1
                else:
                    sell_count+=1



        if buy_count <= 20 or sell_count <= 20 or True:
            if right >= sideLength and left >= sideLength:
                startIndex = midIndex - sideLength
                endIndex = midIndex + sideLength
            elif right < sideLength:
                endIndex = len(self.grid_price) - 1
                startIndex = midIndex - sideLength - (sideLength - right)
            else:
                startIndex = 0
                endIndex = midIndex + sideLength + (sideLength - left)

            if self.grid_price_range:
                old_range = self.grid_price_range
                self.grid_price_range = self.grid_price[startIndex: endIndex+1]

                old_left = list(set(old_range) - set(self.grid_price_range)) # 要撤的单
                # new_left = list(set(old_range) - set(self.grid_price_range)) # 要下的单, 不需要处理,由replenish_grid处理

                # 找出old_left中的所有价格的key
                cancel_index = []
                for vt_orderId in list(self.active_orders.keys()):
                    order = self.active_orders[vt_orderId]
                    price = formatPoint(order.price, self.tick_size)
                    if price in old_left:
                        cancel_index.append(vt_orderId)

                if len(cancel_index) == 0:return
                self.batch_cancel(cancel_index)
            else:
                self.grid_price_range = self.grid_price[startIndex: endIndex+1]

        else:
            msg = f"买单{buy_count}, 卖单{sell_count},足够,不需要更新区间"
            self.write_log(msg)
补单机制
    def replenish_grid(self):
        """"""
        if not self.grid_price_range:
            return
        tick : TickData = self.get_tick(self.vt_symbol)
        current_price = tick.last_price
        msg = f"{datetime.datetime.now()}:开始填充网格"
        self.write_log(msg)
        online_price_list = []
        for vt_orderId in list(self.active_orders.keys()):
            order:OrderData = self.active_orders[vt_orderId]
            if order.vt_symbol != self.vt_symbol:
                continue
            price = formatPoint(order.price, self.tick_size)
            online_price_list.append(price)


        # 找出未填充的网格
        diff = list(set(self.grid_price_range) - set(online_price_list))
        self.write_log(f"差集{diff}, 订单数量{len(self.active_orders)}")

        reqs = []

        if not self.last_trade_price:
            self.last_trade_price = current_price

        if not self.replenish_flag:
            self.replenish_flag = True
        else:
            msg = "下单函数正在执行"
            self.write_log(msg)
            return
        for price in diff:
            price = float(price)
            if price == self.last_trade_price:
                self.write_log(f"网格:{formatPoint(price, self.tick_size)},处于当前level,不补单")
                continue

            if self.contract_type == '现货':
                volume = self.grid_amount
            else:
                if self.margin == '反向':
                    volume = self.grid_volume
                else:
                    volume = self.get_grid_volume(price)


            if float(price) < self.last_trade_price:

                reqs.append({
                    'vt_symbol': self.vt_symbol,
                    'price': formatPoint(price, self.tick_size),
                    'volume': volume,
                    'order_type': OrderType.POSTONLY,
                    'direction': Direction.LONG,
                    'offset': Offset.NONE
                })

            else:
                reqs.append({
                    'vt_symbol': self.vt_symbol,
                    'price': formatPoint(price, self.tick_size),
                    'volume': volume,
                    'order_type': OrderType.POSTONLY,
                    'direction': Direction.SHORT,
                    'offset': Offset.NONE
                })
        if len(reqs) > 0:
            if len(reqs) > 100:
                self.batch_order(self.vt_symbol, reqs[:100])
            else:
                self.batch_order(self.vt_symbol, reqs)
        self.replenish_flag = False
市价补仓机制
   def regular_rebalance(self):

        is_refill = False

        tick: TickData = self.get_tick(self.vt_symbol)
        current_price = tick.last_price

        if current_price > self.upper_price or current_price < self.lower_price:
            print(
                f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}:现价:{current_price}, 超过网格上界:{self.upper_price}或下界:{self.lower_price},")
            return

        current_price = self.last_trade_price
        if not self.last_trade_price:
            current_price = tick.last_price

        actual_pos = self.pos

        if self.contract_type == '现货':
            if not self.equal_gap:
                actual_pos = self.pos  # 现在有多少现货
                self.virtual_tar_pos = (math.log(self.init_price / current_price) / math.log(1 + self.grid_step)) * float(
                    self.grid_amount) + self.init_amount / 2  # 目标仓位有多少base currency
                actual_tar_pos = self.leverage * (self.virtual_tar_pos)
            else:
                # 等差现货
                actual_pos = self.pos  # 现在有多少现货
                self.virtual_tar_pos = int((self.init_price - current_price)/self.contract.pricetick)*float(self.grid_amount) + self.init_amount / 2
                actual_tar_pos = self.leverage * (self.virtual_tar_pos)

        else:
            if self.gateway_name == 'OUYI':
                if self.margin == '反向':
                    self.virtual_tar_pos = (math.log(self.init_price / current_price) / math.log(
                        1 + self.grid_step)) * self.grid_volume  # 目标仓位有多少张
                    actual_tar_pos = self.leverage * (self.virtual_tar_pos)

                else:
                    self.virtual_tar_pos = (math.log(self.init_price / current_price) / math.log(
                        1 + self.grid_step)) * self.grid_amount / (self.contract_val * current_price)  # 目标仓位有多少张
                    actual_tar_pos = self.leverage * (self.virtual_tar_pos)

            if self.gateway_name == "BIAN":
                self.virtual_tar_pos = (math.log(self.init_price / current_price) / math.log(
                    1 + self.grid_step)) * self.grid_amount / (current_price)  # 目标仓位有多少张
                actual_tar_pos = self.leverage * (self.virtual_tar_pos)

        if actual_pos > actual_tar_pos:

            if self.contract_type == '现货':
                pos_diff = actual_pos - actual_tar_pos
                msg = f"与目标仓位差{pos_diff / float(self.grid_amount)}个网格"
                self.write_log(msg)
                if pos_diff / float(self.grid_amount) >= 3:
                    pos_diff = formatPoint(actual_pos - actual_tar_pos, self.min_size)
                    is_refill = True

            elif self.margin == '反向':
                pos_diff = actual_pos - actual_tar_pos
                msg = f"与目标仓位差{pos_diff / float(self.grid_volume)}个网格"
                self.write_log(msg)
                if pos_diff / self.grid_volume >= 3:
                    is_refill = True
            else:
                if self.gateway_name == 'OUYI':
                    pos_diff = actual_pos - actual_tar_pos
                    msg = f"与目标仓位差{pos_diff*self.contract_val*current_price / self.grid_amount}个网格"
                    self.write_log(msg)
                    if pos_diff*self.contract_val*current_price / self.grid_amount >= 3:
                        is_refill = True
                else:
                    pos_diff = actual_pos - actual_tar_pos
                    msg = f"与目标仓位差{pos_diff*current_price / self.grid_amount}个网格"
                    self.write_log(msg)
                    if pos_diff*current_price / self.grid_amount >= 3:
                        is_refill = True


            if is_refill:
                msg = f"开始补仓: {pos_diff}"
                self.write_log(msg)
                self.restart_orderId = self.sell(
                    vt_symbol=self.vt_symbol,
                    price=formatPoint(tick.bid_price_5, self.tick_size),
                    volume=pos_diff,
                    order_type=OrderType.LIMIT
                )

        elif actual_pos < actual_tar_pos:

            if self.contract_type == '现货':
                pos_diff = actual_tar_pos - actual_pos
                msg = f"与目标仓位差{pos_diff / float(self.grid_amount)}个网格"
                self.write_log(msg)
                if pos_diff / float(self.grid_amount) >= 3:
                    pos_diff = formatPoint(actual_tar_pos - actual_pos, self.min_size)

                    is_refill = True

            elif self.margin == '反向':
                pos_diff = actual_tar_pos - actual_pos
                msg = f"与目标仓位差{pos_diff / float(self.grid_volume)}个网格"
                self.write_log(msg)
                if pos_diff / self.grid_volume >= 3:
                    is_refill = True
            else:
                if self.gateway_name == 'OUYI':
                    pos_diff = actual_tar_pos - actual_pos
                    msg = f"与目标仓位差{pos_diff * self.contract_val * current_price / self.grid_amount}个网格,不需补仓"
                    self.write_log(msg)
                    if pos_diff * self.contract_val * current_price / self.grid_amount >= 3:
                        is_refill = True
                else:
                    pos_diff = actual_tar_pos - actual_pos
                    msg = f"与目标仓位差{pos_diff * current_price / self.grid_amount}个网格"
                    self.write_log(msg)
                    if pos_diff * current_price / self.grid_amount >= 3:
                        is_refill = True


            if is_refill:
                msg = f"开始补仓: {pos_diff}"
                self.write_log(msg)
                self.restart_orderId = self.buy(
                    vt_symbol=self.vt_symbol,
                    price=formatPoint(tick.ask_price_5, self.tick_size),
                    volume=pos_diff,
                    order_type=OrderType.LIMIT
                )

        else:
            print(f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: 无需补仓")

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/1323973.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存