고주파 거래 전략에 대한 생각 (5)

저자:리디아, 창작: 2023-08-10 15:57:27, 업데이트: 2023-09-12 15:51:54

img

고주파 거래 전략에 대한 생각 (5)

이전 기사 에서, 중간 가격 을 계산 하는 여러 가지 방법 이 소개 되었으며, 수정 된 중간 가격 이 제안 되었다. 이 기사 에서는 이 주제 에 대해 더 깊이 연구 할 것 이다.

필요한 데이터

우리는 100ms의 업데이트 빈도로 라이브 거래에서 수집 된 주문 서구의 상위 10 레벨의 주문 흐름 데이터와 깊이 데이터를 필요로합니다. 단순성을 위해 우리는 입찰 및 요청 가격에 대한 실시간 업데이트를 포함하지 않습니다. 데이터 크기를 줄이기 위해 우리는 깊이 데이터의 100,000 행만을 유지하고 틱-비-틱 시장 데이터를 개별 열로 분리했습니다.

[1]에서:

from datetime import date,datetime
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import ast
%matplotlib inline

[2]에서:

tick_size = 0.0001

[3]에서:

trades = pd.read_csv('YGGUSDT_aggTrade.csv',names=['type','event_time', 'agg_trade_id','symbol', 'price', 'quantity', 'first_trade_id', 'last_trade_id',
       'transact_time', 'is_buyer_maker'])

[4]:

trades = trades.groupby(['transact_time','is_buyer_maker']).agg({
    'transact_time':'last',
    'agg_trade_id': 'last',
    'price': 'first',
    'quantity': 'sum',
    'first_trade_id': 'first',
    'last_trade_id': 'last',
    'is_buyer_maker': 'last',
})

[5]에서:

trades.index = pd.to_datetime(trades['transact_time'], unit='ms')
trades.index.rename('time', inplace=True)
trades['interval'] = trades['transact_time'] - trades['transact_time'].shift()

[6]에서:

depths = pd.read_csv('YGGUSDT_depth.csv',names=['type','event_time', 'transact_time','symbol', 'u1', 'u2', 'u3', 'bids','asks'])

[7]에서:

depths = depths.iloc[:100000]

[8]에서:

depths['bids'] = depths['bids'].apply(ast.literal_eval).copy()
depths['asks'] = depths['asks'].apply(ast.literal_eval).copy()

[9]에서:

def expand_bid(bid_data):
    expanded = {}
    for j, (price, quantity) in enumerate(bid_data):
        expanded[f'bid_{j}_price'] = float(price)
        expanded[f'bid_{j}_quantity'] = float(quantity)
    return pd.Series(expanded)
def expand_ask(ask_data):
    expanded = {}
    for j, (price, quantity) in enumerate(ask_data):
        expanded[f'ask_{j}_price'] = float(price)
        expanded[f'ask_{j}_quantity'] = float(quantity)
    return pd.Series(expanded)
# Apply to each line to get a new df
expanded_df_bid = depths['bids'].apply(expand_bid)
expanded_df_ask = depths['asks'].apply(expand_ask)
# Expansion on the original df
depths = pd.concat([depths, expanded_df_bid, expanded_df_ask], axis=1)

[10]에서:

depths.index = pd.to_datetime(depths['transact_time'], unit='ms')
depths.index.rename('time', inplace=True);

[11]에서:

trades = trades[trades['transact_time'] < depths['transact_time'].iloc[-1]]

이 20 레벨의 시장 분포를 살펴보십시오. 그것은 예상과 일치합니다. 시장 가격에서 더 멀리 떨어져있는 주문이 더 많이 배치됩니다. 또한 구매 주문과 판매 주문은 대략 대칭적입니다.

[14]에서:

bid_mean_list = []
ask_mean_list = []
for i in range(20):
    bid_mean_list.append(round(depths[f'bid_{i}_quantity'].mean(),0))
    ask_mean_list.append(round(depths[f'ask_{i}_quantity'].mean(),0))
