Estratégia de hedge multi-moeda de futuros da Binance
Três relatórios de pesquisa sobre a estratégia de hedge multi-moeda da Binance foram publicados, aqui está o quarto. A conexão dos três primeiros artigos, você deve lê-lo novamente se não o leu, você pode entender a ideia formadora da estratégia, a definição de parâmetros específicos e a lógica da estratégia.
Pesquisa sobre Binance Futures Multi-currency Hedging Strategy Parte 1:https://www.fmz.com/digest-topic/5584
Pesquisa sobre Binance Futures Multi-currency Hedging Strategy Parte 2:https://www.fmz.com/digest-topic/5588
Pesquisa sobre Binance Futures Multi-currency Hedging Strategy Parte 3:https://www.fmz.com/digest-topic/5605
Este artigo é para analisar a situação real do mercado da última semana e resumir os ganhos e perdas.
# Libraries to import
import pandas as pd
import requests
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
%matplotlib inline
symbols = ['BTC','ETH', 'BCH', 'XRP', 'EOS', 'LTC', 'TRX', 'ETC', 'LINK', 'XLM', 'ADA', 'XMR', 'DASH', 'ZEC', 'XTZ', 'BNB', 'ATOM', 'ONT', 'IOTA', 'BAT', 'VET', 'NEO', 'QTUM', 'IOST']
Os dados de 21 de fevereiro a 15 de abril às duas horas da tarde, um total de 77160 * 24, o que reduziu muito a nossa velocidade de backtest, o mecanismo de backtest não é eficiente o suficiente, você pode otimizá-lo.
price_usdt = pd.read_csv('https://www.fmz.com/upload/asset/2b1fa7ab641385067ad.csv',index_col = 0)
price_usdt.shape
(77160, 24)
price_usdt.index = pd.to_datetime(price_usdt.index,unit='ms')
price_usdt_norm = price_usdt/price_usdt.fillna(method='bfill').iloc[0,]
price_usdt_btc = price_usdt.divide(price_usdt['BTC'],axis=0)
price_usdt_btc_norm = price_usdt_btc/price_usdt_btc.fillna(method='bfill').iloc[0,]
class Exchange:
def __init__(self, trade_symbols, leverage=20, commission=0.00005, initial_balance=10000, log=False):
self.initial_balance = initial_balance # Initial asset
self.commission = commission
self.leverage = leverage
self.trade_symbols = trade_symbols
self.date = ''
self.log = log
self.df = pd.DataFrame(columns=['margin','total','leverage','realised_profit','unrealised_profit'])
self.account = {'USDT':{'realised_profit':0, 'margin':0, 'unrealised_profit':0, 'total':initial_balance, 'leverage':0, 'fee':0}}
for symbol in trade_symbols:
self.account[symbol] = {'amount':0, 'hold_price':0, 'value':0, 'price':0, 'realised_profit':0, 'margin':0, 'unrealised_profit':0,'fee':0}
def Trade(self, symbol, direction, price, amount, msg=''):
if self.date and self.log:
print('%-20s%-5s%-5s%-10.8s%-8.6s %s'%(str(self.date), symbol, 'buy' if direction == 1 else 'sell', price, amount, msg))
cover_amount = 0 if direction*self.account[symbol]['amount'] >=0 else min(abs(self.account[symbol]['amount']), amount)
open_amount = amount - cover_amount
self.account['USDT']['realised_profit'] -= price*amount*self.commission # Minus handling fee
self.account['USDT']['fee'] += price*amount*self.commission
self.account[symbol]['fee'] += price*amount*self.commission
if cover_amount > 0: # close position first
self.account['USDT']['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount # profit
self.account['USDT']['margin'] -= cover_amount*self.account[symbol]['hold_price']/self.leverage # Free margin
self.account[symbol]['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount
self.account[symbol]['amount'] -= -direction*cover_amount
self.account[symbol]['margin'] -= cover_amount*self.account[symbol]['hold_price']/self.leverage
self.account[symbol]['hold_price'] = 0 if self.account[symbol]['amount'] == 0 else self.account[symbol]['hold_price']
if open_amount > 0:
total_cost = self.account[symbol]['hold_price']*direction*self.account[symbol]['amount'] + price*open_amount
total_amount = direction*self.account[symbol]['amount']+open_amount
self.account['USDT']['margin'] += open_amount*price/self.leverage
self.account[symbol]['hold_price'] = total_cost/total_amount
self.account[symbol]['amount'] += direction*open_amount
self.account[symbol]['margin'] += open_amount*price/self.leverage
self.account[symbol]['unrealised_profit'] = (price - self.account[symbol]['hold_price'])*self.account[symbol]['amount']
self.account[symbol]['price'] = price
self.account[symbol]['value'] = abs(self.account[symbol]['amount'])*price
return True
def Buy(self, symbol, price, amount, msg=''):
self.Trade(symbol, 1, price, amount, msg)
def Sell(self, symbol, price, amount, msg=''):
self.Trade(symbol, -1, price, amount, msg)
def Update(self, date, close_price): # Update assets
self.date = date
self.close = close_price
self.account['USDT']['unrealised_profit'] = 0
for symbol in self.trade_symbols:
if np.isnan(close_price[symbol]):
continue
self.account[symbol]['unrealised_profit'] = (close_price[symbol] - self.account[symbol]['hold_price'])*self.account[symbol]['amount']
self.account[symbol]['price'] = close_price[symbol]
self.account[symbol]['value'] = abs(self.account[symbol]['amount'])*close_price[symbol]
self.account['USDT']['unrealised_profit'] += self.account[symbol]['unrealised_profit']
self.account['USDT']['total'] = round(self.account['USDT']['realised_profit'] + self.initial_balance + self.account['USDT']['unrealised_profit'],6)
self.account['USDT']['leverage'] = round(self.account['USDT']['margin']/self.account['USDT']['total'],4)*self.leverage
self.df.loc[self.date] = [self.account['USDT']['margin'],self.account['USDT']['total'],self.account['USDT']['leverage'],self.account['USDT']['realised_profit'],self.account['USDT']['unrealised_profit']]
O código da estratégia foi lançado no grupo WeChat em 10 de abril. No início, um grupo de pessoas executou a estratégia 2 ((cort over-rise e long over-fall). Nos primeiros três dias, o retorno foi muito bom, e o retração foi muito baixo. nos dias seguintes, alguns comerciantes ampliaram a alavancagem, alguns até usam todo o valor de seus fundos para operar, e os ganhos atingiram 10% em um dia. A Strategy Square também lançou muitas estratégias de mercado reais, muitas pessoas começaram a ficar insatisfeitas com os parâmetros recomendados conservadores e amplificaram o volume de transações. Após 13 de abril, devido à tendência independente do BNB
O valor líquido alcançou o pico do valor líquido a partir de 4,13 e esteve na fase de retração e lateral.
Alpha = 0.001
#price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.rolling(20).mean() # Ordinary moving average
price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.ewm(alpha=Alpha).mean() # Here is consistent with the strategy, using EMA
trade_symbols = list(set(symbols))
price_usdt_btc_norm_mean = price_usdt_btc_norm2[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.00075,log=False)
trade_value = 300
for row in price_usdt.iloc[-7500:].iterrows():
e.Update(row[0], row[1])
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm2.loc[row[0],symbol] - price_usdt_btc_norm_mean[row[0]]
aim_value = -trade_value*round(diff/0.01,1)
now_value = e.account[symbol]['value']*np.sign(e.account[symbol]['amount'])
if aim_value - now_value > 0.5*trade_value:
e.Buy(symbol, price, round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
if aim_value - now_value < -0.5*trade_value:
e.Sell(symbol, price, -round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
stragey_2a = e
(stragey_2a.df['total']/stragey_2d.initial_balance).plot(figsize=(17,6),grid = True);
Estratégia 1, estratégia de altcoin curto atinge retornos positivos
trade_symbols = list(set(symbols)-set(['LINK','BTC','XTZ','BCH', 'ETH'])) # Selling short currencies
e = Exchange(trade_symbols+['BTC'],initial_balance=10000,commission=0.00075,log=False)
trade_value = 2000
for row in price_usdt.iloc[-7500:].iterrows():
e.Update(row[0], row[1])
empty_value = 0
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
if e.account[symbol]['value'] - trade_value < -120 :
e.Sell(symbol, price, round((trade_value-e.account[symbol]['value'])/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
if e.account[symbol]['value'] - trade_value > 120 :
e.Buy(symbol, price, round((e.account[symbol]['value']-trade_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
empty_value += e.account[symbol]['value']
price = row[1]['BTC']
if e.account['BTC']['value'] - empty_value < -120:
e.Buy('BTC', price, round((empty_value-e.account['BTC']['value'])/price,6),round(e.account['BTC']['realised_profit']+e.account['BTC']['unrealised_profit'],2))
if e.account['BTC']['value'] - empty_value > 120:
e.Sell('BTC', price, round((e.account['BTC']['value']-empty_value)/price,6),round(e.account['BTC']['realised_profit']+e.account['BTC']['unrealised_profit'],2))
stragey_1 = e
(stragey_1.df['total']/stragey_1.initial_balance).plot(figsize=(17,6),grid = True);
Estratégia 2 compra de lucros em queda excessiva e venda de lucros em alta excessiva
Imprimir as informações da conta final mostra que a maioria das moedas trouxe lucros, e o BNB sofreu as maiores perdas.
pd.DataFrame(stragey_2a.account).T.apply(lambda x:round(x,3)).sort_values(by='realised_profit')
# BNB deviation
(price_usdt_btc_norm2.iloc[-7500:].BNB-price_usdt_btc_norm_mean[-7500:]).plot(figsize=(17,6),grid = True);
#price_usdt_btc_norm_mean[-7500:].plot(figsize=(17,6),grid = True);
Se BNB e ATOM forem removidos, o resultado é melhor, mas a estratégia ainda estará no estágio de retração recentemente.
Alpha = 0.001
price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.ewm(alpha=Alpha).mean() # Here is consistent with the strategy, using EMA
trade_symbols = list(set(symbols)-set(['BNB','ATOM']))
price_usdt_btc_norm_mean = price_usdt_btc_norm2[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.00075,log=False)
trade_value = 300
for row in price_usdt.iloc[-7500:].iterrows():
e.Update(row[0], row[1])
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm2.loc[row[0],symbol] - price_usdt_btc_norm_mean[row[0]]
aim_value = -trade_value*round(diff/0.01,1)
now_value = e.account[symbol]['value']*np.sign(e.account[symbol]['amount'])
if aim_value - now_value > 0.5*trade_value:
e.Buy(symbol, price, round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
if aim_value - now_value < -0.5*trade_value:
e.Sell(symbol, price, -round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
stragey_2b = e
(stragey_2b.df['total']/stragey_2b.initial_balance).plot(figsize=(17,6),grid = True);
Nos últimos dois dias, tornou-se popular executar estratégias de moeda convencionais. Vamos testar essa estratégia. Devido à diminuição na variedade de moedas, o trade_value foi apropriadamente aumentado em 4 vezes para comparação, e os resultados tiveram um bom desempenho, especialmente porque o retracement recente foi pequeno.
Deve-se notar que apenas a moeda principal não é tão boa quanto a moeda completa no backtest de tempo mais longo, e há mais retracements.
Alpha = 0.001
price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.ewm(alpha=Alpha).mean() # Here is consistent with the strategy, using EMA
trade_symbols = ['ETH','LTC','EOS','XRP','BCH']
price_usdt_btc_norm_mean = price_usdt_btc_norm2[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.00075,log=False)
trade_value = 1200
for row in price_usdt.iloc[-7500:].iterrows():
e.Update(row[0], row[1])
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm2.loc[row[0],symbol] - price_usdt_btc_norm_mean[row[0]]
aim_value = -trade_value*round(diff/0.01,1)
now_value = e.account[symbol]['value']*np.sign(e.account[symbol]['amount'])
if aim_value - now_value > 0.5*trade_value:
e.Buy(symbol, price, round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
if aim_value - now_value < -0.5*trade_value:
e.Sell(symbol, price, -round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
stragey_2c = e
(stragey_2c.df['total']/e.initial_balance).plot(figsize=(17,6),grid = True);
Como os primeiros relatórios usaram a linha k de nível de hora, e os parâmetros reais são muito diferentes com as situações reais do mercado, agora com a linha k de nível de minutos, você pode ver como definir alguns parâmetros.
Alpha = 0,03 O parâmetro Alpha da média móvel exponencial. Quanto maior a configuração, mais sensível o rastreamento do preço de referência e menos transações. A posição de detenção final também será menor, o que reduz a alavancagem, mas também reduzirá o retorno e os retracements máximos.
Update_base_price_time_interval = 30 * 60 Quantas vezes atualizar o preço de base, em segundos, relacionado com o parâmetro Alpha, quanto menor a configuração Alpha, menor o intervalo pode ser definido
Trade_value: Cada 1% do preço da altcoin (denominado em BTC) desvia-se do valor de detenção do índice, que precisa ser determinado de acordo com o total de fundos investidos e a preferência de risco. Recomenda-se definir 3-10% dos fundos totais. Você pode olhar para o tamanho da alavanca através do backtest do ambiente de pesquisa. O trade_value pode ser menor que o Adjust_value, como metade do Adjust_value, que é equivalente ao valor de detenção de 2% do índice.
Ajuste_valor: O valor do contrato (avaliação USDT) ajusta o valor do desvio. Quando o índice se desvia de * Trade_value-posição atual> Ajuste_value, ou seja, a diferença entre a posição-alvo e a posição atual excede esse valor, a negociação será iniciada. Ajustes muito grandes são lentos, transações muito pequenas são frequentes e não podem ser menores que 10, caso contrário, a transação mínima não será alcançada, é recomendável definí-la em mais de 40% do Trade_value.
É desnecessário dizer, Trade_value está diretamente relacionado com os nossos ganhos e riscos.
Como o Alpha tem dados de frequência mais altos desta vez, é obviamente mais razoável atualizá-lo a cada 1 minuto. Naturalmente, é menor que o original. O número específico pode ser determinado por backtest.
A configuração original da linha 1h K tem pouco efeito. Algumas pessoas querem ajustá-la muito baixa, para que possa estar mais perto da posição alvo. Aqui vamos analisar por que não deve ser feito.
Primeiro, analise o problema das taxas
Pode-se ver que sob a taxa padrão de 0,00075, a taxa de manipulação é 293 e o lucro é 270, que é uma proporção muito alta.
stragey_2a.account['USDT']
{'fee': 293.85972778530453,
'leverage': 0.45999999999999996,
'margin': 236.23559736312995,
'realised_profit': 281.77464608744435,
'total': 10271.146238,
'unrealised_profit': -10.628408369648495}
Alpha = 0.001
#price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.rolling(20).mean() # Ordinary moving average
price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.ewm(alpha=Alpha).mean() # Here is consistent with the strategy, using EMA
trade_symbols = list(set(symbols))
price_usdt_btc_norm_mean = price_usdt_btc_norm2[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0,log=False)
trade_value = 300
for row in price_usdt.iloc[-7500:].iterrows():
e.Update(row[0], row[1])
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm2.loc[row[0],symbol] - price_usdt_btc_norm_mean[row[0]]
aim_value = -trade_value*round(diff/0.01,1)
now_value = e.account[symbol]['value']*np.sign(e.account[symbol]['amount'])
if aim_value - now_value > 10:
e.Buy(symbol, price, round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
if aim_value - now_value < 10:
e.Sell(symbol, price, -round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
stragey_2d = e
(stragey_2d.df['total']/e.initial_balance).plot(figsize=(17,6),grid = True);
O resultado é uma linha reta para cima, o BNB só traz um pouco de voltas e reviravoltas, o menor valor de ajuste capta todas as flutuações.
E se o valor de ajuste for pequeno se houver uma pequena quantidade de taxa de manipulação?
Alpha = 0.001
#price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.rolling(20).mean() # Ordinary moving average
price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.ewm(alpha=Alpha).mean() # Here is consistent with the strategy, using EMA
trade_symbols = list(set(symbols))
price_usdt_btc_norm_mean = price_usdt_btc_norm2[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.00075,log=False)
trade_value = 300
for row in price_usdt.iloc[-7500:].iterrows():
e.Update(row[0], row[1])
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm2.loc[row[0],symbol] - price_usdt_btc_norm_mean[row[0]]
aim_value = -trade_value*round(diff/0.01,1)
now_value = e.account[symbol]['value']*np.sign(e.account[symbol]['amount'])
if aim_value - now_value > 10:
e.Buy(symbol, price, round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
if aim_value - now_value < 10:
e.Sell(symbol, price, -round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
stragey_2e = e
(stragey_2e.df['total']/e.initial_balance).plot(figsize=(17,6),grid = True);
Em consequência, também saiu de uma curva descendente linear.
Em suma, quanto mais baixo for o nível das taxas, menor o valor de ajuste que pode ser definido, mais frequente a transação e maior o lucro.
Problemas com as configurações Alpha
Uma vez que há uma linha de minuto, o preço de referência será atualizado uma vez por minuto, aqui nós simplesmente backtest para determinar o tamanho de alfa.
for Alpha in [0.0001, 0.0003, 0.0006, 0.001, 0.0015, 0.002, 0.004, 0.01, 0.02]:
#price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.rolling(20).mean() # Ordinary moving average
price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.ewm(alpha=Alpha).mean() #Here is consistent with the strategy, using EMA
trade_symbols = list(set(symbols))
price_usdt_btc_norm_mean = price_usdt_btc_norm2[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.00075,log=False)
trade_value = 300
for row in price_usdt.iloc[-7500:].iterrows():
e.Update(row[0], row[1])
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm2.loc[row[0],symbol] - price_usdt_btc_norm_mean[row[0]]
aim_value = -trade_value*round(diff/0.01,1)
now_value = e.account[symbol]['value']*np.sign(e.account[symbol]['amount'])
if aim_value - now_value > 0.5*trade_value:
e.Buy(symbol, price, round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
if aim_value - now_value < -0.5*trade_value:
e.Sell(symbol, price, -round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
print(Alpha, e.account['USDT']['unrealised_profit']+e.account['USDT']['realised_profit'])
0.0001 -77.80281760941007
0.0003 179.38803796199724
0.0006 218.12579924541367
0.001 271.1462377177959
0.0015 250.0014065973528
0.002 207.38692166891275
0.004 129.08021828803027
0.01 65.12410041648158
0.02 58.62356792410955
Finalmente, olhe para os resultados de um backtest de longo tempo. Agora, um após o outro aumentam, e o patrimônio líquido de hoje está em um novo mínimo. Vamos dar-lhe a seguinte confiança. Porque a frequência da linha de minutos é maior, ele vai abrir e fechar posições dentro da hora, por isso o lucro será muito maior.
Outro ponto, sempre usamos um trade_value fixo, o que torna a utilização de fundos no período posterior insuficiente, e a taxa de retorno real ainda pode aumentar muito.
Onde estamos no período de dois meses de backtest?
Alpha = 0.001
#price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.rolling(20).mean() # Ordinary moving average
price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.ewm(alpha=Alpha).mean() # Here is consistent with the strategy, using EMA
trade_symbols = list(set(symbols))
price_usdt_btc_norm_mean = price_usdt_btc_norm2[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.00075,log=False)
trade_value = 300
for row in price_usdt.iloc[:].iterrows():
e.Update(row[0], row[1])
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm2.loc[row[0],symbol] - price_usdt_btc_norm_mean[row[0]]
aim_value = -trade_value*round(diff/0.01,1)
now_value = e.account[symbol]['value']*np.sign(e.account[symbol]['amount'])
if aim_value - now_value > 0.5*trade_value:
e.Buy(symbol, price, round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
if aim_value - now_value < -0.5*trade_value:
e.Sell(symbol, price, -round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
stragey_2f = e
(stragey_2f.df['total']/stragey_2e.initial_balance).plot(figsize=(17,6),grid = True);
(stragey_2f.df['leverage']/stragey_2e.initial_balance).plot(figsize=(17,6),grid = True);