Как я писал робота для арбитражной торговли биткоинами

от автора


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

Выяснилось, что две наиболее популярные биржи, на которых торгуют биткоинами — это MtGox и BTC-e. Я положил деньги на одну из них и принялся думать над тем, как предсказать изменение цены, а также, как это автоматизировать. Дело осложнялось тем, что на этих биржах можно покупать и продавать только на свои средства, поэтому нельзя играть на понижение, занимая короткую позицию, потому что, как говорил Матроскин: «Чтобы продать что-нибудь ненужное, нужно сначала купить что-нибудь ненужное».

Обзор

Почитав материалы по теме, я пришел к выводу, что существует две наиболее распространенные стратегии для алгоритмической торговли:

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

При рассмотрении применения этих стратегий к торговле биткоинами оказалось, что первая убыточна из-за комиссий бирж — 0.6% на MtGox и 0.2% на BTC-e за одну сделку, в то время как средний спред (разница между ценой покупки и цены продажи) на обоих биржах меньше, чем удвоенная комиссия за сделку.

Вторая стратегия в чистом виде оказалась неприменима: хотя разница в цене между BTC-e и MtGox обычно есть, причем на BTC-e, как правило, дешевле, чем на MtGox, перевод денег с MtGox на BTC-e затруднен задержкой вывода в несколько дней, а также, комиссией, которая в среднем съедает всю прибыль. Но если взглянуть на объем торгов, то можно обнаружить, что на MtGox он почти в 10 раз выше, чем на BTC-e, из чего можно сделать вывод, что «законодателем цены» является MtGox, а BTC-e его просто догоняет с некоторой поправкой (если бы не догоняла, был бы возможен «сильный» арбитраж с переводом денег/биткоинов туда-сюда). В результате было принято решение писать бота для торговли на BTC-e, который ориентируется на цену с MtGox.

Построение модели

Рассмотрим отношение цены на MtGox и BTC-e, а точнее, его натуральный логарифм — для «равноправности» двух цен, назовем его R:

Посмотрим, как меняется цена на BTC-e в зависимости от R:

Заметно, что через некоторое время после резкого увеличения R цена на BTC-e растет, а после уменьшения — напротив, падает. При этом, при R < 0 цена всегда падает, а при R > 0.1 цена всегда растет. Чтобы выяснить, что же между, построим функцию распределения R:

Видно, что на интервале [-0.1; 0.11] находится большинство значений R, при этом в нем она распределена почти равномерно, поэтому по значению R в этом диапазоне нельзя однозначно определить, как будет меняться цена; в то же время, изменение R на некоторую пороговую величину Δ с вероятностью более 50% говорит о том, что цена вырастет/упадет.

Используя полученные результаты, построим следующую стратегию (не забываем, что при торговле биткоинами невозможны короткие позиции, только длинные):
в каждый момент времени оптимальная для BTC на счету от общего капитала должно быть равно , при этом, фактически совершаем покупку/продажу только тогда, когда R меняется по сравнению со значением на момент последней противоположной операции не менее, чем на некоторую величину Δ.
Из очевидных соображений, подкрепленных результатами численного моделирования, примем зависимость на интервале [Rmin; Rmax] линейной:

Оценим величину Δ. Она должна быть не менее минимального роста цены на BTC-e, при котором покупка и последующая продажа будут выгодными. Последняя складывается из комиссии за сделки: 2 * 0.2% и среднего спреда, который примем равным также 2 * 0.2% (равновесное состояние, в котором невозможен маркетмейкинг, а ближайшие друг к другу неисполненные ордеры на покупку и продажу выставляются так, чтобы скомпенсировать комиссию), т.е. Δ > 0.008 для безубыточности. Чтобы была прибыль, нужно взять чуть больше, поэтому пусть будет Δ = 0.01.

Проверка стратегии

Для проверки получившейся модели на реальных данных о ценах, напишем

скрипт на Matlab

