Всем привет! Сегодня мы напишем своего первого боевого торгового робота для игры на бирже. Криптобирже. Почему криптобирже?


а) хайповая тема;
б) у них как-то все попроще.


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


Во-вторых, Вы заработаете. Просто немного.


К/ф «Игра на понижение» (2015 г.)
— Этот запах, что это? Чем пахнет?
— Одеколоном?
— Нет…
— Возможностями?
— Нет. Деньгами.
— Оу….всё ясно
— Запах денег!
— Понятно.

Для самых нетерпеливых, весь код на гитхабе exmo-exchange-trade-bot.


Этап №1: Биржа.


Торговать мы будем на бирже EXMO. Причин несколько. Биржа популярна на просторах СНГ, она русскоязычная и поддерживает ввод рублей, имеет возможность создания пар к рублю.


Среди минусов — неудобный процесс ввода рублей, приличная комиссия, мало торгующихся пар.


Для нас главное, что она имеет готовые решения для работы с их API. Это безусловно облегчит нашу работу.


Итак, приступим.


Разумеется, на бирже нужно зарегистрироваться и внести немного денег. Я для примера внес 5$.


Далее в личном кабинете Вы получите ключи для доступа к API.


Я буду использовать клиент для NodeJS (поэтому потребуется установить nodejs и npm).


На своем компьютере создаем новую папку и файлом, в котором будет наш торговый робот (напр. exmo/index.js), открываем консоль и делаем последние стандартные приготовления.


Переходим в папку с нашим проектом и пишем — npm init, далее на все вопросы нажимаем клавишу enter.


Далее пишем


npm install exmo-api

Пока пакеты устанавливаются, создадим еще один файлик, назовем его exmo.js и наполним вот этим содержимым.


вот этим содержимым
var CryptoJS = require("crypto-js")
    http = require('http'),
    querystring = require('querystring'),
    request = require('request'),
    config = {
        url: 'https://api.exmo.me/v1/'
    };

function sign(message){
    return CryptoJS.HmacSHA512(message, config.secret).toString(CryptoJS.enc.hex);
}

exports.init_exmo = function (cfg) {
    config.key = cfg.key;
    config.secret = cfg.secret;
    config.nonce = Math.floor(new Date().getTime()*1000);
};

exports.api_query = function(method_name, data, callback){
    data.nonce = config.nonce++;
    var post_data = querystring.stringify(data);

    var options = {
      url: config.url + method_name,
      method: 'POST',
      headers: {
        'Key': config.key,
        'Sign': sign(post_data)
      },
      form:data
    };

    request(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            callback(body);
        }else{
            callback(error);
        }
    });
};

exports.api_query2 = function(method_name, data, callback){
    data.nonce = config.nonce++;
    var post_data = querystring.stringify(data);

    var post_options = {
        host: 'api.exmo.me',
        port: '80',
        path: '/v1/' + method_name,
        method: 'POST',
        headers: {
            'Key': config.key,
            'Sign': sign(post_data),
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': Buffer.byteLength(post_data)
        }
    };
    var post_req = http.request(post_options, function(res) {
      res.setEncoding('utf8');
      res.on('data', function (chunk) {
          callback(chunk);
      });
    });

    post_req.write(post_data);
    post_req.end();
};

exports.test = function(){
    return config.key;
};

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


Всё, мы все подготовили и теперь можем непосредственно приступить к созданию персональной «машины по зарабатыванию денег» ;)


Этап №2: Код


Открываем наш index.js и подключим фаил exmo.js:


const exmo = require("./exmo");

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


const apiKey = ‘ВАШ ключ’;
const apiSecret = ‘Ваш секрет’;

Теперь создадим две переменные:
currency1 — это ЧТО покупаем;
currency2 — валюта, за которую покупаем.


Я хочу купить биткоины за доллары:


const currency1 = 'BTC';
const currency2 = 'USD';

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


Идем по адресу https://api.exmo.com/v1/pair_settings/ ищем свою пару (для меня это BTC_USD) и смотрим первый параметр — min_quantity — 0.001


const currency1MinQuantity = 0.001; 

И ещё немного переменных:


