2
집중하다
20
수행원

DATADATA는 그리드 전략(II)을 지원합니다. 그리드 수와 수익률 간의 관계

만든 날짜: 2025-02-13 16:22:36, 업데이트 날짜: 2025-02-17 17:38:52
comments   0
hits   352

이전 기사에서는 DATADATA 플랫폼에서 진폭과 상승 및 하락을 기준으로 통화에 대한 예비 검토를 수행했습니다. 다음으로, 그리드 거래의 핵심 요소인 그리드 수 설정에 대해 살펴보겠습니다. 그리드의 수는 거래 빈도와 각 거래에 필요한 자금 규모를 결정하며, 이는 그리드 거래의 총 수익과 위험 수준에 영향을 미칩니다. 따라서 최적의 균형점을 달성하기 위해 그리드 수를 합리적으로 설정하는 방법은 모든 그리드 거래자가 고려해야 할 중요한 문제입니다.

그리드 수 설정의 과제

  • 그리드가 너무 적습니다: 그리드의 개수를 너무 작게 설정하면 그리드 간 간격이 커지게 되며, 이는 다음 그리드에 도달하기 위해 가격 변동이 더 커야 한다는 것을 의미합니다. 각 그리드의 거래 금액이 더 크고 더 큰 변동 수익을 포착할 수 있지만, 거래 기회가 적기 때문에 작은 변동으로 얻는 수익 중 일부가 놓칠 수 있습니다. 따라서 전반적인 수익성은 예상보다 낮을 수 있습니다.

  • 그리드가 너무 많습니다: 그리드의 개수를 너무 많이 설정하면, 각 그리드의 가격 범위가 작아지고, 거래 기회가 늘어나며, 매수매도를 더 자주 수행할 수 있습니다. 그러나 각 거래에 관련된 자본 금액이 적기 때문에 이러한 전략은 수익을 내기 위해 고빈도 거래가 필요한 경우가 많습니다. 이로 인해 거래 수수료가 쉽게 높아질 수 있으며, 거래가 너무 잦으면 작은 시장 변동이 주요 수익원이 될 수 있으며 수익의 상한선도 제한됩니다.

  • 적당한 수의 그리드: 적절한 그리드 수는 시장 변동성, 계좌 규모, 예상 거래 빈도를 고려해야 합니다. 시장 변동성이 높은 경우, 그리드의 수를 적절히 늘리면 변동을 더 잘 포착할 수 있고, 자금이 큰 경우, 그리드의 수를 줄이면 단일 거래 금액이 늘어나 거래 수수료 압박이 줄어들 수 있습니다. 그리드 간격과 빈번한 거래 비용을 균형 있게 조절함으로써 이 전략의 수익과 위험 관리를 극대화할 수 있습니다.

그리드의 개수는 다음에 따라 설정됩니다.

1. 그리드 간격 및 기금 할당

그리드 거래의 핵심 특징은 여러 개의 그리드 간격을 설정하여 각 간격에 자금을 할당하는 것입니다. 그리드의 개수를 결정할 때는 먼저 각 그리드 간의 간격과 각 그리드에 포함된 자금의 양을 계산해야 합니다. 그리드 개수의 설정은 각 그리드의 자금 규모에 영향을 미칠 뿐만 아니라, 각 그리드의 매수 및 매도 가격 범위도 결정합니다.

  • 그리드 간격: 그리드가 너무 조밀하면 거래 기회는 많아지지만, 각 거래에 필요한 자금량이 적어 총 수익의 상한이 낮아질 수 있습니다. 반대로 그리드가 너무 희소하면 각 거래에 필요한 자금량은 많아지지만 거래 빈도가 낮아 수익 기회를 놓칠 수 있습니다.
  • 자금 할당: 그리드의 수를 설정할 때는 자금 분배를 균형 있게 조정해야 하며, 각 그리드에 너무 많은 자금이 분산되어 효과적으로 수익을 창출하지 못하는 일이 없도록 해야 합니다.

2. 계정 자금 및 위험 관리

계정 자금은 그리드 수를 결정하는 중요한 요소입니다. 계좌 자금이 클수록 더 많은 그리드를 설정할 수 있는 반면, 계좌 자금이 작을수록 자금을 너무 분산시켜 각 그리드에 있는 자금이 너무 적어지고 효과적인 수익을 창출할 수 없게 되는 것을 피하기 위해 그리드 수에 제한이 필요합니다.

또한, 그리드 거래의 위험 관리 전략에서는 그리드 수의 설정도 고려해야 합니다. 특히 시장이 격렬하게 변동할 때, 그리드가 너무 많으면 손실이 커질 수 있으므로, 과도한 거래를 피하기 위해 그리드 수를 합리적으로 조절해야 합니다.

그리드 트레이딩 전략 구현