plt.figure(figsize=(10, 5))
plt.plot(bid_mean_list);
plt.plot(ask_mean_list);
plt.grid(True)

아웃[14]:

img

예측 정확성의 평가를 촉진하기 위해 깊이 데이터를 거래 데이터와 결합하십시오. 거래 데이터가 깊이 데이터보다 늦게 있는지 확인하십시오. 지연 시간을 고려하지 않고 예측 값과 실제 거래 가격 사이의 평균 제곱 오류를 직접 계산하십시오. 이것은 예측의 정확성을 측정하는 데 사용됩니다.

결과에서, bid와 ask 가격 (mid_price) 의 평균 값에 대한 오류는 가장 높습니다. 그러나 가중된 mid_price로 변경되면 오류는 즉시 크게 감소합니다. 조정된 가중된 mid_price를 사용하여 추가 개선이 관찰됩니다. I^3/2만을 사용하는 피드백을 받은 후, 테스트를 통해 결과가 더 좋다는 것을 알 수 있습니다. 반성하면, 이것은 다른 이벤트 빈도에 기인할 가능성이 높습니다. I가 -1과 1에 가까워지면 낮은 확률 이벤트를 나타냅니다. 이러한 낮은 확률 이벤트를 수정하기 위해, 높은 빈도 이벤트를 예측하는 정확성은 손상됩니다. 따라서, 높은 빈도 이벤트를 우선 순위에 지정하기 위해 일부 조정이 이루어졌습니다 (이 매개 변수들은 순전히 시행착오이며 라이브 트레이딩에서 제한된 실용적 의미를 가지고 있습니다.)

img

결과는 약간 개선되었습니다. 이전 기사에서 언급했듯이 전략은 예측을 위해 더 많은 데이터에 의존해야합니다. 더 많은 깊이와 주문 거래 데이터의 사용 가능성으로 인해 주문서에 집중하는 데 얻은 개선은 이미 약합니다.

[15]에서:

df = pd.merge_asof(trades, depths, on='transact_time', direction='backward')

[17]에서:

df['spread'] = round(df['ask_0_price'] - df['bid_0_price'],4)
df['mid_price'] = (df['bid_0_price']+ df['ask_0_price']) / 2
df['I'] = (df['bid_0_quantity'] - df['ask_0_quantity']) / (df['bid_0_quantity'] + df['ask_0_quantity'])
df['weight_mid_price'] = df['mid_price'] + df['spread']*df['I']/2
df['adjust_mid_price'] = df['mid_price'] + df['spread']*(df['I'])*(df['I']**8+1)/4
df['adjust_mid_price_2'] = df['mid_price'] + df['spread']*df['I']*(df['I']**2+1)/4
df['adjust_mid_price_3'] = df['mid_price'] + df['spread']*df['I']**3/2
df['adjust_mid_price_4'] = df['mid_price'] + df['spread']*(df['I']+0.3)*(df['I']**4+0.7)/3.8

[18]에서:

print('Mean value     Error in mid_price:', ((df['price']-df['mid_price'])**2).sum())
print('Error of pending order volume weighted mid_price:', ((df['price']-df['weight_mid_price'])**2).sum())
print('The error of the adjusted mid_price:', ((df['price']-df['adjust_mid_price'])**2).sum())
print('The error of the adjusted mid_price_2:', ((df['price']-df['adjust_mid_price_2'])**2).sum())
print('The error of the adjusted mid_price_3:', ((df['price']-df['adjust_mid_price_3'])**2).sum())
print('The error of the adjusted mid_price_4:', ((df['price']-df['adjust_mid_price_4'])**2).sum())

아웃[18]:

중간값 오류: 0.0048751924999999845 미드_프라이스 중량된 대기 주문 부피 오류: 0.0048373440193987035 조정된 중간 가격의 오류: 0.004803654771638586 조정된 중간 가격의 오류: 0.004808216498329721 조정된 중간 가격의 오류: 0.004794984755260528 조정된 중간 가격의 오류: 0.0047909595497071375

