在上一篇文章中,我们使用量化分析的方法通过振幅和涨跌幅对币种进行了初步筛选。接下来,我们将探讨网格交易中的一个关键因素——网格数的设置。网格数决定了交易的频率和每次交易的资金量,进而影响网格交易的总收益和风险水平。因此,如何合理设置网格数以达到最佳的平衡点,是每个网格交易者都需要考虑的重要问题。
网格数过少:如果网格数设置得过少,网格之间的间隔较大,意味着价格需要较大的波动才会触及下一网格。虽然每个网格的交易额较大,可以捕获较大的波动利润,但由于交易机会较少,可能错失一些较小波动带来的盈利。因此,整体盈利能力可能低于预期。
网格数过多:当网格数设置得过多时,每个网格的价格区间较小,交易机会增多,能够更频繁地进行买卖。然而,由于每次交易的资金较小,这样的策略往往需要高频的交易来获取利润。这就容易导致较高的手续费支出,并且过于频繁的交易可能使得市场的小幅波动成为主要盈利来源,收益的上限有限。
合理的网格数:合适的网格数需要综合考虑市场的波动性、账户资金规模以及期望的交易频率。市场波动性较大时,适当增加网格数可以更好地捕捉波动,而资金较多时,设置较少的网格数可能带来更高的单次交易额并减少手续费压力。通过平衡网格间距和频繁交易的成本,可以最大化策略的收益与风险控制。
网格交易的一个核心特点是通过设定多个网格区间,将资金分配到每个区间。在确定网格数时,首先需要计算每个网格的间隔和每个网格的资金量。网格数的设置不仅影响到每个网格的资金量,还决定了每个网格的买卖价格区间。
账户资金是决定网格数的重要因素。较大的账户资金允许设置更多的网格,而较小的账户资金则需要限制网格数,以避免资金分配过于分散,导致每个网格的资金量过小,无法有效盈利。
此外,网格交易的风险管理策略也需要考虑到网格数的设置。尤其在市场剧烈波动时,过多的网格可能导致更多的亏损,因此要合理控制网格数,以避免过度交易。
在实际的网格交易策略应用中,特别是在优化网格数和收益率之间的平衡时,【数据探索】模块的DQL(DATA Query Language)提供了极大的灵活性。DQL不仅支持高效的数据查询、处理和分析,还能帮助交易者模拟和回测网格交易策略的表现,以便找到最适合的网格数和其它参数配置。
通过DQL,我们可以轻松地从多个交易所(如Binance)获取历史K线数据,并根据这些数据调整网格交易策略。这样可以根据不同市场环境和具体币种的波动情况,精确地筛选出最优的网格交易策略。
在开始网格交易策略的回测之前,我们首先需要获取目标币种的市场数据。以下是从数据库查询指定币种的K线数据的代码:
# 获取目标币种的K线数据
data = query("select * from klines.spot_1d where Exchange = 'Binance' and Symbol = '???_usdt' order by Time")
说明:
- 通过查询数据库,我们可以获得特定币种(例如 ???_usdt
)在Binance交易平台的日线数据(包括时间、开盘价、最高价、最低价和收盘价等)。这为策略执行提供基础数据。
网格交易策略的核心在于利用预设的网格数量和价格区间进行交易。具体的步骤如下:
计算网格间隔(gap)与每格交易额(notional):
网格间隔(gap):根据市场的最高价和最低价之间的比例来计算。公式如下:
[
\text{gap} = \frac{\log(\text{max_p} / \text{min_p})}{\text{grid_num}}
]
其中,max_p
为最高价,min_p
为最低价,grid_num
为网格数量。
每格交易额(notional):每个网格的交易额通过总资金和网格数来计算。公式为: [ \text{notional} = \frac{\text{balance}}{\text{grid_num}} ]
网格起始与结束价格:
exp(gap)
,依此类推。交易操作:
交易费用:假设每笔交易的手续费为0.0001,我们需要在每次交易时计算并累计手续费。
def grid_trading_strategy(
raw,
grid_num, # 网格数量
min_p, # 最低价格
max_p, # 最高价格
):
"""
执行网格交易策略的函数
"""
# 初始化变量
balance = 1000 # 初始资金
raw = raw[['Time', 'Open', 'High', 'Low', 'Close']] # 只选择相关列
# 网格交易策略的设置
gap = math.log(max_p / min_p) / grid_num # 计算网格间隔
notional = balance / grid_num # 每个网格的交易额
# 初始化网格
net = []
for i in range(grid_num):
net.append({
'start_p': min_p * math.exp(i * gap), # 每个网格的起始价格
'end_p': min_p * math.exp((i + 1) * gap), # 每个网格的结束价格
'amt': notional / (min_p * math.exp(i * gap)), # 每个网格的购买量
'status': 'idle' # 初始状态为闲置
})
# 记录状态
state = {
'stock': 0, # 当前持仓
'fee': 0, # 交易费用
'longTradeVol': 0, # 长期交易量
'shortTradeVol': 0, # 短期交易量
'profitTbl': [], # 存储每个时刻的利润
'feeTbl': [], # 存储每个时刻的费用
'netCnt': 0, # 记录净交易次数
'idx': 0 # 当前数据的索引
}
# 检查开盘交易
def check_open_orders(state, net):
for i in range(len(net)):
if net[i]['status'] == 'idle' and raw['Low'][state['idx']] <= net[i]['start_p'] and raw['Open'][state['idx']] > net[i]['start_p']:
net[i]['status'] = 'taken' # 网格被占用
tradeVol = net[i]['amt'] * net[i]['start_p']
state['stock'] += net[i]['amt']
state['longTradeVol'] += tradeVol
state['fee'] += tradeVol * 0.0001 # 假设手续费为0.0001
# 检查平仓交易
def check_close_orders(state, net):
for i in range(len(net)):
if net[i]['status'] == 'taken' and raw['High'][state['idx']] >= net[i]['end_p'] and raw['Open'][state['idx']] < net[i]['end_p']:
net[i]['status'] = 'idle' # 网格状态恢复为空闲
tradeVol = net[i]['amt'] * net[i]['end_p']
state['stock'] -= net[i]['amt']
state['shortTradeVol'] += tradeVol
state['fee'] += tradeVol * 0.0001 # 假设手续费为0.0001
state['netCnt'] += 1
# 日志记录利润和费用
def log(state):
addVol = state['stock'] * raw['Close'][state['idx']] # 当前仓位的总价值
pl = state['shortTradeVol'] - state['longTradeVol'] + addVol # 计算利润
state['profitTbl'].append(pl)
state['feeTbl'].append(state['fee'])
# 主交易循环
for i in range(len(raw)):
state['idx'] = i
if raw['Close'][state['idx']] >= raw['Open'][state['idx']]:
check_open_orders(state, net)
check_close_orders(state, net)
else:
check_close_orders(state, net)
check_open_orders(state, net)
log(state)
# 将利润和费用数据整理为DataFrame
pl = DataFrame({'pl' : state['profitTbl'], 'fee' : state['feeTbl']})
pl['time'] = raw['Time']
pl['pl-net'] = pl['pl'] - pl['fee']
return pl
针对目标币种,我们选择’oax_usdt’币种,经过上篇代码检验,该币种在较长一段时间内,保持较高的振幅,又不呈现显著的单边趋势;我们可以尝试使用不同的网格数(例如:5、10、15等)进行模拟回测,查看不同网格数的效果,进而找到适合的网格数。例如,通过计算每个网格数下的净利润(pl-net),我们可以评估哪些网格数在当前市场环境下能够带来最好的收益。以下是运行回测的代码:
grid_nums = [5*i+5 for i in range(5)]
out = []
for g in grid_nums:
pl = grid_trading_strategy(
data,
grid_num=g,
min_p=min(data['Close']),
max_p=max(data['Close'])
)
out.append({
'num': g,
'pl-net': pl['pl-net'][-1],
'min_pl': min(pl['pl-net']),
'max_pl': max(pl['pl-net'])
})
return out
经过不同网格数量配置下的回测,我们得到了以下结果:
分析: - 随着网格数量的增加,净收益呈现先增加后减少的趋势。当网格数为15时,净收益达到了最大值。继续增加网格数后,净收益出现下降。 - 最小收益和最大收益的波动与净收益趋势相似,表明网格数的选择对收益有重要影响。
合理设置网格数是网格交易策略中的一项重要任务。通过网格数的优化,可以有效提高网格交易策略的收益表现,并更好地控制风险。本文通过对网格数设置的分析,提供了具体的计算方法和示例代码,希望可以帮助大家优化网格交易策略,提高策略的稳定性和盈利性。
注: 本文网格回测代码改编自知乎大神 Halcyon,源码解释请参考文章算法交易实战日记(十八) —— 网格交易中的细节:网格数与长期收益率的关系。