Мультивалютная стратегия хеджирования Binance Futures

часть 1

Нажмите кнопку исследования на странице Панель мониторинга, а затем щелкните стрелку для ввода. Откройте загруженный файл суффикса .pynb и нажмите Shift + Enter, чтобы выполнить построчно. Существуют базовые учебные пособия по использованию исследовательской среды.

Стратегические причины

Binance разместила множество альткоинов на месте. Хотя краткосрочные колебания неопределенны, если вы посмотрите на дневную линию в течение длительного времени, вы обнаружите, что они в основном упали более чем на 90%, а некоторые даже имеют только доли самой высокой ценовой фракции. Однако универсального метода коротких продаж для спота не существует, и нет никаких особых рекомендаций, кроме как не трогать альткоин. За последние два месяца Binance Futures запустила более 20 бессрочных контрактов, большинство из которых являются основными валютами, а некоторые неизвестны. Это дает нам возможность шортить эти комбинации альткоинов. Использование коэффициента корреляции между альткоинами и BTC будет эффективным методом анализа, можно разработать две стратегии.

Принципы стратегии

Первая стратегия: Короткая продажа выбранной корзины альткоинов в децентрализованном эквиваленте, и в то же время покупка длинной позиции на такое же количество позиций BTC для хеджирования, чтобы снизить риски и волатильность. Поскольку цены колеблются, постоянно корректируйте позиции, чтобы значения коротких позиций оставались постоянными и равными длинным позициям. По сути, это операция по продаже индекса цен альткоин-биткойн.

Вторая стратегия: шортинг валют с ценой выше, чем индекс цен альткоин-биткоин, и лонгинг с валютами ниже индекса, чем больше отклонение, тем больше позиция. В то же время, хеджирование нехеджированных позиций с помощью BTC (или нет).

# Libraries to import
import pandas as pd
import requests
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
%matplotlib inline

Проверьте требуемую валюту

В настоящее время в бессрочном контракте Binance перечислены валюты, которые можно получить с помощью интерфейса API, всего 23 (без учета BTC).

#Info = requests.get('https://fapi.binance.com/fapi/v1/exchangeInfo')
#symbols = [symbol_info['baseAsset'] for symbol_info in Info.json()['symbols']]
symbols = ['ETH', 'BCH', 'XRP', 'EOS', 'LTC', 'TRX', 'ETC', 'LINK', 'XLM', 'ADA', 'XMR', 'DASH', 'ZEC', 'XTZ', 'BNB', 'ATOM', 'ONT', 'IOTA', 'BAT', 'VET', 'NEO', 'QTUM', 'IOST']

Во-первых, давайте изучим движение цены альткоинов к биткойну в прошлом году. Я заранее скачал данные и разместил их на форуме, который можно напрямую процитировать в исследовательской среде.

price_btc = pd.read_csv('https://www.fmz.com/upload/asset/1ef1af8ec28a75a2dcb.csv', index_col = 0)
price_btc.index = pd.to_datetime(price_btc.index,unit='ms') #Index date
price_btc.tail()

Результаты:

5 строк × 23 столбца

Сначала нарисуйте цены этих валют, чтобы увидеть тенденцию, данные должны быть нормализованы. Видно, что, за исключением четырех валют, ценовые тренды других валют в основном одинаковы, демонстрируя тенденцию к снижению.

price_btc_norm = price_btc/price_btc.fillna(method='bfill').iloc[0,]
price_btc_norm.plot(figsize=(16,6),grid = True,legend=False);

Отсортировав последние изменения цены, вы можете найти несколько монет, которые явно отличаются, а именно LINK, XTZ, BCH, ETH. Объясните, что они часто могут управлять своим собственным трендом, и их короткая позиция сопряжена с более высоким риском и должна быть исключена из стратегии.

Нарисуйте тепловую карту коэффициента корреляции остальных валют и обнаружите, что тренд ETC и ATOM также относительно особенный и может быть исключен.

price_btc_norm.iloc[-1,].sort_values()[-5:]

Результаты:

ETH     0.600417
ETC 0.661616
BCH 1.141961
XTZ 2.512195
LINK 2.764495
Name: 2020-03-25 00:00:00, dtype: float64
trade_symbols = list(set(symbols)-set(['LINK','XTZ','BCH', 'ETH'])) # Remaining currencies
plt.subplots(figsize=(12, 12)) # Set the screen size
sns.heatmap(price_btc[trade_symbols].corr(), annot=True, vmax=1, square=True, cmap="Blues");

Последняя оставшаяся валюта падала в среднем на 66% в год, очевидно, что есть достаточно места для шортинга. Синтезировав тренд этих монет в ценовой индекс альткоинов, было обнаружено, что он в основном падал на всем пути, он был более стабильным во второй половине прошлого года и начал падать полностью в этом году. В этом исследовании отсеивались «LINK», «XTZ», «BCH», «ETH», «ETC», «ATOM», «BNB», «EOS», «LTC» не участвовали в короткометражке первой стратегии, конкретные детали можно протестировать самостоятельно.

Следует отметить, что текущий индекс альткоинов находится на нижней точке прошлого года. Возможно, это не короткая возможность, а скорее длинная возможность покупки. Вы должны решить это сами.

trade_symbols = list(set(symbols)-set(['LINK','XTZ','BCH', 'ETH', 'ETC','ATOM','BNB','EOS','LTC'])) # You can set the remaining currencies, which you want to subtract.
1-price_btc_norm[trade_symbols].iloc[-1,].mean()

Результаты:

0.6714306758250285
price_btc_norm[trade_symbols].mean(axis=1).plot(figsize=(16,6),grid = True,legend=False);

Данные об устойчивом развитии Binance

Точно так же были собраны данные об устойчивости Binance, вы также можете напрямую процитировать их в своем блокноте, данные представляют собой 1-часовую рыночную линию K с 28 января по 31 марта 2020 года, потому что большая часть бессрочных контрактов Binance была заключена всего два месяца, поэтому данных достаточно для тестирования на истории.

price_usdt = pd.read_csv('https://www.fmz.com/upload/asset/20227de6c1d10cb9dd1.csv ', index_col = 0)
price_usdt.index = pd.to_datetime(price_usdt.index)
price_usdt.tail()

Результаты:

Сначала посмотрите на общую тенденцию с нормализованными данными. Во время мартовского падения, по отношению к цене в начале февраля, цена в целом была снижена, показывая, что риск бессрочного контракта также очень высок. Эта волна спада также является большим испытанием для стратегии.