깊이 의 두 번째 수준 을 고려 하십시오

우리는 이전 기사에서 접근 방식을 따라 매개 변수의 다른 범위를 조사하고 거래 가격의 변화에 따라 중간 가격에 대한 기여를 측정 할 수 있습니다. 첫 번째 깊이 수준과 마찬가지로, I가 증가함에 따라 거래 가격이 증가 할 가능성이 높으며, I의 긍정적 인 기여를 나타냅니다.

두 번째 깊이 수준에 동일한 접근 방식을 적용하면 첫 번째 수준보다 약간 더 작은 효과는 여전히 중요하고 무시해서는 안된다는 것을 알 수 있습니다. 세 번째 깊이 수준 또한 약한 기여를 보여줍니다. 그러나 더 적은 단조화. 더 깊은 깊이에는 거의 참조 값이 없습니다.

서로 다른 기여를 바탕으로, 우리는 불균형 매개 변수 세 단계에 다른 무게를 부여합니다. 다른 계산 방법을 조사함으로써 우리는 예측 오류의 추가 감소를 관찰합니다.

[19]에서:

bins = np.linspace(-1, 1, 50)
df['change'] = (df['price'].pct_change().shift(-1))/tick_size
df['I_bins'] = pd.cut(df['I'], bins, labels=bins[1:])
df['I_2'] = (df['bid_1_quantity'] - df['ask_1_quantity']) / (df['bid_1_quantity'] + df['ask_1_quantity'])
df['I_2_bins'] = pd.cut(df['I_2'], bins, labels=bins[1:])
df['I_3'] = (df['bid_2_quantity'] - df['ask_2_quantity']) / (df['bid_2_quantity'] + df['ask_2_quantity'])
df['I_3_bins'] = pd.cut(df['I_3'], bins, labels=bins[1:])
df['I_4'] = (df['bid_3_quantity'] - df['ask_3_quantity']) / (df['bid_3_quantity'] + df['ask_3_quantity'])
df['I_4_bins'] = pd.cut(df['I_4'], bins, labels=bins[1:])
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8, 5))


axes[0][0].plot(df.groupby('I_bins')['change'].mean())
axes[0][0].set_title('I')
axes[0][0].grid(True)

axes[0][1].plot(df.groupby('I_2_bins')['change'].mean())
axes[0][1].set_title('I 2')
axes[0][1].grid(True)

axes[1][0].plot(df.groupby('I_3_bins')['change'].mean())
axes[1][0].set_title('I 3')
axes[1][0].grid(True)

axes[1][1].plot(df.groupby('I_4_bins')['change'].mean())
axes[1][1].set_title('I 4')
axes[1][1].grid(True)
plt.tight_layout();

아웃[19]:

img

[20]에서:

df['adjust_mid_price_4'] = df['mid_price'] + df['spread']*(df['I']+0.3)*(df['I']**4+0.7)/3.8
df['adjust_mid_price_5'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.3*df['I_2'])/2
df['adjust_mid_price_6'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.3*df['I_2'])**3/2
df['adjust_mid_price_7'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.3*df['I_2']+0.3)*((0.7*df['I']+0.3*df['I_2'])**4+0.7)/3.8
df['adjust_mid_price_8'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.2*df['I_2']+0.1*df['I_3']+0.3)*((0.7*df['I']+0.3*df['I_2']+0.1*df['I_3'])**4+0.7)/3.8

[21]에서:

print('The error of the adjusted mid_price_4:', ((df['price']-df['adjust_mid_price_4'])**2).sum())
print('The error of the adjusted mid_price_5:', ((df['price']-df['adjust_mid_price_5'])**2).sum())
print('The error of the adjusted mid_price_6:', ((df['price']-df['adjust_mid_price_6'])**2).sum())
print('The error of the adjusted mid_price_7:', ((df['price']-df['adjust_mid_price_7'])**2).sum())
print('The error of the adjusted mid_price_8:', ((df['price']-df['adjust_mid_price_8'])**2).sum())