количество минут, через которое неисполненный ордер на покупку будет отменен currency1


const orderLifeTime = 3;

биржевая комиссия (0.002 = 0.2%)


const stockFee = 0.002;

период времени (в минутах) для вычисления средней цены (это понадобится для нашего алгоритма)


const avgPricePeriod = 1;

количество currency2 для покупки currency1 при единоразовой сделки (я закинул 5$ — ими и буду оперировать)


const canSpend = 5;

желаемое количество прибыли с каждой сделки (0.001 = 0.1%)


const profit = 0.001;

Если расходится время биржи с текущим


const stockTimeOffset = 0;

Для удобства объединяем нашу пару через _


let currentPair = currency1+'_'+currency2;

Инициализируем подключение.


exmo.init_exmo({key:apiKey, secret:apiSecret});

Для теста можете запросить информацию о себе:


exmo.api_query("user_info", { }, result => console.log(result););

Переходим в консоль и запускаем


node index.js

Если все сделано верно, то Вы увидите информацию по Вам!


Все работает и можно переходит к самому интересному — функции, которая будет генерировать нам бабки.


Итак, я выше уже говорил что наш алгоритм будет тупым, сейчас Вы поймете на сколько)


Весь фокус состоит в том, чтобы взять историю завершенных сделок за какой-либо период — у нас за это отвечает переменная avgPricePeriod — и посчитать среднюю цену, за которую была продана currency1. За эту среднюю цену мы и выставим свой ордер.


Итак, приступим. Пишем нашу функцию trade()


function trade(){}

Сначала получим список наших открытых ордеров:


1) проверяем есть ли у нас открытые ордера по нашей паре с помощью api-метода user_open_orders. Если есть и они на продажу,


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