price_usdt_norm = price_usdt/price_usdt.fillna(method='bfill').iloc[0,]
price_usdt_norm.plot(figsize=(16,6),grid = True,legend=False);

Нарисуйте индексную цену монеты, которую мы хотим продать за биткойн, принцип стратегии состоит в том, чтобы шортить эту кривую, а доходность в основном обратная по этой кривой.

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,]
price_usdt_btc_norm[trade_symbols].mean(axis=1).plot(figsize=(16,6),grid = True);
#price_usdt_btc_norm.mean(axis=1).plot(figsize=(16,6),grid = True,legend=False);

Движок тестирования на истории

Поскольку локальный бэктест FMZ не имеет данных для всех валют и не поддерживает мультивалютный бэктест, необходимо повторно внедрить механизм бэктеста. Поэтому я написал новый движок для тестирования на истории, он относительно прост, но в принципе достаточен. Принимая во внимание комиссию за транзакцию, но в основном игнорируя ставку капитала, не рассматривал ситуацию с поддержанием маржинального капитала. Был зафиксирован общий капитал, занятая маржа и кредитное плечо. Поскольку эта стратегия имеет атрибут, что длинная позиция равна короткой позиции, влияние ставок капитала незначительно.

Бэктест не учитывает ситуацию проскальзывания цены, вы можете самостоятельно увеличить симуляцию комиссии за транзакцию, учитывая низкую комиссию за транзакцию производителя Binance, даже разница в ценовом разрыве на непопулярном валютном рынке очень мала, вы можете использовать метод комиссии айсберга на реальном рынке при размещении ордера, Воздействие не должно быть значительным.

При создании объекта обмена необходимо указать валюту, которой будет торговаться. Покупка длинная, а продажа короткая. Из-за ограничения бессрочного контракта, при открытии позиции длинная и короткая позиции автоматически закрываются вместе. При продаже короткая позиция и количество валют отрицательные. Параметры следующие:

  • trade_symbols: список валют для торговли
  • кредитное плечо: кредитное плечо, влияющее на маржу,
  • Комиссия: Комиссия за транзакцию, по умолчанию 0,00005
  • initial_balance: начальный актив, оценка USDT
  • Журнал: следует ли печатать записи транзакций
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}}
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}

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 transaction fee

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 the 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']
if self.date.hour in [0,8,16]:
pass
self.account['USDT']['realised_profit'] += -self.account[symbol]['amount']*close_price[symbol]*0.01/100

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']]
# First test the backtest engine
e = Exchange(['BTC','XRP'],initial_balance=10000,commission=0,log=True)

e.Buy('BTC',100, 5)
e.Sell('XRP',10, 50)

e.Sell('BTC',105,e.account['BTC']['amount'])
e.Buy('XRP',9,-e.account['XRP']['amount'])

round(e.account['USDT']['realised_profit'],4)
75.0

Первый код стратегии

Логика стратегии:

  • Проверьте цену валюты, если не «нан», вы можете торговать
  • Проверьте стоимость контракта альткоина. Если она меньше целевого значения trade_value, соответствующая разница будет продаваться без покрытия, а если она больше, то будет куплена соответствующая сумма для закрытия позиции.
  • Добавьте короткую стоимость всех альткоинов и скорректируйте позицию BTC, чтобы застраховаться от нее.

Короткая позиция trade_value определяет размер позиции. Установка log = True приведет к печати журнала транзакций

# Need to hedge with BTC
trade_symbols = list(set(symbols)-set(['LINK','XTZ','BCH', 'ETH', 'ETC','ATOM','BNB','EOS','LTC'])) # Remaining currencies
e = Exchange(trade_symbols+['BTC'],initial_balance=10000,commission=0.0005,log=False)
trade_value = 2000
for row in price_usdt.iloc[:].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 < -20 :
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 > 20 :
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 < -20:
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 > 20:
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

Конечная прибыль каждой валюты выглядит следующим образом:

pd.DataFrame(stragey_1.account).T.apply(lambda x:round(x,3))

На двух приведенных ниже графиках показана кривая чистой стоимости и используемое кредитное плечо.

Желтый цвет на кривой чистой стоимости активов — это эффект 1-кратного кредитного плеча, открывающего короткую позицию по индексу альткоина. Видно, что стратегия в основном усиливает колебания индекса, что соответствует ожиданиям. Окончательная двухмесячная доходность составляет 60%, максимальная коррекция — 20%, а максимальное кредитное плечо — около 8 раз. В большинстве случаев это менее чем в 6 раз. Это по-прежнему безопасно. Самое главное, что полное хеджирование привело к тому, что стратегия немного потеряла во время падения 12 марта.

Когда цена короткой продажи валюты растет, а стоимость контракта увеличивается, позиция сокращается, с другой стороны, при получении прибыли позиция увеличивается. Это сохраняет общую стоимость контракта постоянной, даже если стремительные падения имеют ограниченные убытки.

Но о рисках также упоминалось ранее, альткоины, скорее всего, будут работать в своем собственном тренде и могут сильно подняться со дна. Это зависит от того, как его использовать. Если вы настроены оптимистично в отношении альткоина и считаете, что он достиг дна, вы можете действовать в направлении и покупать лонг по этому индексу. Или, если вы оптимистично настроены в отношении определенных валют, вы можете хеджировать их.

(stragey_1.df['total']/stragey_1.initial_balance).plot(figsize=(18,6),grid = True); # Net worth curve
#(2-price_usdt_btc_norm[trade_symbols].mean(axis=1)).plot(figsize=(18,6),grid = True);
# Strategy leverage
stragey_1.df['leverage'].plot(figsize=(18,6),grid = True);

Кроме того, поскольку цена альткоина по отношению к USDT также упала, крайний план не хеджируется, напрямую продавая на понижение, но колебания очень большие, а коррекция высокая.

trade_symbols = list(set(symbols)-set(['LINK','XTZ','BCH', 'ETH', 'ETC','ATOM','BNB','EOS','LTC'])) # Remaining currencies
e = Exchange(trade_symbols+['BTC'],initial_balance=10000,commission=0.0005,log=False)
trade_value = 2000
for row in price_usdt.iloc[:].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 < -20 :
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 > 20 :
pass
#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']
stragey_1b = e
(stragey_1b.df['total']/stragey_1.initial_balance).plot(figsize=(18,6),grid = True); # Net worth curve
(2-price_usdt_btc_norm[trade_symbols].mean(axis=1)).plot(figsize=(18,6),grid = True);

Второй код стратегии

