Месяц назад, когда цена биткоина достигла 250 долларов, а затем упала до 50, у меня появилось желание поучаствовать в этом веселье, написав торгового бота, который бы зарабатывал на подобных изменениях.
Выяснилось, что две наиболее популярные биржи, на которых торгуют биткоинами — это MtGox и BTC-e. Я положил деньги на одну из них и принялся думать, как предсказать изменение цены, а также, как это автоматизировать. Дело осложнялось тем, что на этих биржах можно покупать и продавать только на свои средства, поэтому нельзя играть на понижение, занимая короткую позицию, потому что, как говорил Матроскин: «Чтобы продать что-нибудь ненужное, нужно сначала купить что-нибудь ненужное».
Обзор
Почитав материалы по теме, я пришел к выводу, что существует две наиболее распространенные стратегии для алгоритмической торговли:
- маркетмейкинг — держим постоянно выставленнными ордера на покупку и/или продажу по текущей цене продажи и покупки соответственно, так что если кто-то хочет купить или продать биткоины, то он торгует именно с нами, в результате мы в среднем покупаем и продаем одинаковое количество, дешевле и дороже соответственно, за счет чего имеем прибыль.
- арбитраж — если есть две биржи, на которых торгуют одним и тем же, и в некоторый момент времени цена покупки на одной из них меньше, чем цена продажи на другой, то покупаем на первой и продаем на второй.
При рассмотрении применения этих стратегий к торговле биткоинами оказалось, что первая убыточна из-за комиссий бирж — 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.
Проверка стратегии
Для проверки получившейся модели на реальных данных о ценах, напишем
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: %fn', 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. Поскольку скрипт должен работать круглосуточно и с хорошим каналом связи, лучше арендовать для него
Выводы
Описанная стратегия позволяет получать заметную прибыль при больших колебаниях цены (15-20%), при малой волатильности она почти не приносит дохода. К сожалению, когда я полностью закончил разработку модели и написание программы, уже шел май, и как я думаю, период апрельских колебаний курса закончился, поскольку ажиотаж, связанный с биткоинами, везде притих. Программа проработала около недели, но из-за спокойствия рынка заработка практически не было. Поэтому интерес к торговле начал пропадать, а поскольку внесенные на биржу деньги неожиданно понадобились, сегодня я вывел их и написал этот пост.
Автор: therussianphysicist