- PVSM.RU - https://www.pvsm.ru -

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

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

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

Обзор

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

  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: %fn', result_profit, result_profit / (max(time) - min(time)));    
end

В результате его исполнения для данных за последние три недели получается следующий график:
Как я писал робота для квазиарбитражной торговли биткоинами
и среднее значение ежедневной прибыли 3.5%. Тестовые данные и скрипт можно посмотреть на github [3].

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

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

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

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

Выводы

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

Автор: therussianphysicist

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ruby/34046

Ссылки в тексте:

[1] MtGox: http://mtgox.com

[2] BTC-e: http://btc-e.com

[3] посмотреть на github: https://github.com/therussianphysicist/btc-trader/tree/master/model_sample

[4] Queue: http://www.ruby-doc.org/stdlib-2.0/libdoc/thread/rdoc/Queue.html

[5] доступны на github: https://github.com/therussianphysicist/btc-trader

[6] VPS: https://www.reg.ru/?rlink=reflink-717

[7] Источник: http://habrahabr.ru/post/179343/