외출 [1]:

조정된 중간 가격의 오류: 0.0047909595497071375 조정된 중간 가격의 오류: 0.0047884350488318714 조정된 중간 가격의 오류: 0.0047778319053133735 조정된 중간 가격의 오류: 0.004773578540592192 조정된 중간 가격의 오류: 0.004771415189297518

트랜잭션 데이터를 고려

트랜잭션 데이터는 장기 및 단위 포지션의 범위를 직접 반영합니다. 결국, 거래는 실제 돈을 포함하고, 주문을하는 것은 훨씬 낮은 비용을 부담하며 의도적인 속임수를 포함 할 수도 있습니다. 따라서, mid_price를 예측할 때 전략은 트랜잭션 데이터에 초점을 맞추어야 합니다.

형태 측면에서, 우리는 평균 주문 도착 양의 불균형을 VI로 정의 할 수 있습니다. Vb와 Vs는 각각 단위 시간 간격 내에서 구매 및 판매 주문의 평균 양을 나타냅니다.

img

이 연구 결과는 짧은 기간에 도착하는 양이 가격 변화 예측에 가장 중요한 영향을 미친다는 것을 보여줍니다. VI가 0.1에서 0.9 사이일 때, 그것은 가격과 부정적인 상관관계를 가지고 있으며, 이 범위 밖에서, 그것은 가격과 긍정적 인 상관관계를 가지고 있습니다. 이것은 시장이 극단적이지 않고 주로 변동할 때 가격이 평균으로 되돌아가는 경향이 있음을 시사합니다. 그러나, 많은 수의 구매 주문이 판매 주문을 압도하는 경우와 같은 극단적인 시장 조건에서 추세가 나타납니다. 이러한 낮은 확률 시나리오를 고려하지 않고도 추세와 VI 사이의 부정적인 선형 관계를 가정하면 중간 가격의 예측 오류를 크게 감소시킵니다. 계수 a는 방정식의 이 관계의 무게를 나타냅니다.

img

[22]에서:

alpha=0.1

[23]에서:

df['avg_buy_interval'] = None
df['avg_sell_interval'] = None
df.loc[df['is_buyer_maker'] == True, 'avg_buy_interval'] = df[df['is_buyer_maker'] == True]['transact_time'].diff().ewm(alpha=alpha).mean()
df.loc[df['is_buyer_maker'] == False, 'avg_sell_interval'] = df[df['is_buyer_maker'] == False]['transact_time'].diff().ewm(alpha=alpha).mean()

[24]에서:

df['avg_buy_quantity'] = None
df['avg_sell_quantity'] = None
df.loc[df['is_buyer_maker'] == True, 'avg_buy_quantity'] = df[df['is_buyer_maker'] == True]['quantity'].ewm(alpha=alpha).mean()
df.loc[df['is_buyer_maker'] == False, 'avg_sell_quantity'] = df[df['is_buyer_maker'] == False]['quantity'].ewm(alpha=alpha).mean()

[25]에서:

df['avg_buy_quantity'] = df['avg_buy_quantity'].fillna(method='ffill')
df['avg_sell_quantity'] = df['avg_sell_quantity'].fillna(method='ffill')
df['avg_buy_interval'] = df['avg_buy_interval'].fillna(method='ffill')
df['avg_sell_interval'] = df['avg_sell_interval'].fillna(method='ffill')

df['avg_buy_rate'] = 1000 / df['avg_buy_interval']
df['avg_sell_rate'] =1000 / df['avg_sell_interval']

df['avg_buy_volume'] = df['avg_buy_rate']*df['avg_buy_quantity']
df['avg_sell_volume'] = df['avg_sell_rate']*df['avg_sell_quantity']