그리드 거래 전략을 실제로 적용할 때, 특히 그리드 수와 수익률 간의 균형을 최적화할 때 Datadata 플랫폼의 DQL(Datadata Query Language)은 큰 유연성을 제공합니다. DQL은 효율적인 데이터 쿼리, 처리, 분석을 지원할 뿐만 아니라, 트레이더가 그리드 트레이딩 전략의 성과를 시뮬레이션하고 백테스트하여 가장 적합한 그리드 번호와 기타 매개변수 구성을 찾는 데 도움이 됩니다.

DQL을 사용하면 여러 거래소(예: 바이낸스)의 과거 K-라인 데이터를 쉽게 얻고, 이 데이터를 기반으로 그리드 거래 전략을 조정할 수 있습니다. 이런 방식으로 다양한 시장 환경과 특정 통화의 변동성에 따라 최적의 그리드 거래 전략을 정확하게 선별할 수 있습니다.

1. 데이터를 얻으세요

그리드 트레이딩 전략의 백테스트를 시작하기에 앞서, 먼저 목표 통화에 대한 시장 데이터를 얻어야 합니다. 다음은 데이터베이스에서 지정된 통화의 K-라인 데이터를 쿼리하는 코드입니다.

# 获取目标币种的K线数据
data = query("select * from klines.spot_1d where Exchange = 'Binance' and Symbol = '???_usdt' order by Time")

설명:

  • 데이터베이스를 쿼리하면 특정 통화의 가치를 얻을 수 있습니다(예:???_usdt) 바이낸스 거래 플랫폼의 정보(시간, 시작 가격, 최고 가격, 최저가, 종가 등 포함). 이는 전략 실행을 위한 기본 데이터를 제공합니다.

2. 그리드 전략 기능

그리드 거래 전략의 핵심은 사전에 설정된 그리드 수량과 가격 범위를 사용하여 거래하는 것입니다. 구체적인 단계는 다음과 같습니다.

  1. 그리드 간격(갭)과 그리드당 거래 금액(명목)을 계산합니다.

    • 그리드 간격(갭): 시장에서 가장 높은 가격과 가장 낮은 가격의 비율을 기준으로 계산됩니다. 공식은 다음과 같습니다. [ \text{gap} = \frac{\log(\text{max_p} / \text{min_p})}{\text{grid_num}} ] 안에,max_p가장 높은 가격으로,min_p가장 낮은 가격으로,grid_num격자의 수입니다.

    • 그리드당 거래 금액(명목):각 그리드의 거래금액은 총자금과 그리드수에 의해 계산됩니다. 공식은 다음과 같습니다. [ \text{notional} = \frac{\text{balance}}{\text{grid_num}} ]

  2. 그리드 시작 및 종료 가격

    • 각 그리드의 시작 및 종료 가격은 그리드 간격에 따라 동적으로 계산됩니다. 예를 들어, 첫 번째 그리드의 시작 가격은 가장 낮은 가격이고 두 번째 그리드의 시작 가격은 가장 낮은 가격에 다음을 곱한 것입니다.exp(gap), 등등.
  3. 거래 운영

    • 개방 조건: 가격이 특정 그리드의 시작 가격으로 떨어지면 매수 작업이 실행됩니다.
    • 평점 조건: 가격이 특정 그리드의 최종 가격까지 상승하면 매도 작업이 실행됩니다.
  4. 거래 수수료: 각 거래에 대한 처리 수수료가 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

3. 백테스트 실행

대상 통화로 ‘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

4. 실험 결과 분석

다양한 그리드 번호 구성으로 백테스트를 실시한 결과 다음과 같은 결과를 얻었습니다.

분석:

  • 그리드의 수가 증가함에 따라 순이익은 처음에는 증가하다가 그다음에는 감소하는 추세를 보입니다. 그리드의 수가 15개일 때 순이익은 최대치에 도달합니다. 그리드의 숫자가 계속 늘어나면서 순이익은 감소했습니다.
  • 최소 및 최대 수익률의 변동은 순수익률의 추세와 유사하여 그리드 번호 선택이 수익률에 중요한 영향을 미친다는 것을 알 수 있습니다.

요약하다

그리드 거래 전략에서 그리드의 수를 적절히 설정하는 것은 중요한 작업입니다. 그리드 수를 최적화함으로써 그리드 거래 전략의 수익 성과를 효과적으로 개선하고 위험을 보다 효과적으로 통제할 수 있습니다. 이 글에서는 그리드 번호 설정을 분석하고 구체적인 계산 방법과 샘플 코드를 제공하여 모든 사람이 그리드 거래 전략을 최적화하고 전략의 안정성과 수익성을 개선하는 데 도움이 되기를 바랍니다.

참고: 이 기사의 그리드 백테스팅 코드는 Zhihu Da Shen에서 가져왔습니다. Halcyon, 소스코드 설명은 해당 문서를 참고해주세요알고리즘 트레이딩 연습 일기(XVIII) - 그리드 트레이딩의 세부 사항: 그리드 번호와 장기 수익률의 관계