真正手把手教会你:用 Python 构建网格交易策略

作者:老余捞鱼

原创不易,转载请标明出处及原作者。

写在前面的话:
       
网络上充斥着大量有关使用 Python 构建网格交易策略的文章,我借助 AI 工具迅速浏览了一番,结果发现要么是把简单的事情复杂化的技术文,要么是那种说一半留一半的 “钩子” 文,而最多的还是作者自己也不太明白就搬运过来的填坑文。因此,我打算撰写一篇能够让大家跟着操作就能完成的手把手指导文。我将按照环境配置、创建网格、创建交易信号、准备回溯测试、进行回溯测试以及结果分析这 6 个必要的顺序环节,指导大家运用 Python 构建并回溯测试一个简单的网格交易策略,而且该策略能够呈现出资产净值的稳定增长夏普比率接近 5.7)。而券商和机构不会告诉你的是,他们就是拿网格交易策略去做持仓深套的底层解决逻辑

       网格交易策略 —— 一种无需运用技术指标来检测趋势的简便且高效的技术。该方法是算法交易的上佳之选,我们的 Python 回溯测试表明,当两个月的数据显示在 5 分钟图表上时,资产净值稳步增长,夏普比率(Sharpe Ratio)接近 5.7。

       夏普比率(Sharpe Ratio):这是一个在金融和投资领域广泛使用的指标,用于衡量投资组合的风险调整后表现。这一指标由诺贝尔经济学奖获得者威廉·夏普(William Sharpe)提出,其核心思想在于帮助投资者在承担相同风险水平的情况下,比较并选择具有更高回报潜力的投资品种。夏普比率是投资组合的超额收益(即投资组合的平均收益率减去无风险利率)与投资组合波动率(即标准差)的比值。

Step 0: 网格交易入门

       网格交易是指在价格图表上设置数值网格。每当价格越过一条网格线,我们就同时建立一个多仓和一个空仓。每次开仓的止盈都放在随后的网格线上。这种策略擅长价格波动频繁的市场适合用在较短的时间框架,如果网格交易法不考虑时间成本可能导致资金被长时间占用无法获得更高回报。

1. 示例

       假设你选择了一只股票,初始价格为100元。你设置了一个10%的网格间距,共有6个格子,每格10%。交易规则是: 当股价下跌到90元时,买入1000股当股价上涨到110元时,卖出1000股当股价下跌到90元时,再次买入1000股,以此类推。这种方式可以在价格波动时实现买低卖高,但在单边上升或下跌趋势中可能表现不佳。

2. 优点

       在震荡行情中,如果网格设置合理,可以来回不断地赚取利润。在震荡行情中表现得好,是网格最重要的作用。这种策略特别适合那些没有明显趋势的市场(大A,你就直接报我名字不好吗?),因为它能够在价格上涨和下跌时都实现收益。

       持有体验会很不错,因为它每一笔交易都旨在“高抛低吸”,每一次买入都旨在盈利退出。理论上来说,网格交易策略的交易胜率是100%,这很容易让投资者在投资的过程中心理愉悦,获得正向反馈。


3. 缺点

  • 忽视时间成本:网格交易法不考虑时间成本可能导致资金被长时间占用无法获得更高回报。
  • 单次获利有限:该策略的盈利潜力受到网格间距的限制,只能获得设定的买入卖出幅度内的利润。
  • 不适用于高增长股票:网格交易最适合于价格波动较大但没有明显增长趋势的资产, 不适合那些每年利润稳定增长的公司。

       总之,网格交易法是一种适用于震荡市场的交易策略,它忽略了时间成本,盈利有限,适合用于波动频繁的标的资产。而大家关心的网格交易策略如何能作为一种解决持仓深套问题的方法,其核心逻辑就是在于通过机械化的自动执行高抛低吸操作,在市场价格波动中寻求盈利机会,从而逐步降低持仓成本并最终实现解套。但残酷的现实是,如果你不具备券商或机构那样的软硬件环境,也没有反正操作的都是别人钱的心态时,当他们来电话询问你是否需要持仓深套解套服务时,你还是老老实实的回答 yes 吧!

       前面啰啰嗦嗦这么多,就是告诉我的读者,在使用该策略时需要谨慎,必须先了解其局限性和适用性。现在,我们将用 Python 实现网格策略,并在历史数据上进行回溯测试。