[26]에서:

df['I'] = (df['bid_0_quantity']- df['ask_0_quantity']) / (df['bid_0_quantity'] + df['ask_0_quantity'])
df['OI'] = (df['avg_buy_rate']-df['avg_sell_rate']) / (df['avg_buy_rate'] + df['avg_sell_rate'])
df['QI'] = (df['avg_buy_quantity']-df['avg_sell_quantity']) / (df['avg_buy_quantity'] + df['avg_sell_quantity'])
df['VI'] = (df['avg_buy_volume']-df['avg_sell_volume']) / (df['avg_buy_volume'] + df['avg_sell_volume'])

[27]에서:

bins = np.linspace(-1, 1, 50)
df['VI_bins'] = pd.cut(df['VI'], bins, labels=bins[1:])
plt.plot(df.groupby('VI_bins')['change'].mean());
plt.grid(True)

외출 [1]:

img

[28]에서:

df['adjust_mid_price'] = df['mid_price'] + df['spread']*df['I']/2
df['adjust_mid_price_9'] = df['mid_price'] + df['spread']*(-df['OI'])*2
df['adjust_mid_price_10'] = df['mid_price'] + df['spread']*(-df['VI'])*1.4

[29]에서:

print('The error of the adjusted mid_price:', ((df['price']-df['adjust_mid_price'])**2).sum())
print('The error of the adjusted mid_price_9:', ((df['price']-df['adjust_mid_price_9'])**2).sum())
print('The error of the adjusted mid_price_10:', ((df['price']-df['adjust_mid_price_10'])**2).sum())

외출[29]:

조정된 중간 가격의 오류: 0.0048373440193987035 조정된 중간 가격의 오류: 0.004629586542840461 조정된 중간_가격_10의 오류: 0.004401790287167206

종합적 중간 가격

오더북 불균형과 거래 데이터 모두 중간값을 예측하는 데 도움이 된다는 점을 고려하면, 이 두 매개 변수를 함께 결합할 수 있다. 이 경우 무게의 할당은 임의이며 경계 조건을 고려하지 않는다. 극단적인 경우, 예측된 중간값은 입찰과 수요 가격 사이에 떨어지지 않을 수 있다. 그러나 예측 오류가 줄일 수 있는 한, 이러한 세부 사항은 큰 우려의 여지가 없다.

결국, 예측 오류는 0.00487에서 0.0043로 감소합니다. 이 시점에서, 우리는 주제에 더 깊이 들어가지 않을 것입니다. 그것은 본질적으로 가격 자체를 예측하기 때문에, mid_price를 예측할 때 탐구해야 할 많은 측면이 있습니다. 모든 사람들이 자신의 접근법과 기술을 시도하도록 권장됩니다.

[30]에서:

#Note that the VI needs to be delayed by one to use
df['CI'] = -1.5*df['VI'].shift()+0.7*(0.7*df['I']+0.2*df['I_2']+0.1*df['I_3'])**3 

[31]에서:

df['adjust_mid_price_11'] = df['mid_price'] + df['spread']*(df['CI'])
print('The error of the adjusted mid_price_11:', ((df['price']-df['adjust_mid_price_11'])**2).sum())

아웃[31]:

조정된 중간 가격의 오류: 0.0043001941412563575

요약

이 기사는 심도 데이터와 거래 데이터를 결합하여 중간 가격의 계산 방법을 더욱 향상시킵니다. 정확성을 측정하고 가격 변화 예측의 정확성을 향상시키는 방법을 제공합니다. 전반적으로 매개 변수는 엄격하지 않으며 참조용입니다. 더 정확한 중간 가격으로 다음 단계는 실제 응용 프로그램에서 중간 가격을 사용하여 백테스팅을 수행하는 것입니다. 내용의이 부분은 광범위하므로 업데이트는 일정 기간 동안 중단됩니다.


더 많은