Логика стратегии:

  • Проверьте, есть ли цена или есть цена для торговли
  • Проверьте отклонение цены валюты от индекса
  • Открывайте длинные и короткие позиции на основе суждения об отклонении и оценивайте положение в соответствии с размером отклонения
  • Рассчитайте нехеджированные позиции и хеджируйте с помощью BTC

Trade_value также контролирует размер открытых позиций. Вы также можете изменить коэффициент преобразования diff/0.001

trade_symbols = list(set(symbols)-set(['LINK','XTZ','BCH', 'ETH'])) # Remaining currencies
price_usdt_btc_norm_mean = price_usdt_btc_norm[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols+['BTC'],initial_balance=10000,commission=0.0005,log=False)
trade_value = 300
for row in price_usdt.iloc[:].iterrows():
e.Update(row[0], row[1])
empty_value = 0
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm.loc[row[0],symbol] - price_usdt_btc_norm_mean[row[0]]
aim_value = -trade_value*round(diff/0.01,0)
now_value = e.account[symbol]['value']*np.sign(e.account[symbol]['amount'])
empty_value += now_value
if aim_value - now_value > 50:
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 < -50:
e.Sell(symbol, price, -round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
price = row[1]['BTC']
aim_value = -empty_value
now_value = e.account['BTC']['value']*np.sign(e.account['BTC']['amount'])
if aim_value - now_value > 50:
e.Buy('BTC', price, round((aim_value - now_value)/price, 6),round(e.account['BTC']['realised_profit']+e.account['BTC']['unrealised_profit'],2))
if aim_value - now_value < -50:
e.Sell('BTC', price, -round((aim_value - now_value)/price, 6),round(e.account['BTC']['realised_profit']+e.account['BTC']['unrealised_profit'],2))
stragey_2 = e

Отдача второй стратегии намного лучше, чем первой. За последние два месяца он имеет 100% доходность, но все еще имеет 20% коррекцию. На прошедшей неделе из-за небольших колебаний рынка доходность не очевидна. Общее кредитное плечо невелико. Эту стратегию стоит попробовать. В зависимости от степени отклонения было открыто не более 7800 USDT позиции.

Обратите внимание, что если у валюты заканчивается самостоятельный тренд, например, она выросла в несколько раз относительно индекса, она будет накапливать большое количество коротких позиций в валюте, и такое же резкое снижение также сделает стратегию покупки длинной, которая может ограничить максимальное открытие позиции.

(stragey_2.df['total']/stragey_2.initial_balance).plot(figsize=(18,6),grid = True);
# Summary results by currency
pd.DataFrame(e.account).T.apply(lambda x:round(x,3))
e.df['leverage'].plot(figsize=(18,6),grid = True);

Если результат отказа от хеджирования следующий, разница на самом деле невелика. Потому что длинные и короткие позиции в основном сбалансированы.

trade_symbols = list(set(symbols)-set(['LINK','XTZ','BCH', 'ETH'])) # Remaining currencies
price_usdt_btc_norm_mean = price_usdt_btc_norm[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.0005,log=False)
trade_value = 300
for row in price_usdt.iloc[:].iterrows():
e.Update(row[0], row[1])
empty_value = 0
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm.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'])
empty_value += now_value
if aim_value - now_value > 20:
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 < -20:
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_2.initial_balance).plot(figsize=(18,6),grid = True);
#(stragey_2.df['total']/stragey_2.initial_balance).plot(figsize=(18,6),grid = True); # Can be stacked together

Если вы ссылаетесь на регрессию цены USDT, эффект будет намного хуже

trade_symbols = list(set(symbols)-set(['LINK','XTZ','BCH', 'ETH']))+['BTC'] #Remaining currencies
price_usdt_norm_mean = price_usdt_norm[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.0005,log=False)
trade_value = 300
for row in price_usdt.iloc[:].iterrows():
e.Update(row[0], row[1])
empty_value = 0
for symbol in trade_symbols+['BTC']:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_norm.loc[row[0],symbol] - price_usdt_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'])
empty_value += now_value
if aim_value - now_value > 20:
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 < -20:
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']/stragey_2.initial_balance).plot(figsize=(18,6),grid = True);
(stragey_2b.df['total']/stragey_2.initial_balance).plot(figsize=(18,6),grid = True);

Если ограничить максимальное значение позиции, производительность будет хуже.

trade_symbols = list(set(symbols)-set(['LINK','XTZ','BCH', 'ETH'])) #Remaining currencies
price_usdt_btc_norm_mean = price_usdt_btc_norm[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols+['BTC'],initial_balance=10000,commission=0.0005,log=False)
trade_value = 300
for row in price_usdt.iloc[:].iterrows():
e.Update(row[0], row[1])
empty_value = 0
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm.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'])
empty_value += now_value
if aim_value - now_value > 20 and abs(aim_value)<3000:
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 < -20 and abs(aim_value)<3000:
e.Sell(symbol, price, -round((aim_value - now_value)/price, 6),round(e.account[symbol]['realised_profit']+e.account[symbol]['unrealised_profit'],2))
price = row[1]['BTC']
aim_value = -empty_value
now_value = e.account['BTC']['value']*np.sign(e.account['BTC']['amount'])
if aim_value - now_value > 20:
e.Buy('BTC', price, round((aim_value - now_value)/price, 6),round(e.account['BTC']['realised_profit']+e.account['BTC']['unrealised_profit'],2))
if aim_value - now_value < -20:
e.Sell('BTC', price, -round((aim_value - now_value)/price, 6),round(e.account['BTC']['realised_profit']+e.account['BTC']['unrealised_profit'],2))
stragey_2d = e
(stragey_2d.df['total']/stragey_2.initial_balance).plot(figsize=(17,6),grid = True);

Резюме и риск

Первая стратегия использует тот факт, что общая стоимость альткоинов не так хороша, как у биткоина. Если вы покупаете длинные биткойны, вы можете придерживаться этой стратегии в течение длительного времени. Из-за эквивалентности длинных и коротких позиций вы в принципе не боитесь ставки финансирования 8h. В долгосрочной перспективе процент выигрышей относительно высок. Но я также беспокоюсь, что альткоин в настоящее время находится на дне, и он может исчерпать восходящий тренд и привести к потере этой стратегии.

Вторая стратегия использует функцию ценовой регрессии альткоина, которая растет больше индекса и имеет высокую вероятность отката. Однако он может накапливать слишком много позиций в одной валюте. Если определенная валюта действительно не упадет назад, это приведет к большим убыткам.