function trademodel()     r_min = -0.01; % нижняя граница значений R, при достижении которой продаем все BTC     r_max = 0.11;  % верхняя граница значений R, при достижении которой покупаем BTC на все деньги     delta = 0.01;  % минимальное изменение значения R, при котором совершается торговая операция      data = importdata('data.txt', ' ');     time = data(:, 1);     time = time / 3600 / 24 + datenum(1970, 1, 1); % преобразуем unix timestamp в дату matlab     mtgox_buy = data(:, 2) ;     mtgox_sell = data(:, 3);     btce_buy = data(:, 4) ;     btce_sell = data(:, 5);         btce_btc_amount = 0;     btce_usd_amount = btce_buy(1) * 1.002;              function state = total_in_usd(index)          state = btce_usd_amount + btce_sell(index) * btce_btc_amount / 1.002;     end          function state = total_in_btc(index)         state = btce_btc_amount + (btce_usd_amount - 1) / (btce_buy(index) * 1.002);     end      function expected_btc = btc_for_ratio(ratio)         if ratio <= r_min             expected_btc = 0;         elseif ratio >= r_max             expected_btc = 1;         else             expected_btc = (ratio - r_min) / (r_max - r_min);         end     end              count = 1;     trading_times = zeros(1, length(time));     trading_states = zeros(1, length(time));          trading_times(1) = time(1);     trading_states(1) = total_in_usd(1);          last_buy_ratio = 0.0;     last_sell_ratio = 0.0;          for i = 1:length(time)         ratio = log(mtgox_sell(i)) - log(btce_buy(i));         expected_btce = btc_for_ratio(ratio) * total_in_btc(i);         btce_diff = expected_btce - btce_btc_amount;                  if btce_diff > 0.01 && abs(last_sell_ratio - ratio) >= delta             last_buy_ratio = ratio;             btce_usd_amount = btce_usd_amount - btce_buy(i) * abs(btce_diff) * 1.002;             btce_btc_amount = expected_btce;                          count = count + 1;             trading_times(count) = time(i);             trading_states(count) = total_in_usd(i);         else             ratio = log(mtgox_buy(i)) - log(btce_sell(i));             expected_btce = btc_for_ratio(ratio);             expected_btce = expected_btce * total_in_btc(i);             btce_diff = expected_btce - btce_btc_amount;                          if btce_diff < -0.01 && abs(last_buy_ratio - ratio) >= delta                 last_sell_ratio = ratio;                 btce_usd_amount = btce_usd_amount + btce_sell(i) * abs(btce_diff) / 1.002;                 btce_btc_amount = expected_btce;                  count = count + 1;                 trading_times(count) = time(i);                 trading_states(count) = total_in_usd(i);             end         end     end          count = count + 1;     trading_times(count) = time(end);     trading_states(count) = total_in_usd(length(time));          trading_times = trading_times(1:count);     trading_states = trading_states(1:count);          subplot(2, 1, 1);     plot(trading_times, 100*(trading_states / trading_states(1) - 1), '.-');     grid on;     title 'Total profit, %';     datetick('x', 'dd.mm');     set(gca, 'XTick', trading_times(1):((trading_times(end) - trading_times(1)) / 10):trading_times(end));          subplot(2, 1, 2);      plot(time, (btce_buy + btce_sell) / 2);     grid on;     datetick('x', 'dd.mm');     title 'BTC-e price, USD';     set(gca, 'XTick', time(1):((time(end) - time(1)) / 10):time(end));          result_profit = trading_states(end) / trading_states(1) - 1;     fprintf('profit: %f, profit per day: %f\n', result_profit, result_profit / (max(time) - min(time)));     end 

В результате его исполнения для данных за последние три недели получается следующий график:

и среднее значение ежедневной прибыли 3.5%. Тестовые данные и скрипт можно посмотреть на github.

Написание программы

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

Программа подключается через websocket к обновлениям цены на MtGox иполучает цену через HTTP-запросы с BTC-e. Запросы на BTC-e отправляются каждые две секунды, т.к. на сервере такая квота; данные с MtGox приходят примерно с такой же частотой. И то и другое просходит в отдельных потоках, информация о цене отправляется в главный поток через Queue. В главном потоке просходит обработка данных по описанному выше алгоритму, и если нужно совершить покупку или продажу, создается ордер по последней лучшей цене. Если ордер не исполнился полностью, то на следующей итерации он отменяется и создается новый, до тех пор, пока существует необходимость в покупке или продаже (пока разница между и текущим состоянием счета составляет более 0.01 BTC, меньшими объемами торговать не позволяет биржа).

Всего получилось около 500 строк кода, все исходники доступны на github. Поскольку скрипт должен работать круглосуточно и с хорошим каналом связи, лучше арендовать для него VPS, что я и сделал.

Выводы

Описанная стратегия позволяет получать заметную прибыль при больших колебаниях цены (15-20%), при малой волатильности она почти не приносит дохода. К сожалению, когда я полностью закончил разработку модели и написание программы, уже шел май, и как я думаю, период апрельских колебаний курса закончился, поскольку ажиотаж, связанный с биткоинами, везди притих. Программа проработала около недели, но из-за спокойствия рынка заработк почти не было. Поэтому интерес к торговле начал пропадать, а поскольку внесенные на биржу деньги неожиданно понадобились, сегодня я вывел их и написал этот пост.

ссылка на оригинал статьи http://habrahabr.ru/post/179343/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *