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

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

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

Навскидку план работы должен быть такой:

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

Исходники доступны по ссылке arb_analysis.

Создание базы данных


Для того чтобы хранить данные, понадобится 3 таблицы.

В этой таблице будет храниться список бирж.

CREATE TABLE `exchange` (
      `id`   int(11)     NOT NULL AUTO_INCREMENT,
      `name` varchar(50) NOT NULL,
	  PRIMARY KEY (id)
    ) ENGINE=MyISAM;

Список криптовалютных пар.

CREATE TABLE `market` (
  `id`          int(11)  NOT NULL AUTO_INCREMENT,
  `name`        char(50) NOT NULL,
  `id_exchange` int(11)  NOT NULL,
  PRIMARY KEY (id)
) ENGINE=MyISAM;
CREATE INDEX id_exchange ON market (id_exchange);

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

CREATE TABLE `ticker` (
  `id`         int(11) NOT NULL AUTO_INCREMENT,
  `id_market`  int(5) NOT NULL,
  `local_time`  int(9)    NOT NULL,
  `timestamp`  int(9)    NOT NULL,
  `last`       DECIMAL (11,11) NOT NULL,
  `low`        DECIMAL (11,11),
  `high`       DECIMAL (11,11),
  `bid`        DECIMAL (11,11),
  `ask`        DECIMAL (11,11),
  PRIMARY KEY (id)
) ENGINE=MyISAM;
CREATE INDEX id_market ON ticker (id_market);

Получение данных с бирж


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

В файле create_markets.py происходит инициализация, получаем список криптовалютных пар, по биржам. При этом используется метод load_markets(), который возвращает список пар для биржи.

name_exchange = ["acx", "binance", "bitfinex", "bitfinex2", "wex"]
def create_exchange_and_market_in_db():

    exchanges = {}

    for id in name_exchange:

        exchange = getattr(ccxt, id)
        exchanges[id] = exchange()

        id_exchage = db_helper.insert_exchage_to_db(exchanges[id],cnx,cursor)

        markets = exchanges[id].load_markets()

        for mark in markets:
            id_market = db_helper.insert_market_to_db( id_exchage, mark, cnx,cursor)

Далее в файле ex_data_saver.py, запускаем сохранение изменение цен для пар:

def save():

    markets = db_helper.get_data_exch()

    exchanges = {}

    for id in name_exchange:
        exchange = getattr(ccxt, id)
        #isHas = exchange.hasFetchTickers
        #if isHas:
        exchanges[id] = exchange({
            'enableRateLimit': True,  # or .enableRateLimit = True later
        })

    cnx = db_helper.CreateConnection()
    cursor = cnx.cursor()
	
	loop = asyncio.get_event_loop()
    while True:
        start_time = time.time()
        input_coroutines = [fetch_ticker(exchanges, name) for name in exchanges]
        exch_tickers = loop.run_until_complete(asyncio.gather(*input_coroutines, return_exceptions=True))
        
        count_exchange = 0
        
        delta = time.time() - start_time
        
        for tickers in exch_tickers:
            if  tickers is not None:
                count_exchange+=1
        
        inserted_start = time.time()
        db_helper.insert_tick(markets,exch_tickers,cnx,cursor)
        inserted_time = time.time()
        print(count_exchange," ", delta, ' ', inserted_start - inserted_time)

Асинхронное получение тиков для конкретной пары, происходит с помощью метода ccxt fetchTickers().

async def fetch_ticker(exchanges, name):
    item = exchanges[name]

    try:
        ticker = await item.fetchTickers()
       
        return {name:ticker}

Предварительный анализ данных


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

SELECT ex.name as exchange_name, m.name as market_name, count(*) as count_deals 
FROM exchange ex
LEFT JOIN market m ON m.id_exchange = ex.id
LEFT JOIN ticker t ON t.id_market =m.id 
GROUP BY ex.id, t.id_market
ORDER BY m.name
HAVING count_deals > 10000;

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

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

  • Задержки.
  • Проскальзывание.
  • Комиссии.

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


  1. resetme
    14.01.2019 20:01
    +1

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

    Хотя бы PEP8 для начала прочитали перед тем как писать статьи на Хабре.


  1. rPman
    15.01.2019 01:02

    Совет, вместо бесполезных тиков и bid/ask (они не дают ответа на вопрос СКОЛЬКО можно обменять по этой цене), собирайте у бирж стаканы — depth, все они в криптоэкономике такой выдают, иногда лимитированный до некоторой глубины, но для нужд арбитража хватит.

    Еще больше пользы вы получите, если вместо запроса информации по http REST api, подключайтесь к серверу биржи по websocket (или fix если есть) и восстанавливайте стаканы самостоятельно в реальном времени, без задержек (rest api обычно лимитированы количеством запросов, в конечном счете секунды между ними в среднем), ведь за секунды содержимое стакана изменится, а вы об этом не узнаете.
    p.s. я сильно сомневаюсь, что вы сумеете используя sql, делать все это достаточно оперативно, слишком уж много оно накладных расходов дает.

    По стакану вы сможете оценить объем сделки и ее реальную стоимость, т.е. например если вы можете на одной бирже по цене 10 купить 100 монет, а на другой по цене 12 продать всего 5, то ваш бот должен на первой купить тоже только пять, иначе на второй бирже вы выставите продажу по 12, и этот ордер на 95 монет останется лимитным, сдвинув курс на ней вниз.