Из-за разного времени запуска стратегии и конкретных параметров влияние людей, которые используют эту стратегию в течение длительного времени, не должно быть большим.

Короче говоря, идеальной стратегии не существует, только правильное отношение к стратегии, это в конечном итоге зависит от понимания пользователем рисков и суждения о будущем.

часть 2

Адрес оригинального отчета об исследовании: https://www.fmz.com/digest-topic/5584 Вы можете сначала прочитать его, в этой статье не будет дублированного контента. В этой статье будет освещен процесс оптимизации второй стратегии. После оптимизации, очевидно, улучшается вторая стратегия, рекомендуется обновить стратегию в соответствии с этой статьей. Движок бэктеста добавил статистику платы за обработку.

# 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 = ['ETH', 'BCH', 'XRP', 'EOS', 'LTC', 'TRX', 'ETC', 'LINK', 'XLM', 'ADA', 'XMR', 'DASH', 'ZEC', 'XTZ', 'BNB', 'ATOM', 'ONT', 'IOTA', 'BAT', 'VET', 'NEO', 'QTUM', 'IOST']
price_usdt = pd.read_csv('https://www.fmz.com/upload/asset/20227de6c1d10cb9dd1.csv ', index_col = 0)
price_usdt.index = pd.to_datetime(price_usdt.index)
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']
if self.date.hour in [0,8,16]:
pass
self.account['USDT']['realised_profit'] += -self.account[symbol]['amount']*close_price[symbol]*0.01/100

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']]

Эффективность первоначальной стратегии, после выбора типа валюты, показала хорошие результаты, но все еще есть много удерживающих позиций, как правило, около 4 раз

Принцип:

  • Обновите рыночные котировки и позиции по счету, начальная цена будет записана при первом запуске (вновь добавленные валюты рассчитываются в соответствии с временем присоединения)
  • Обновите индекс, индекс — это индекс цены альткоина-биткоина = среднее (сумма ((цена альткоина / цена биткоина) / (начальная цена альткоина / начальная цена биткоина)))
  • Оценка длительной и короткой работы в соответствии с индексом отклонения и оценка размера позиции в соответствии с величиной отклонения
  • При размещении ордеров количество ордеров определяется стратегией комиссии айсберга, а сделка исполняется в соответствии с новейшей исполняемой ценой
  • Зацикливание еще раз
trade_symbols = list(set(symbols)-set(['LINK','XTZ','BCH', 'ETH'])) # Remaining currencies
price_usdt_btc_norm_mean = price_usdt_btc_norm[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.0005,log=False)
trade_value = 300
for row in price_usdt.iloc[:].iterrows():
e.Update(row[0], row[1])
empty_value = 0
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm.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'])
empty_value += now_value
if aim_value - now_value > 20:
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 < -20:
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);
stragey_2b.df['leverage'].plot(figsize=(18,6),grid = True); # leverage
pd.DataFrame(e.account).T.apply(lambda x:round(x,3)) # holding position

Зачем улучшать

Первоначальная самая большая проблема заключается в сравнении между последней ценой и начальной ценой, начатой стратегией. Со временем он будет становиться все более и более отклоняющимся. Мы будем аккумулировать много позиций в этих валютах. Самая большая проблема с фильтрацией валют заключается в том, что у нас все еще могут быть уникальные валюты в будущем, основанные на нашем прошлом опыте. Ниже приведена производительность режима без фильтрации. На самом деле, когда trade_value = 300, на средней стадии запущенной стратегии, она уже потеряла все. Даже если это не так, LINK и XTZ также удерживают позиции выше 10000USDT, что слишком много. Поэтому мы должны решить эту проблему в бэктесте и пройти тест всех валют.

trade_symbols = list(set(symbols)) # Remaining currencies
price_usdt_btc_norm_mean = price_usdt_btc_norm[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.0005,log=False)
trade_value = 300
for row in price_usdt.iloc[:].iterrows():
e.Update(row[0], row[1])
empty_value = 0
for symbol in trade_symbols:
price = row[1][symbol]
if np.isnan(price):
continue
diff = price_usdt_btc_norm.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'])
empty_value += now_value
if aim_value - now_value > 20:
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 < -20:
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']/stragey_2c.initial_balance).plot(figsize=(17,6),grid = True);
pd.DataFrame(stragey_2c.account).T.apply(lambda x:round(x,3)) # Last holding position
((price_usdt_btc_norm.iloc[-1:] - price_usdt_btc_norm_mean[-1]).T) # Each currency deviates from the initial situation

Поскольку причина проблемы заключается в сравнении с начальной ценой, она может быть все более и более предвзятой. Мы можем сравнить его со скользящей средней за прошедший период времени, протестировать полную валюту и увидеть результаты ниже.

Alpha = 0.05
#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))#All currencies
price_usdt_btc_norm_mean = price_usdt_btc_norm2[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.0005,log=False)
trade_value = 300
for row in price_usdt.iloc[:].iterrows():
e.Update(row[0], row[1])
empty_value = 0
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'])
empty_value += now_value
if aim_value - now_value > 20:
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 < -20:
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
#print(N,stragey_2d.df['total'][-1],pd.DataFrame(stragey_2d.account).T.apply(lambda x:round(x,3))['value'].sum())

Эффективность стратегии полностью оправдала наши ожидания, а доходность практически одинакова. Ситуация с разрывом позиций по счетам в исходной валюте целых валют также плавно перешла, и отката почти нет. Тот же размер открывающей позиции, почти все кредитное плечо ниже 1 раза, 12 марта 2020 года цена упала в крайнем случае, она все еще не превышает 4 раза, а это значит, что мы можем увеличить trade_value, а при том же кредитном плече удвоить прибыль. Окончательная позиция удержания составляет всего один BCH, превышающий 1000USDT, что очень хорошо.

С чего бы понизить позицию? Представьте себе, что присоединение к индексу альткоинов без изменений, одна монета выросла на 100%, и она будет поддерживаться в течение длительного времени. Первоначальная стратегия будет удерживать короткие позиции в размере 300 * 100 = 30000USDT в течение длительного времени, а новая стратегия в конечном итоге будет отслеживать эталонную цену По последней цене вы не будете удерживать ни одной позиции в конце.

(stragey_2d.df['total']/stragey_2d.initial_balance).plot(figsize=(17,6),grid = True);
#(stragey_2c.df['total']/stragey_2c.initial_balance).plot(figsize=(17,6),grid = True);
stragey_2d.df['leverage'].plot(figsize=(18,6),grid = True);
stragey_2b.df['leverage'].plot(figsize=(18,6),grid = True); # Screen currency strategy leverage
pd.DataFrame(stragey_2d.account).T.apply(lambda x:round(x,3))