exmo.api_query("user_open_orders", { }, result => {

    let res = JSON.parse(result);

    if(res[currentPair] == undefined) console.log('Открытых оредеров нет');

    let buyOrders = [];

    for(let i in res[currentPair]){
        console.log(res[currentPair][i]);
        if(res[currentPair][i].type == 'sell'){
           console.log('Выход, ждем пока не исполнятся/закроются все ордера на продажу');
        }else{
          buyOrders.push(res[currentPair][i]);
        }
    }

2) Проверяем, если у нас есть открытые ордера на покупку.


Перебираем все ордера и получаем историю по ним с помощью метода order_trades, передав туда id ордера.


Здесь может быть 3 варианта:


if(buyOrders.length > 0){ 
    for(let key in buyOrders){
        console.log('Проверяем, что происходит с отложенным ордером', buyOrders[key]['order_id']);

        exmo.api_query('order_trades', {"order_id": buyOrders[key]['order_id']}, result => {
            let res = JSON.parse(result);

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


if(res.result !== false){
     console.log('Выход, продолжаем надеяться докупить валюту по тому курсу, по которому уже купили часть');
}

2) при втором варианте нам нужно проверить не слишком ли долго висит наш ордер. Цены меняются быстро и, возможно, средняя цена уже не актуальна. Для этого мы завели переменную orderLifeTime, где указываем, сколько наш ордер должен висеть в минутах.


Если время вышло, то отменяем ордер с помощью метода order_cancel, передав ему id ордера.


let timePassed = (new Date().getTime() / 1000) + stockTimeOffset * 60 * 60 - (buyOrders[key]['created']);
if(timePassed > orderLifeTime * 60){
    exmo.api_query('order_cancel',{"order_id":buyOrders[key]['order_id']}, res => {
        let result = JSON.parse(res);
        if(result.error) console.log(result.error);

    console.log(`Отменяем ордер за ${orderLifeTime} минут не удалось купить ${currency1}`);
                    });
        }else{

3) если время ещё не вышло, то мы просто надеемся, что сможем купить по нашей цене.


console.log(`Выход, продолжаем надеяться купить валюту по указанному ранее курсу, со времени создания ордера прошло ${timePassed} секунд`);
                }
            }
          });
        }
    }else{

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


Итак, блок если у нас нет ордеров.


Получаем информацию по нашему аккаунту с помощью метода user_info:


exmo.api_query('user_info',{},(result)=>{
     let res = JSON.parse(result);

Для удобства запишем балансы по нашим парам:


let balance = res.balances[currency1];
let balance2 = res.balances[currency2];

Проверим, есть ли currency1, которую можно продать?


if(balance >= currency1MinQuantity){}

Если да, нам нужно высчитать курс продажи.


Нужно продать всю валюту, которую купили, на сумму которую купили плюс прибыль за вычетом комиссии биржи.


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


let wannaGet = canSpend + canSpend * (stockFee+profit);
console.log('sell', balance, wannaGet, (wannaGet/balance));

При создании ордеров, методу order_create нужно передать параметры:


  • pair — это наша актуальная пара для торговли;
  • quantity — количество;
  • price — цена;
  • type — типа создаваемого ордера (buy/sell);

Мы хотим продать — в типе указываем sell.


let options = {
    "pair": currentPair,
    "quantity": balance,
    "price": wannaGet / balance,
    "type": 'sell'
};

и отправляем запрос, если все верно, то Вы увидите запись "Создан ордер на продажу"


exmo.api_query("order_create", options,(result)=>{
     if(result.error) console.log(result.error);

     console.log("Создан ордер на продажу", currency1, result.order_id);
});

Здесь все. Если у нас была валюта, мы просто создали ордер на его продажу.


Теперь переходим к самому интересному блоку: случай, если у нас нет currency1(btc) и мы хотим ее купить за нашу currency2 (usd).


Для начала проверим, достаточно ли денег на балансе в валюте currency2.


if(balance2 >= canSpend){}

Если есть, то нам нужно получить среднюю цену, за которую продают currency1(btc) за промежуток времени, который мы указали в avgPricePeriod.


Немного лирики:
У Exmo есть метод ticker со статистикой и объемами торгов по валютным парам. В статистике указана средняя цена за последние 24 часа. Однако разница средней цены и той, по которой идут торги сейчас может очень отличатся.


Из за этого мы можем долго ждать, когда исполниться ордер на продажу.


Мы сделаем свой велосипед.


У Exmo есть метод trades, он возвращает список сделок по валютной паре.


Мы возьмем совершенные сделки за интересующий нас avgPricePeriod и из них посчитаем среднюю цену.


Это не идеальный вариант, но он покажет реальные цены, по которым продают и покупают.


Например, на момент написания статьи средняя цена BTC_USD — 8314, при этом покупка на бирже осуществляется по цене 7970.


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


Но добавив прибыль и биржевую комиссию мы, вероятнее всего, будем очень долго ждать продажи.


Итак, обратимся к методу trades и запросим у него статистику по нашей паре currentPair:


exmo.api_query("trades",{"pair":currentPair}, result => {

    let res = JSON.parse(result);
    let prices = [];
    let summ = 0;

Переберем все результаты и оставим только те, которые подходят к нашему временному периоду.


for(deal in res[currentPair]){
    let timePassed = (new Date().getTime() / 1000) + stockTimeOffset * 60 * 60 - res[currentPair][deal].date;

     if(timePassed < avgPricePeriod * 60){
         summ += parseInt(res[currentPair][deal].price);
         prices.push(parseInt(res[currentPair][deal].price));
     }
}

И посчитаем среднюю цену.


let avgPrice = summ2 / prices.length;

Средняя цена у нас есть, но нам нужно ее немного подправить — вычесть комиссию биржи stockFee и добавить желаемый профит. Таким образом, получив цену ниже средней цены рынка, мы купим чуть больше валюты, так как биржа впоследствии забирает ее часть;


let needPrice = avgPrice - avgPrice * (stockFee + profit);

Получаем конечное количество, которое нам нужно купить.


let ammount = canSpend / needPrice;
console.log('Buy', ammount, needPrice);

Проверяем можно ли купить такое количество валюты (не нарушается ли минимальная сумма покупки).


if(ammount >= currency1MinQuantity){}

Если наше количество больше, то формируем параметры для метода order_create, только на этот раз уже с типом buy.


 let options = {
    "pair": currentPair,
    "quantity": ammount,
    "price": needPrice,
    **"type": 'buy'**
};

exmo.api_query('order_create', options, res => {
    let result = JSON.parse(res);
    if(result.error) console.log(result.error);
        console.log('Создан ордер на покупку', result.order_id);
    });

}else{
    console.log('Выход, не хватает денег на создание ордера');
}

});
}else{
    console.log('Выход, не хватает денег');
}

Теперь нужно поставить нашу функцию на таймер (диапазон — раз в 5 секунду, например) и можем запускать.


var timerId = setTimeout(function tick() {
  trade();
  timerId = setTimeout(tick, 5000);
}, 5000);

node index.js

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


Думаю у вас возник резонный вопрос «Сколько же денег так можно заработать?»


За одну операцию с 5$ я зарабатываю примерно 2-3 цента. Это связано с примитивностью алгоритма, который работает в случае, если цена колеблется в определенном диапазоне (а это почти всегда не так на криптобиржах). За сутки происходит порядка 10-20 операций (при хороших раскладах). Посчитать можете сами;)


Но мы же не ради денег код мастерим)


Ещё раз ссылка на гитхаб с полной версией бота и комментариями.
https://github.com/v-florinskiy/exmo-exchange-trade-bot


Эта моя первая статья — не судите строго)


Всем профита.


P.S.: Для тех, кто не работал с node, ваш скрипт естественно будет работать, пока отрыта консоль.


Чтобы ваш робот работал 24/7, вам нужен какой-нибудь vps: там вы ставите nodejs, npm и, например, pm2. С помощью этой утилиты скрипт продолжит работу, даже если консоль закрыта.

Комментарии (9)


  1. Aspos
    03.07.2018 17:53

    C $5 зарабатываете 3 цента и делаете 20-30 транзакций в день?
    Т.е. с $5000 будете делать $30? Это ж $450 в день, нет?


    1. Epoiiika
      03.07.2018 18:20

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


      1. SADKO
        03.07.2018 20:21

        Вооот, очень правильное замечание, эта фигня называется ликвидностью, и мала она не только на крипте, но и в московском болоте, на «честных» CFD и «стаканах» форексников…
        … ликвидность не постоянна, и на крипте бывает движуха, вопрос лишь в том, сколько это в деньгах


  1. RISENT
    03.07.2018 23:59
    +1

    Кто-то спер код отсюда, параллельно транслировав его с python на js.


  1. Slav2
    04.07.2018 12:52

    Историю торгов как индикатор лучше не брать, т.к. некоторые сделки могут принадлежать самой бирже, а она торгует без комиссии. Биржи часто занимаются арбитражем, т.е. выполняют сделки типа A->B->C->A повторить которые невозможно, т.к. весь профит съедает комиссия.

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

    Торгового бота надо начинать писать с тестировщика торговых стратегий. По быстрому срубить бабла не получится.


    1. akafloa Автор
      04.07.2018 12:57

      > «Ваша торговая стратегия даст одни убытки если цена на крипту начнет падать.»
      Нет, если рынок будет падать, то ордер просто не исполнится — в убыток не будете торговать.

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


      1. Slav2
        04.07.2018 18:21

        При формировании цены на покупку по формуле

        let needPrice = avgPrice — avgPrice * (stockFee + profit);

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

        На bitmex и hitbtc создатель заказа не платит комиссию, наоборот биржи возмещают процент от сделки, поэтому можно сказать что комиссия отрицательная. Вот там стратегии типа «давай быстрей» с депозитом до $100 могут и вправду что то и заработать


        1. akafloa Автор
          05.07.2018 00:32

          Мне кажется, Вы немного не туда смотрите.
          Цену на продажу мы формируем немного выше — вот тут:
          let wannaGet = canSpend + canSpend * (stockFee+profit);
          И потом передаем в ордер: «price»: wannaGet / balance
          Если рынок будет падать, то мы просто будем долго ждать, когда исполнится наш ордер на продажу и все.

          Про bitmex и hitbtc спасибо за инфу) посмотрю


  1. Flexey
    04.07.2018 13:16

    Может в недалёком будущем всё задвигается. А как платить налоги с прибыли?