Step 1: 配置环境

       首先,我们需要配置 Python 环境,导入所需的库并下载数据集。我们将使用 yfinance 获取数据,使用 pandas 进行数据操作,使用 numpy 进行数值运算,使用 pandas_ta 进行技术分析指标。(特别是 numpy和pandas_ta ,国内大部分教程都不会教这个

import yfinance as yf
import pandas as pd
import numpy as np
import pandas_ta as ta

# Download the EUR/USD data for the last 59 days with a 5-minute interval
dataF = yf.download("EURUSD=X", start=pd.Timestamp.today() - pd.DateOffset(days=59), end=pd.Timestamp.today(), interval='5m')

# yfinance (yf): A Python library to download historical market data from Yahoo Finance.
# pandas (pd): A powerful data manipulation and analysis library.
# numpy (np): A library for numerical operations.
# pandas_ta (ta): A technical analysis library for financial datasets.

Step 2: 创建网格

       在这一步中,我们要建立一个数值网格,决定我们的进场和出场点。网格由价格图表上均匀分布的一系列水平线组成。每当价格穿过其中一条网格线,我们就会建立一个新仓位。

grid_distance = 0.005
midprice = 1.065

def generate_grid(midprice, grid_distance, grid_range):
    return np.arange(midprice - grid_range, midprice + grid_range, grid_distance)
grid = generate_grid(midprice=midprice, grid_distance=grid_distance, grid_range=0.1)

2.1 设置参数

  1. grid_distance = 0.005:设置每条网格线之间的间距,代表开新仓的价格增量。
  2. midprice = 1.065: 这是建立网格的中心参考价。可以设置为当前市场价格或其他参考价格。

2.2 重要提示

       这些值必须根据市场、交易资产和当前分析进行调整。因此,每次从 Yfinance 查询数据时,数据都会更新,这些参数以及本策略中的其他参数也需要相应更新。

2.3 生成网格

  1. 函数 generate_grid 会创建一系列数值,范围从中间价 - grid_range 到中间价 + grid_range,中间间隔为 grid_distance。
  2. grid_range = 0.1: 定义中间价上下的网格总范围。

2.4 构建网格

       网格阵列包括我们将考虑建仓的所有价位。这是我们网格交易策略的核心,使我们能够随着价格波动系统地进入和退出交易。

Step 3: 创建交易信号

       在这一步中,我们根据建立的网格线生成交易信号。只要价格与网格线交叉,就会产生交易信号。这需要遍历数据集中的每一行,检查该区间的最高价或最低价是否与任何网格线交叉。

signal = [0] * len(dataF)
i = 0
for index, row in dataF.iterrows():
    for p in grid:
        if min(row.Low, row.High) < p and max(row.Low, row.High) > p:
            signal[i] = 1
    i += 1
dataF["signal"] = signal
dataF[dataF["signal"] == 1]

3.1 初始化信号

       signal = [0] * len(dataF):我们创建一个长度与数据集相同的零列表。该列表将存储我们的交易信号。

3.2 数据迭代

       我们使用 dataF.iterrows() 循环查看数据集中的每一行。

       对于每一行,我们通过比较该行最高价和最低价的最小值和最大值与每条网格线,来检查价格是否与任何网格线交叉。

3.3 生成信号

       如果网格线被交叉(即最低价低于网格线,最高价高于网格线),我们就会将信号列表中的相应条目设为 1。

3.4 为 DataFrame 添加信号

  1. dataF["signal"] = signal:我们将信号列表作为新列添加到 DataFrame 中。
  2. dataF[dataF["信号"] == 1]:这将过滤 DataFrame,只显示产生交易信号的行。

       通过创建这些交易信号,我们可以确定价格与网格线交叉的确切时间点,从而指示潜在的交易入口。这种系统化的方法可确保我们捕捉到所定义网格内的所有相关价格走势。

Step 4: 准备回溯测试

       在这一步中,我们通过计算平均真实范围 (ATR) 和定义信号函数,为回测数据集做好准备。ATR 是一个波动率指标,可以帮助我们设置动态止损和止盈水平。

dfpl = dataF[:].copy()

def SIGNAL():
    return dfpl.signal
dfpl['ATR'] = ta.atr(high=dfpl.High, low=dfpl.Low, close=dfpl.Close, length=16)
dfpl.dropna(inplace=True)

4.1 复制 DF

       dfpl = dataF[:].copy():我们创建原始数据集的副本,以避免在回溯测试过程中直接更改数据集。这将确保我们的原始数据保持不变。

4.2 定义信号功能

       def SIGNAL():此函数返回 DataFrame 中的信号列。它将在回溯测试中用于访问生成的交易信号。

4.3 计算平均真实范围 (ATR)

  • dfpl['ATR'] = ta.atr(high=dfpl.High, low=dfpl.Low, close=dfpl.Close, length=16):我们使用 16 个区间计算 ATR。ATR 是衡量市场波动性的指标,有助于设置合适的止损和止盈水平。
  • high=dfpl.High,low=dfpl.Low,close=dfpl.Close:这些参数指定用于计算 ATR 的最高价、最低价和收盘价。

4.4 清洗

       dfpl.dropna(inplace=True):我们剔除所有缺失值的行,以保持回溯测试过程的完整性。这一步对于获得准确可靠的回溯测试结果至关重要。

       ATR 是对波动率的动态衡量。通过计算 ATR 和定义信号函数,我们的回溯测试框架就具备了评估网格交易策略性能的必要工具。

Step 5: 回溯测试

       在这一步中,我们使用回溯测试库实施我们的交易策略,并执行回溯测试以评估其性能。我们定义一个自定义策略类并配置回溯测试参数。

from backtesting import Strategy
from backtesting import Backtest
import backtesting

class MyStrat(Strategy):
    mysize = 50
    
    def init(self):
        super().init()
        self.signal1 = self.I(SIGNAL)
    def next(self):
        super().next()
        slatr = 1.5 * grid_distance  # Stop loss distance
        TPSLRatio = 0.5  # Take profit to stop loss ratio
        if self.signal1 == 1 and len(self.trades) <= 10000:
            # Sell position
            sl1 = self.data.Close[-1] + slatr
            tp1 = self.data.Close[-1] - slatr * TPSLRatio
            self.sell(sl=sl1, tp=tp1, size=self.mysize)
            # Buy position
            sl1 = self.data.Close[-1] - slatr
            tp1 = self.data.Close[-1] + slatr * TPSLRatio
            self.buy(sl=sl1, tp=tp1, size=self.mysize)
# Running the backtest
bt = Backtest(dfpl, MyStrat, cash=50, margin=1/100, hedging=True, exclusive_orders=False)
stat = bt.run()
stat

5.1 导入库

  1. from backtesting import Strategy, Backtest:从回溯测试库中导入基本类,以创建并执行我们的交易策略。
  2. import backtesting: 导入反向测试库

5.2 创建策略类

  1. class MyStrat(Strategy): 我们通过扩展策略类来定义自定义策略类。
  2. mysize = 50: 设置每次交易的仓位大小。

5.3 初始化

  1. def init(self):初始化策略。
  2. self.signal1 = self.I(SIGNAL):  使用之前定义的 SIGNAL 函数访问交易信号。

5.4 确定交易逻辑

  1. def next(self): 概述执行交易的规则。
  2. slatr = 1.5 * grid_distance: 根据网格间距确定止损距离。
  3. TPSLRatio = 0.5:  建立止盈与止损比率。

5.5 执行交易

  1. 如果触发了信号(self.signal1 == 1),且交易次数小于或等于 10,000:
  2. 卖出头寸: - sl1 = self.data.Close[-1] + slatr:设置高于当前收盘价的止损。 - tp1 = self.data.Close[-1] - slatr * TPSLRatio:设置低于当前收盘价的止盈。 - self.sell(sl=sl1, tp=tp1, size=self.mysize):买入头寸: - sl1 = self.data.Close[-1] - slatr:设置低于当前收盘价的止损。 - tp1 = self.data.Close[-1] + slatr * TPSLRatio:设置高于当前收盘价的止盈。 - self.buy(sl=sl1, tp=tp1, size=self.mysize):下达买入指令。

5.6 运行回溯测试

  1. bt = Backtest(dfpl, MyStrat, cash=50, margin=1/100, hedging=True, exclusive_orders=False): 设置回溯测试参数。
  2. cash=50: 初始可用现金额。
  3. margin=1/100: 杠杆比率。
  4. hedging=True: 允许对冲。
  5. exclusive_orders=False: 允许重叠订单。
  6. stat = bt.run(): 执行回溯测试并保存结果。

       通过该回溯测试,我们可以评估网格交易策略的性能。`stat` 对象提供了详细的指标,用于评估策略的有效性。

Step 6: 结果

       下图展示了回溯测试的结果,包括策略的持续时间、交易时间比例、绩效指标(最终权益、收益峰值、收益率等)、风险调整指标(夏普比率、索提诺比率、卡尔玛比率等)、跌穿率、交易活动等。后面还有程序输出的结果数据,请大家查阅。

### Summary:
- **Duration:** The strategy was evaluated over a period of 57 days and 21 hours, offering a thorough assessment timeframe.
- **Exposure Time:** 99.14%, meaning the strategy was nearly always active in the market.
### Performance Metrics:
- **Final Equity:** $136.02, up from the initial $50.
- **Equity Peak:** $156.59, indicating potential for greater gains.
- **Return:** 172.04%, showing substantial profitability.
- **Buy & Hold Return:** 4.23%, highlighting the strategy's superior performance over a passive approach.
- **Annualized Return:** 37364.62%, extremely high due to the brief backtesting period.
- **Annualized Volatility:** 46309.60%, suggesting significant risk and frequent trading activity.
### Risk-Adjusted Metrics:
- **Sharpe Ratio:** 0.81, indicating moderate returns adjusted for risk.
- **Sortino Ratio:** 1089.28, reflecting excellent management of downside risk.
- **Calmar Ratio:** 2194.58, demonstrating high returns relative to the maximum drawdown.
### Drawdown:
- **Max Drawdown:** -17.03%, a notable but manageable drop.
- **Avg Drawdown:** -1.16%, relatively modest.
- **Max Drawdown Duration:** Almost 10 days, representing the longest recovery time.
- **Avg Drawdown Duration:** Under 7 hours, suggesting quick recoveries.
### Trading Activity:
- **Number of Trades:** 1698, indicating high trading frequency due to the grid strategy.
- **Win Rate:** 73.03%, a very high success rate.
- **Best Trade:** 0.87%, reflecting consistent small gains.
- **Worst Trade:** -0.85%, indicating controlled losses.
- **Avg Trade:** 0.10%, showing the cumulative effect of many small trades.

网格交易策略的有效性:我们通过实际数据的回溯测试,展示了网格交易策略在某些市场条件下的有效性,尤其是在波动性较高的市场中,该策略确实能够实现稳定的收益增长。

风险和收益的平衡:文章中的回溯测试结果显示,网格交易策略在短时间内实现了高收益,但同时伴随着较高的波动性和风险,这要求交易者在实际应用中对策略进行适当的调整和优化。

策略的系统化和自动化:通过 Python 编程实现网格交易策略,可以系统化和自动化交易决策过程,减少个人情绪波动对交易的影响。

回溯测试的重要性:我需要再次强调回溯测试在评估和优化交易策略中的重要性,通过回溯测试可以在实际投入资金之前评估策略的稳健性和潜在性能。


本文内容仅仅是技术探讨和学习,并不构成任何投资建议。转发请注明原作者和出处。

### 网格交易策略 Python 源码实现 #### 设置中枢价格并构建网格系统 为了实施网格交易策略,首先需要定义中枢价格,并基于此创建多头和空头方向上的网格结构。这涉及到设定网格的数量、每个网格的价格差以及相应的仓位大小。 ```python import numpy as np # 定义参数 GRID_AMOUNT = 10 # 网格数量 INITIAL_PRICE = 100.0 # 中枢价格 POSITION_SIZE = 1 # 单次买入/卖出的手数 # 计算多头每格价格变化幅度 grid_region_long = [0.005] * GRID_AMOUNT # 多头每格价格跌幅(网格密度)[^1] # 构建网格列表 prices_long = [(INITIAL_PRICE - i * INITIAL_PRICE * grid_region_long[i]) for i in range(GRID_AMOUNT)] positions_long = [POSITION_SIZE] * GRID_AMOUNT ``` #### 执行买卖操作逻辑 当市场价格触及预设的某个网格价位时,则按照预先配置好的规则执行买进或卖出动作。这里展示了一个简化版的例子来说明如何根据当前市价判断是否应该下单: ```python def check_and_trade(current_price, prices, positions): """检查当前价格是否触碰到了任何网格位置,并据此决定是否进行交易""" trades = [] for idx, price_level in enumerate(prices): if current_price >= price_level and positions[idx] > 0: # 如果达到或多穿过多头网格水平线则卖出 trades.append(('sell', positions[idx])) elif current_price <= price_level and positions[idx] < 0: # 如果跌至或跌破空头网格水平线则买入 trades.append(('buy', abs(positions[idx]))) return trades ``` #### 主循环模拟市场变动过程 最后通过一个简单的主程序框架来进行测试,模拟不同时间点下的市场报价情况,并调用上述函数完成实际的订单处理流程。 ```python if __name__ == "__main__": import random # 初始化变量 market_prices = [] # 存储历史行情数据序列 all_trades = [] # 收集所有的成交记录 # 假定一段时间内的随机波动范围 time_steps = 100 volatility = 0.02 # 波动率 initial_market_price = INITIAL_PRICE for t in range(time_steps): # 更新最新时刻的市场价格(此处采用简单随机游走模型) next_move = (random.random() - 0.5) * volatility * initial_market_price new_market_price = max(min(initial_market_price + next_move, max(prices_long)), min(prices_long)) # 将新产生的价格加入到历史序列中去 market_prices.append(new_market_price) # 对照现有网格体系评估是否有符合条件的新单可下 executed_orders = check_and_trade(new_market_price, prices_long, positions_long) # 添加这些已发生的交易行为到总的清单里面保存起来 all_trades.extend(executed_orders) # 输出部分信息供观察调试用途 print(f"Time Step {t}: Market Price={new_market_price:.2f}, Trades Executed={len(executed_orders)}") ``` 这段代码提供了一种基础形式的网格交易算法实现方式,可以根据具体需求调整各个组件的具体细节以适应不同的应用场景[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老余捞鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值