Что произойдет с валютой с механизмом скрининга, с теми же параметрами, прибыль на более ранней стадии работает лучше, коррекция меньше, но общая доходность немного ниже. Поэтому рекомендуется иметь механизм скрининга.

#price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.rolling(50).mean()
price_usdt_btc_norm2 = price_usdt_btc/price_usdt_btc.ewm(alpha=0.05).mean()
trade_symbols = list(set(symbols)-set(['LINK','XTZ','BCH', 'ETH'])) # Remaining currencies
price_usdt_btc_norm_mean = price_usdt_btc_norm2[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.0005,log=False)
trade_value = 300
for row in price_usdt.iloc[:].iterrows():
e.Update(row[0], row[1])
empty_value = 0
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'])
empty_value += now_value
if aim_value - now_value > 20:
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 < -20:
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_2d.df['total']/stragey_2d.initial_balance).plot(figsize=(17,6),grid = True);
(stragey_2e.df['total']/stragey_2e.initial_balance).plot(figsize=(17,6),grid = True);
stragey_2e.df['leverage'].plot(figsize=(18,6),grid = True);
pd.DataFrame(stragey_2e.account).T.apply(lambda x:round(x,3))

Оптимизация параметров

Чем больше настройка параметра Альфа экспоненциальной скользящей средней, тем чувствительнее отслеживание эталонной цены, чем меньше сделок, тем ниже конечная позиция удержания. При снижении кредитного плеча доходность также снижается. Понизьте максимальную коррекцию, это может увеличить объем транзакций. Конкретные операции с балансом необходимы на основе результатов бэктеста.

Поскольку бэктест представляет собой линию 1h K, его можно обновлять только один раз в час, реальный рынок может обновляться быстрее, и необходимо всесторонне взвешивать конкретные настройки.

Это результат оптимизации:

for Alpha in [i/100 for i in range(1,30)]:
#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))# All currencies
price_usdt_btc_norm_mean = price_usdt_btc_norm2[trade_symbols].mean(axis=1)
e = Exchange(trade_symbols,initial_balance=10000,commission=0.0005,log=False)
trade_value = 300
for row in price_usdt.iloc[:].iterrows():
e.Update(row[0], row[1])
empty_value = 0
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'])
empty_value += now_value
if aim_value - now_value > 20:
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 < -20:
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
# These are the final net value, the initial maximum backtest, the final position size, and the handling fee
print(Alpha, round(stragey_2d.account['USDT']['total'],1), round(1-stragey_2d.df['total'].min()/stragey_2d.initial_balance,2),round(pd.DataFrame(stragey_2d.account).T['value'].sum(),1),round(stragey_2d.account['USDT']['fee']))
0.01 21116.2 0.14 15480.0 2178.0
0.02 20555.6 0.07 12420.0 2184.0
0.03 20279.4 0.06 9990.0 2176.0
0.04 20021.5 0.04 8580.0 2168.0
0.05 19719.1 0.03 7740.0 2157.0
0.06 19616.6 0.03 7050.0 2145.0
0.07 19344.0 0.02 6450.0 2133.0
0.08 19174.0 0.02 6120.0 2117.0
0.09 18988.4 0.01 5670.0 2104.0
0.1 18734.8 0.01 5520.0 2090.0
0.11 18532.7 0.01 5310.0 2078.0
0.12 18354.2 0.01 5130.0 2061.0
0.13 18171.7 0.01 4830.0 2047.0
0.14 17960.4 0.01 4770.0 2032.0
0.15 17779.8 0.01 4531.3 2017.0
0.16 17570.1 0.01 4441.3 2003.0
0.17 17370.2 0.01 4410.0 1985.0
0.18 17203.7 0.0 4320.0 1971.0
0.19 17016.9 0.0 4290.0 1955.0
0.2 16810.6 0.0 4230.6 1937.0
0.21 16664.1 0.0 4051.3 1921.0
0.22 16488.2 0.0 3930.6 1902.0
0.23 16378.9 0.0 3900.6 1887.0
0.24 16190.8 0.0 3840.0 1873.0
0.25 15993.0 0.0 3781.3 1855.0
0.26 15828.5 0.0 3661.3 1835.0
0.27 15673.0 0.0 3571.3 1816.0
0.28 15559.5 0.0 3511.3 1800.0
0.29 15416.4 0.0 3481.3 1780.0

часть 3

Просто грубая симуляция, чтобы у каждого было конкретное представление о сумме потерянной маржи. Вы можете скачать блокнот и загрузить его в исследовательскую среду FMZ, а также запустить код самостоятельно.

Оценка риска Binance по стратегиям тренда с короткими продажами по сравнению с ростом и покупкой на длинные позиции по сравнению с падением

Первый взгляд на исходный отчет: https://www.fmz.com/digest-topic/5584 и улучшенный отчет: https://www.fmz.com/digest-topic/5588

Стратегия находится в открытом доступе уже 4 дня. Ранняя стадия сформировалась очень хорошо, с высокой доходностью и небольшим количеством коррекций, так что многие пользователи используют очень высокое кредитное плечо, чтобы играть в азартные игры с доходностью 10% в день. Однако, как указывалось в первоначальном докладе, идеальной стратегии не существует. Короткие продажи с повышением и покупки с длинным трендом с падением используют характеристики альткоинов для совместного роста и падения. Если валюта выходит из уникального тренда, она накапливает много удерживающих позиций, хотя для отслеживания начальной цены использовалась скользящая средняя, риски все еще существуют. В этом отчете в основном количественно оцениваются конкретные риски и почему рекомендуемый параметр trade_value составляет 3% от общего объема средств.

Для того, чтобы выделить код, который мы поместили в расширенную часть этой части, каждый должен попробовать сначала запустить следующий код (начиная с части библиотек импорта).

Чтобы смоделировать, мы предполагаем, что существует 20 валют, но нужно только добавить BTC и ETH и использовать BTC для представления 19 валют с постоянными ценами. ETH представляет собой валюту с независимой валютой тренда. Поскольку это всего лишь симуляция, здесь нет необходимости отслеживать начальную цену по скользящей средней, предполагая, что цена растет быстрыми темпами.

Во-первых, смоделируйте ситуацию, когда цена единой валюты продолжает расти. Stop_loss указывает на отклонение стоп-лосса. Здесь только симуляция. Реальная ситуация будет иметь прерывистую коррекцию, она будет не такой плохой, как симуляция.

