Автоматическая торговля с Deephaven

Часть 1. Как генерировать ордера и регистрировать сделки

Eephaven — это мощная платформа для анализа данных, позволяющая легко анализировать данные в режиме реального времени, такие как цены на фондовом рынке. Одним из потенциальных применений Deephaven является определение акций для покупки и продажи в режиме реального времени. Затем системы управления ордерами (OMS) могут использоваться для автоматического размещения этих торговых ордеров.

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

Есть несколько событий и частей, которые должны работать вместе для достижения базовой автоматической торговли. Во-первых, нам нужно будет сгенерировать сделки, которые мы хотим выполнить. Затем мы будем прослушивать новые торговые запросы и размещать ордер с помощью API управления заказами. Как только ордер будет выполнен, нам нужно будет записать результат обратно в Deephaven для отслеживания, что, возможно, повлияет на нашу торговую модель. Описание этой последовательности событий показано ниже.

Таблица торговых ордеров

Первое, что мы создадим — это таблица, которая отслеживает сделки, которые мы хотим совершить. Эта таблица должна содержать информацию, необходимую для подачи торгового запроса в OMS. В нашем случае нам понадобится биржевой символ, цена предложения, цена предложения и количество акций, которые мы хотим в торговле (отрицательные акции будут указывать на продажу). Мы также получим текущую дату и метку времени, чтобы помочь отслеживать наши запросы. Этой информации должно быть достаточно для размещения базовых заказов, поэтому теперь нам нужен источник данных.

Мы будем использовать котировки акций в режиме реального времени из одного из наших демонстрационных источников данных под названием FeedOS. Чтобы определить, какие акции покупать, мы просто случайным образом выберем некоторые котировки по мере их поступления. Обычно каждый день в эту таблицу добавляется от 10 до 20 миллионов котировок, поэтому мы будем случайным образом покупать и продавать 0,01% котировок по их цене покупки или продажи. Мы также будем торговать случайным количеством акций от 0 до 100, чтобы добавить разнообразия.

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

orders = (
   db.i('FeedOS', 'EquityQuoteL1').where("Date = currentDateNy()")
   .update("rand = Math.random()", "Buy = rand > 0.9999", "Sell = rand < 0.0001")
   .where("Buy = true || Sell = true")
   .update("Shares = Buy ? (int) Math.round(Math.random() * 100) : (int) Math.round(Math.random() * -100)")
   .view("Date", "Timestamp", "Symbol = LocalCodeStr", "Bid", "Ask", "Shares")
)

Таблица результатов сделок

Теперь нам нужен способ фиксации результатов наших сделок в постоянной таблице. Это даст возможность отслеживать наш портфель в Deephaven и при желании программно корректировать нашу торговую стратегию. В таблице нужно будет как минимум отслеживать биржевой символ, количество акций, доход от сделки, идентификатор сделки (мы просто будем использовать временную метку), а также то, была ли сделка успешной или нет. Один из способов сделать это в Deephaven — использовать таблицы ввода.

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

Пример кода создания входной таблицы показан ниже вместе с изображением, показывающим таблицы.

from deephaven import InputTable, TableInputHandler
 
namespace = 'OMSExample'
tradeTableName = 'Trades'
 
# Create the input table if it doesn’t exist
if not db.hasTable(namespace, tradeTableName):
   InputTable.newInputTable(db, namespace, tradeTableName,
       TableInputHandler.c("Symbol", "java.lang.String"),
       TableInputHandler.cKey("Timestamp", "com.illumon.iris.db.tables.utils.DBDateTime"),
       TableInputHandler.c("Exchange", "java.lang.String"),
       TableInputHandler.c("Shares", "double"),
       TableInputHandler.c("Proceeds", "double"),
       TableInputHandler.c("Accepted", "java.lang.Boolean")
   )
 
trades = InputTable.inputTable(db, namespace, tradeTableName)

Подводя итог

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

Часть 2. Как выставлять ордера с помощью внешней OMS и регистрировать сделки

Эта статья является второй частью серии из двух частей о том, как интегрировать Deephaven с внешней системой управления заказами (OMS) для автоматизации торговли. В части 1 мы рассмотрели, как настроить постоянный запрос в Deephaven, который генерирует ордера в одной таблице и записывает сделки в другую.

В этой части мы рассмотрим, как использовать OpenAPI Deephaven с постоянным запросом из части 1 для выполнения наших ордеров с помощью внешнего OMS и записи окончательного результата сделки. Диаграмма, показывающая поток событий в системе, показана ниже.

Прислушиваемся к новым заказам

Часть системы OpenAPI должна прослушивать новые заказы из таблицы заказов. Для этого мы будем использовать события на столах. События отправляются сервером один раз в секунду. Два события, которые нас интересуют, — это «sizechanged» и «rowadded». Событие «sizechanged» срабатывает при изменении размера таблицы. Событие «rowadded» инициируется, когда строка добавляется в окно просмотра. Окно просмотра используется для извлечения части таблицы. Объединив эти два события, мы сможем устанавливать новое окно просмотра каждый раз, когда новые строки изменяют размер нашей таблицы. Тогда мы получим событие «rowadded» для каждой новой строки.

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

class OMS {
 constructor(client) {
   const pqName = 'OMS Example';
   const orderTableName = 'orders';
   const tradeTableName = 'trades';
   this.client = client;
   this.query = null;
   this.orderTable = null;
   this.tradeTable = null;
   this.lastKnownSize = 0;
 
   this.client.addEventListener(iris.Client.EVENT_CONFIG_ADDED, async e => {
     let query = e.detail;
     if (query.name === pqName) {
       this.query = query;
       this.orderTable = await query.getTable(orderTableName);
       const tradeTable = await query.getTable(tradeTableName);
       this.tradeTable = await tradeTable.inputTable();
       this.setupTradeListener();
       console.log('Listening for trades...');
     }
   });
 }
...

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

class OMS {
...
 setupTradeListener() {
   let table = this.orderTable;
   this.lastKnownSize = table.size;
  
   // Use sizechanged to see if there was more than 1 row added in the last second
   table.addEventListener('sizechanged', e => {
     table.setViewport(this.lastKnownSize, e.detail);
     this.lastKnownSize = e.detail;
   });
 
   table.addEventListener('rowadded', e => this.placeOrder(e) );
 }
...

Фальшивая OMS

Чтобы упростить тестирование и не тратить деньги на покупку произвольных акций, мы создадим поддельную систему управления заказами для размещения наших заказов. Наша OMS должна имитировать реальный асинхронный API OMS, поэтому мы будем использовать обещание, которое разрешается через некоторый случайный промежуток времени менее 10 секунд и случайным образом отклоняет 10% заказов. Код этой фальшивой OMS показан ниже.

class OMS {
...
// Returns Promise<boolean> if trade is accepted
 fakeOMS(order) {
   console.log(`Placing order for: ${order.Symbol}`);
   return new Promise((resolve, reject) => {
     setTimeout(
       resolve,
       Math.floor(Math.random() * 10000), // Wait between 0 and 10 seconds
       Math.random() > 0.1
     );
   });
 }
...

Размещение ордеров и учет сделок

Теперь мы готовы размещать наши фальшивые заказы. Мы будем анализировать данные из каждой строки по мере ее добавления для создания объекта заказа. Затем мы отправим заказ и будем ждать ответа от нашей OMS. Как только ответ будет получен, мы сможем определить любые поступления и записать наши данные обратно в наш постоянный запрос в Deephaven. Код для оформления заказа приведен ниже.

class OMS {
...
async placeOrder(e) {
   const tradeColNames = this.orderTable.columns.map(c => c.name);
   const rowData = e.detail.row.dataColumns;
   let orderObj = {Exchange: 'NASDAQ'};
   for (let i = 0; i < tradeColNames.length; i++) {
     orderObj[tradeColNames[i]] = rowData[i][e.detail.index];
   }
 
   orderObj.Accepted = await this.fakeOMS(orderObj);
   console.log('Trade finished: ' + orderObj.Symbol);
   if (orderObj.Accepted) {
     if (orderObj.Shares > 0) { // Buying shares at ask price
       orderObj.Proceeds = -1 * orderObj.Shares * orderObj.Ask;
     } else { // Selling at bid price
       orderObj.Proceeds = -1 * orderObj.Shares * orderObj.Bid;
     }
   } else {
     orderObj.Proceeds = 0;
   }
 
   await this.recordTrade(orderObj);
 }
...

Как только фальшивый OMS ответит, мы отправим данные обратно в нашу таблицу результатов сделки, которая представляет собой входную таблицу Deephaven, которая отслеживает символ акции, количество акций, доход от сделки, была ли сделка успешной или нет и т. д. Нам нужно получить входной объект таблицы именно из объекта таблицы в OpenAPI, который мы затем можем использовать для записи данных в наш постоянный запрос. Если имеется большой объем сделок (более 1 раз в секунду или две), то очередь входной таблицы может начать резервное копирование или события ордеров могут быть пропущены или отправлены слишком поздно, так как входная таблица может блокировать некоторые события во время записи. Для производственной OMS вместо этого вы должны настроить импорт потока данных Deephaven для записи сделок. Код для записи в таблицу ввода показан ниже.

class OMS {
...
// Record the trade to our input table
 async recordTrade(trade) {
   console.log(`Recording trade for: ${trade.Symbol}`);
   let inputTable = this.tradeTable;
   try {
     await inputTable.addRow({
       Symbol: trade.Symbol,
       Timestamp: iris.DateWrapper.ofJsDate(new Date()).asNumber(), // Timestamp is used to uniquely identify. Should really use a tradeID
       Exchange: trade.Exchange,
       Shares: trade.Shares,
       Proceeds: trade.Proceeds,
       Accepted: trade.Accepted
     });
   } catch(err) {
     console.log('recordTrade exception:', err);
   }
 }
}
}
Мои тикающие таблицы

Подводя итог

В этой серии мы рассмотрели один из способов автоматической торговли с использованием Deephaven и Node.js. Мы использовали постоянный запрос Deephaven для генерации наших заказов и записи наших сделок. Deephaven OpenAPI предоставил приятный интерфейс для доступа к нашим данным, чтобы мы могли отправлять заказы в OMS с помощью Node.js который легко обрабатывает асинхронный код и прост в использовании для HTTP-запросов к реальному API OMS. Способность Deephaven к анализу в реальном времени делает его отличным выбором для создания автоматической торговой системы.

Источник