Предположим, что нет отката к этой валюте, когда отклонение стоп-лосса составляет 0,41, ETH в это время вырос на 44%, а результаты окончательно потеряли в 7 раз от торговой стоимости, то есть trade_value * 7. Если trade_value установлен на 3% от общей суммы средств, то убыток = общие средства * 0,03 * 7. Максимальная коррекция составляет около 0,03 * 7 = 21%.

Вы можете оценить свою собственную толерантность к риску на основе приведенных ниже результатов.

btc_price = [1]*500 # Bitcoin price, always unchanged
eth_price = [i/100. for i in range(100,500)] # Ethereum, up 1% in one cycle

for stop_loss in [i/1000. for i in range(10,1500,50)]:
e = Exchange(['BTC','ETH'],initial_balance=10000,commission=0.0005,log=False)
trade_value = 300 # 300 transactions

for i in range(200):

index = (btc_price[i]*19+eth_price[i])/20. # index

e.Update(i,{'BTC':btc_price[i], 'ETH':eth_price[i]})

diff_btc = btc_price[i] - index # deviation
diff_eth = eth_price[i] - index

btc_value = e.account['BTC']['value']*np.sign(e.account['BTC']['amount'])
eth_value = e.account['ETH']['value']*np.sign(e.account['ETH']['amount'])

aim_btc_value = -trade_value*round(diff_btc/0.01,1)*19 # Here BTC replaces 19 currencies
aim_eth_value = -trade_value*round(diff_eth/0.01,1)

if aim_btc_value - btc_value > 20:
e.Buy('BTC',btc_price[i],(aim_btc_value - btc_value)/btc_price[i])

if aim_eth_value - eth_value < -20 and diff_eth < stop_loss:
e.Sell('ETH',eth_price[i], (eth_value-aim_eth_value)/eth_price[i],diff_eth)

if diff_eth > stop_loss and eth_value < 0: # Stop loss
stop_price = eth_price[i]
e.Buy('ETH',eth_price[i], (-eth_value)/eth_price[i],diff_eth)

print('Currency price:',stop_price,' Stop loss deviation:', stop_loss,'Final balance:',e.df['total'].iloc[-1], ' Multiple of losing trade volume:',round((e.initial_balance-e.df['total'].iloc[-1])/300,1))
Currency price: 1.02  Stop loss deviation: 0.01 Final balance: 9968.840396  Multiple of losing trade volume: 0.1
Currency price: 1.07 Stop loss deviation: 0.06 Final balance: 9912.862738 Multiple of losing trade volume: 0.3
Currency price: 1.12 Stop loss deviation: 0.11 Final balance: 9793.616067 Multiple of losing trade volume: 0.7
Currency price: 1.17 Stop loss deviation: 0.16 Final balance: 9617.477263 Multiple of losing trade volume: 1.3
Currency price: 1.23 Stop loss deviation: 0.21 Final balance: 9337.527299 Multiple of losing trade volume: 2.2
Currency price: 1.28 Stop loss deviation: 0.26 Final balance: 9051.5166 Multiple of losing trade volume: 3.2
Currency price: 1.33 Stop loss deviation: 0.31 Final balance: 8721.285267 Multiple of losing trade volume: 4.3
Currency price: 1.38 Stop loss deviation: 0.36 Final balance: 8350.582251 Multiple of losing trade volume: 5.5
Currency price: 1.44 Stop loss deviation: 0.41 Final balance: 7856.720861 Multiple of losing trade volume: 7.1
Currency price: 1.49 Stop loss deviation: 0.46 Final balance: 7406.412066 Multiple of losing trade volume: 8.6
Currency price: 1.54 Stop loss deviation: 0.51 Final balance: 6923.898356 Multiple of losing trade volume: 10.3
Currency price: 1.59 Stop loss deviation: 0.56 Final balance: 6411.276143 Multiple of losing trade volume: 12.0
Currency price: 1.65 Stop loss deviation: 0.61 Final balance: 5758.736222 Multiple of losing trade volume: 14.1
Currency price: 1.7 Stop loss deviation: 0.66 Final balance: 5186.230956 Multiple of losing trade volume: 16.0
Currency price: 1.75 Stop loss deviation: 0.71 Final balance: 4588.802975 Multiple of losing trade volume: 18.0
Currency price: 1.81 Stop loss deviation: 0.76 Final balance: 3841.792751 Multiple of losing trade volume: 20.5
Currency price: 1.86 Stop loss deviation: 0.81 Final balance: 3193.215479 Multiple of losing trade volume: 22.7
Currency price: 1.91 Stop loss deviation: 0.86 Final balance: 2525.155765 Multiple of losing trade volume: 24.9
Currency price: 1.96 Stop loss deviation: 0.91 Final balance: 1837.699982 Multiple of losing trade volume: 27.2
Currency price: 2.02 Stop loss deviation: 0.96 Final balance: 988.009942 Multiple of losing trade volume: 30.0
Currency price: 2.07 Stop loss deviation: 1.01 Final balance: 260.639618 Multiple of losing trade volume: 32.5
Currency price: 2.12 Stop loss deviation: 1.06 Final balance: -483.509646 Multiple of losing trade volume: 34.9
Currency price: 2.17 Stop loss deviation: 1.11 Final balance: -1243.486107 Multiple of losing trade volume: 37.5
Currency price: 2.24 Stop loss deviation: 1.16 Final balance: -2175.438384 Multiple of losing trade volume: 40.6
Currency price: 2.28 Stop loss deviation: 1.21 Final balance: -2968.19255 Multiple of losing trade volume: 43.2
Currency price: 2.33 Stop loss deviation: 1.26 Final balance: -3774.613275 Multiple of losing trade volume: 45.9
Currency price: 2.38 Stop loss deviation: 1.31 Final balance: -4594.305499 Multiple of losing trade volume: 48.6
Currency price: 2.44 Stop loss deviation: 1.36 Final balance: -5594.651063 Multiple of losing trade volume: 52.0
Currency price: 2.49 Stop loss deviation: 1.41 Final balance: -6441.474964 Multiple of losing trade volume: 54.8
Currency price: 2.54 Stop loss deviation: 1.46 Final balance: -7299.652662 Multiple of losing trade volume: 57.7

При моделировании ситуации непрерывного снижения снижение сопровождается уменьшением стоимости контракта, поэтому риск выше, чем рост, а по мере падения цены скорость увеличения убытков ускоряется. При значении отклонения стоп-лосса -0,31 цена валюты в это время падает на 33%, а убыток составляет 6,5 сделок. Если сумма сделки trade_value установлена на уровне 3% от общей суммы средств, максимальная коррекция составляет около 0,03 * 6,5 = 19,5%.

btc_price = [1]*500 # Bitcoin price, always unchanged
eth_price = [2-i/100. for i in range(100,200)] # Ethereum

for stop_loss in [-i/1000. for i in range(10,1000,50)]:
e = Exchange(['BTC','ETH'],initial_balance=10000,commission=0.0005,log=False)
trade_value = 300 # 300 transactions

for i in range(100):

index = (btc_price[i]*19+eth_price[i])/20. # index

e.Update(i,{'BTC':btc_price[i], 'ETH':eth_price[i]})

diff_btc = btc_price[i] - index # deviation
diff_eth = eth_price[i] - index

btc_value = e.account['BTC']['value']*np.sign(e.account['BTC']['amount'])
eth_value = e.account['ETH']['value']*np.sign(e.account['ETH']['amount'])

aim_btc_value = -trade_value*round(diff_btc/0.01,1)*19 # Here BTC replaces 19 currencies
aim_eth_value = -trade_value*round(diff_eth/0.01,1)

if aim_btc_value - btc_value < -20:
e.Sell('BTC',btc_price[i],-(aim_btc_value - btc_value)/btc_price[i])

if aim_eth_value - eth_value > 20 and diff_eth > stop_loss:
e.Buy('ETH',eth_price[i], -(eth_value-aim_eth_value)/eth_price[i],diff_eth)

if diff_eth < stop_loss and eth_value > 0:
e.Sell('ETH',eth_price[i], (eth_value)/eth_price[i],diff_eth)
stop_price = eth_price[i]

print('Currency price:',round(stop_price,2),' Stop loss deviation:', stop_loss,'Final balance:',e.df['total'].iloc[-1], ' Multiple of losing trade volume:',round((e.initial_balance-e.df['total'].iloc[-1])/300,1))
Currency price: 0.98  Stop loss deviation: -0.01 Final balance: 9983.039091  Multiple of losing trade volume: 0.1
Currency price: 0.93 Stop loss deviation: -0.06 Final balance: 9922.200148 Multiple of losing trade volume: 0.3
Currency price: 0.88 Stop loss deviation: -0.11 Final balance: 9778.899361 Multiple of losing trade volume: 0.7
Currency price: 0.83 Stop loss deviation: -0.16 Final balance: 9545.316075 Multiple of losing trade volume: 1.5
Currency price: 0.77 Stop loss deviation: -0.21 Final balance: 9128.800213 Multiple of losing trade volume: 2.9
Currency price: 0.72 Stop loss deviation: -0.26 Final balance: 8651.260863 Multiple of losing trade volume: 4.5
Currency price: 0.67 Stop loss deviation: -0.31 Final balance: 8037.598952 Multiple of losing trade volume: 6.5
Currency price: 0.62 Stop loss deviation: -0.36 Final balance: 7267.230651 Multiple of losing trade volume: 9.1
Currency price: 0.56 Stop loss deviation: -0.41 Final balance: 6099.457595 Multiple of losing trade volume: 13.0
Currency price: 0.51 Stop loss deviation: -0.46 Final balance: 4881.767442 Multiple of losing trade volume: 17.1
Currency price: 0.46 Stop loss deviation: -0.51 Final balance: 3394.414792 Multiple of losing trade volume: 22.0
Currency price: 0.41 Stop loss deviation: -0.56 Final balance: 1575.135344 Multiple of losing trade volume: 28.1
Currency price: 0.35 Stop loss deviation: -0.61 Final balance: -1168.50508 Multiple of losing trade volume: 37.2
Currency price: 0.29 Stop loss deviation: -0.66 Final balance: -4071.007983 Multiple of losing trade volume: 46.9
Currency price: 0.25 Stop loss deviation: -0.71 Final balance: -7750.361195 Multiple of losing trade volume: 59.2
Currency price: 0.19 Stop loss deviation: -0.76 Final balance: -13618.366286 Multiple of losing trade volume: 78.7
Currency price: 0.14 Stop loss deviation: -0.81 Final balance: -20711.473968 Multiple of losing trade volume: 102.4
Currency price: 0.09 Stop loss deviation: -0.86 Final balance: -31335.965608 Multiple of losing trade volume: 137.8
Currency price: 0.04 Stop loss deviation: -0.91 Final balance: -51163.223715 Multiple of losing trade volume: 203.9
Currency price: 0.04 Stop loss deviation: -0.96 Final balance: -81178.565715 Multiple of losing trade volume: 303.9
# Libraries to import
import pandas as pd
import requests
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
%matplotlib inline
price_usdt = pd.read_csv('https://www.fmz.com/upload/asset/20227de6c1d10cb9dd1.csv ', index_col = 0)
price_usdt.index = pd.to_datetime(price_usdt.index)
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 positions 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']]

часть 4

Недавний обзор мультивалютной стратегии хеджирования фьючерсов Binance и результаты тестирования K-line на минутном уровне

Опубликованы три исследовательских отчета о мультивалютной стратегии хеджирования Binance, вот четвертый. Связь первых трех статей, вы должны прочитать ее еще раз, если вы ее не читали, вы можете понять формирующую идею стратегии, настройку конкретных параметров и логику стратегии.

Исследование мультивалютной стратегии хеджирования Binance Futures Часть 1: https://www.fmz.com/digest-topic/5584

Исследование мультивалютной стратегии хеджирования Binance Futures Часть 2: https://www.fmz.com/digest-topic/5588

Исследование мультивалютной стратегии хеджирования Binance Futures Часть 3: https://www.fmz.com/digest-topic/5605

В этой статье мы рассмотрим реальную ситуацию на рынке за последнюю неделю и подведем итоги прибылей и убытков. После сканирования данных минутной линии K Binance Futures за последние два месяца исходные результаты бэктеста линии K за 1 час могут быть обновлены, что может лучше объяснить значение некоторых настроек параметров.

# 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']

Данные линии K минутного уровня

Данные с 21 февраля по 15 апреля в два часа дня, итого 77160*24, что сильно снизило нашу скорость бэктеста, движок бэктеста недостаточно эффективен, вы можете оптимизировать его самостоятельно. В дальнейшем я буду регулярно отслеживать последние данные.

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']]

Обзор прошлой недели

Код стратегии был опубликован в группе WeChat 10 апреля. Вначале группа людей запустила стратегию 2 (короткий подъем и длительное падение). В первые три дня отдача была очень хорошей, а коррекция была очень низкой. В последующие дни некоторые трейдеры увеличили кредитное плечо, некоторые даже использовали всю сумму своих средств для работы, и прибыль достигла 10% за один день. Strategy Square также выпустила множество реальных рыночных стратегий, многие люди стали недовольны консервативными рекомендуемыми параметрами, и увеличили объем сделок. После 13 апреля из-за самостоятельного тренда BNB прибыль начала отступать и вбок. Если вы посмотрите на стандартные 3% trade_value, он, вероятно, отступил на 1%. Однако из-за увеличенных значений параметров многие трейдеры зарабатывают меньше и много теряют. Эта волна отката была довольно своевременной, немного успокоив всех.

давайте посмотрим на полный валютный бэктест Стратегии 2. Здесь, поскольку это минутное обновление уровня, необходимо настроить параметр Alpha. С реальной рыночной точки зрения тенденция кривой является последовательной, что указывает на то, что наш бэктест можно использовать в качестве сильного ориентира. Чистая стоимость достигла пика чистой стоимости с 4,13 и далее и находилась в фазе коррекции и бокового движения.

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);

Стратегия 1, стратегия коротких альткоинов достигает положительной доходности

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);

Стратегия 2: покупка длинных перепадов и короткие продажи анализ прибыли с повышенным ростом

Распечатка окончательной информации о счете показывает, что большинство валют принесли прибыль, и BNB понес наибольшие убытки. Это также в основном связано с тем, что BNB вышел из волны независимого тренда, сильно вырос, и наибольшее отклонение составляет 0,06.

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);

Если BNB и ATOM будут удалены, результат будет лучше, но стратегия все еще будет находиться в стадии коррекции в последнее время.

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);

В последние два дня стало популярным запускать основные валютные стратегии. Давайте протестируем эту стратегию. Из-за уменьшения валютного разнообразия trade_value был соответственно увеличен в 4 раза для сравнения, и результаты показали хорошие результаты, тем более что недавняя коррекция была небольшой.

Следует отметить, что только основная валюта не так хороша, как полная валюта в более длительном тесте, и есть больше откатов. Вы можете провести свой собственный бэктест на часовой линии ниже, главным образом потому, что валюта менее рассредоточена, а волатильность вместо этого растет.

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);

Обработка комиссий и анализ параметров стратегии

Поскольку в первых нескольких отчетах использовалась линия k часового уровня, а фактические параметры сильно отличаются от реальных рыночных ситуаций, теперь с линией k минутного уровня вы можете увидеть, как установить некоторые параметры. Сначала посмотрите на настройки параметров по умолчанию:

  • Альфа = 0,03 Параметр Альфа экспоненциальной скользящей средней. Чем больше настройка, тем чувствительнее отслеживание эталонной цены и тем меньше транзакций. Конечная позиция удержания также будет ниже, что уменьшит кредитное плечо, но также уменьшит доходность и максимальные откаты.
  • Update_base_price_time_interval = 30 * 60 Как часто обновлять базовую цену, в секундах, связанных с параметром Alpha, чем меньше настройка Alpha, тем меньший интервал можно установить
  • Trade_value: Каждый 1% цены альткоина (деноминированного в BTC) отклоняется от значения удержания индекса, которое необходимо определять в соответствии с общей суммой вложенных средств и предпочтениями риска. Рекомендуется установить 3–10% от общего объема средств. Вы можете посмотреть на размер рычага через бэктест исследовательской среды. Trade_value может быть меньше Adjust_value, например, половина Adjust_value, что эквивалентно холдинговой стоимости в 2% от индекса.
  • Adjust_value: Стоимость контракта (оценка USDT) корректирует значение отклонения. Когда индекс отклоняется от *Trade_value-текущей позиции> Adjust_value, то есть разница между целевой позицией и текущей позицией превышает это значение, начнутся торги. Слишком большие корректировки происходят медленно, слишком маленькие транзакции часты и не могут быть меньше 10, иначе минимальная транзакция не будет достигнута, рекомендуется установить ее более чем на 40% от Trade_value.

Излишне говорить, что Trade_value напрямую связано с нашими доходами и рисками. Если Trade_value не был изменен, он должен быть прибыльным до сих пор.

Поскольку на этот раз Alpha имеет данные с более высокой частотой, очевидно, что разумнее обновлять их каждые 1 минуту. Естественно, он меньше оригинального. Конкретное число можно определить с помощью бэктеста.

Adjust_value всегда рекомендовал более 40% Trade_value. Первоначальная настройка линии 1h K мало влияет. Некоторые люди хотят отрегулировать его очень низко, чтобы он мог быть ближе к целевому положению. Здесь мы разберем, почему этого делать не стоит.

Во-первых, проанализируйте проблему обработки сборов

Видно, что при ставке по умолчанию 0,00075 комиссия за обработку составляет 293, а прибыль — 270, что является очень высокой долей. Мы устанавливаем плату за обработку на 0, а Adjust_value на 10, чтобы посмотреть, что произойдет.

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);

В результате получается прямая линия вверх, BNB приносит лишь небольшие повороты, нижняя Adjust_value улавливает каждое колебание. Если нет платы за обработку, прибыль будет отличной.

Что делать, если adjustment_value небольшой, если есть небольшая плата за обработку?

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);

В результате он также вышел из прямой нисходящей кривой. Это легко понять, если подумать, частые корректировки в пределах небольшого спреда только потеряют комиссию за обработку.

В совокупности, чем ниже уровень комиссии, тем меньший Adjust_value можно установить, тем чаще совершаются транзакции и тем выше прибыль.

Проблемы с настройками альфа-канала

Так как есть минутная линия, эталонная цена будет обновляться раз в минуту, здесь мы просто проводим бэктест, чтобы определить размер альфы. Текущее рекомендуемое значение альфа-канала — 0,001.

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

Результаты тестирования минутной линии за последние два месяца

Наконец, посмотрите на результаты длительного бэктеста. Просто сейчас один за другим растут, и сегодняшний собственный капитал находится на новом минимуме. Давайте дадим вам следующую уверенность. Поскольку частота минутной линии выше, она будет открывать и закрывать позиции в течение часа, поэтому прибыль будет намного выше.

Другой момент, мы всегда использовали фиксированную trade_value, что делает использование средств в более поздний период недостаточным, и фактическая норма прибыли все еще может значительно увеличиться.

Где мы находимся в двухмесячном периоде бэктеста?

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);

Источник