Можно ли с помощью ИИ автоматизировать набор правил, по которым действуют на бирже профессиональные трейдеры? Команда VK Cloud Solutions перевела статью о том, как это удалось реализовать и что вышло из такой затеи.
Как появилась идея автоматизации
Пару недель назад я болтал с приятелем. Он рассказывал мне, что пытался устроить детокс от брокерского приложения на смартфоне. Я забеспокоился, не разорился ли он, и уточнил значение слова «детокс» в этом контексте.
Он ответил, что занимается трейдингом. «Если какая-то ценная бумага растет на протяжении часа и я уже заработал более 1%, я ее продаю, — объяснил он. — Это одно из моих персональных правил». Не обращая внимания на псевдонаучный аспект этих правил, я понял, что он имел в виду под детоксом. Чтобы соблюдать такие правила, нужно постоянно заглядывать в смартфон.
И я задумался, можно ли автоматизировать набор правил, по которым действовал мой приятель? Чтобы система занималась трейдингом вместо меня? Вы наверняка уже догадались, что ответ на этот вопрос — «да». Что ж, давайте порассуждаем.
Но для начала: время — деньги, и я не хочу никого разводить на деньги. Вот что мы сделаем:
Возьмем детализированные данные о ценах на акции в реальном времени, в идеале с интервалом в одну минуту. Чем их больше, тем лучше. Используем для этого Yahoo! Finance, подробнее объясню ниже.
Вместо персонального набора правил добавим в систему ИИ. Раскрою все карты. Я, мягко говоря, не эксперт по анализу временных рядов. Сейчас есть немало руководств по обучению нейронных сетей трейдингу, и мне совершенно не хочется усложнять игрушечную систему вроде этой. Так что давайте стремиться к простоте: пока нам хватит самой базовой модели ARIMA.
У нас есть данные и прогноз, который мы получаем от алгоритма. С его помощью нужно решать, что делать с акциями: покупать, продавать или сохранять. А еще нужно подключиться к брокеру, чтобы выполнять нужные действия. Мы будем использовать RobinHood и Alpaca.
Вот, собственно, и все — система готова. Осталось только где-то деплоить и отслеживать ее работу. Я решил, что система будет отправлять сообщение в телеграм-чат при выполнении какого-либо действия.
Что нам понадобится?
Python 3.6 с несколькими библиотеками;
учетная запись в облаке с правами администратора для хранения и деплоймента;
Node.js, чтобы установить Serverless Framework для деплоймента;
аккаунт в телеграме для мониторинга.
Весь написанный код — здесь. Без лишних церемоний перейдем к первой части — сбору данных.
Сбор данных
Собрать данные — дело непростое. Еще несколько лет назад были доступны официальный Yahoo! Finance API и его альтернатива Google Finance. К сожалению, оба сервиса закрылись несколько лет назад. Но остались альтернативы. Я сформулировал следующие требования:
Бесплатно. Для рабочей системы я бы, конечно, поменял это требование на «недорого», но для игрушечной системы данные мне нужны бесплатно.
Высокая скорость. Желательно вообще без ограничений, но производительности свыше 500 запросов в минуту более чем достаточно.
Данные в реальном времени. Некоторые API выдают данные с небольшой задержкой, скажем, в 15 минут. Мне нужны цены на акции, максимально приближенные к текущим.
Простота использования. Это проверка идеи, так что мне нужно самое простое решение.
С учетом своих требований я решил присмотреться к yfinance — неофициальной альтернативе старого доброго Yahoo Finance API. Для рабочей системы я бы выбрал Alpha Vantage API, опираясь на замечательный список Патрика Коллинза. Но пока давайте не усложнять.
Ран Арусси разработал библиотеку yfinance для доступа к данным Yahoo! Finance, когда официальный API перестал работать. Приведу цитату с GitHub:
«С тех пор как Yahoo! Finance закрыли свой API исторических данных, многие программы, которые использовали этот интерфейс, прекратили работу. yfinance призвана решить проблему — это надежное решение на Python для загрузки исторических данных по рынку из Yahoo! Finance».
Мило, мне подходит. Как это работает? Для начала библиотеку нужно установить:
$ pip install yfinance --user
А потом можно получить доступ к данным через объект Ticker
:
import yfinance as yf
google = yf.Ticker(“GOOG”)
Это достаточно быстрый метод (в среднем исполняется чуть дольше 0,005 секунды), который возвращает МАССУ информации об акциях. Например, google.info содержит 123 поля, в том числе:
52WeekChange: 0.3531152
SandP52WeekChange: 0.17859101
address1: 1600 Amphitheatre Parkway
algorithm: None
annualHoldingsTurnover: None
annualReportExpenseRatio: None
ask: 1815
askSize: 1100
…
twoHundredDayAverage: 1553.0764
volume: 1320946
volume24Hr: None
volumeAllCurrencies: None
website: http://www.abc.xyz
yield: None
ytdReturn: None
zip: 94043
Еще больше данных можно получить с помощью методов dividends
, splits
, balance_sheet
, earnings
и других. Большинство из них возвращают данные в виде объекта pandas DataFrame, так что придется немного повозиться, чтобы получить все, что нам нужно.
Пока мне требуется информация об изменении цен на акции с течением времени, лучше всего для этого подходит метод history
. Мы можем выбрать период или даты интервала, а также частоту получения данных до одной минуты.
Обратите внимание, что информация с разбивкой по времени внутри дня доступна, если вы указали период менее 60 дней, и что за один запрос можно получить данные с детализацией до минуты только за семь дней. Транспонированные данные последней записи с интервалом в одну минуту:
df = google.history(period='1d', interval="1m")print(df.head())
Видно, как они индексируются по дате и времени. При этом у каждой записи семь характеристик: четыре значения цены на акцию за эту минуту (открытие, максимальная, минимальная, закрытие) а также объем, дивиденды и сплит акций. Я буду использовать только характеристику «минимальная». Соберем необходимые данные:
df = google.history(period='1d', interval="1m")
df = df[['Low']]
df.head()
Поскольку мы используем только данные за последний день, давайте проиндексируем DataFrame, чтобы удалить дату и часовой пояс, оставив только время.
df['date'] = pd.to_datetime(df.index).time
df.set_index('date', inplace=True)
df.head()
Хорошо выглядит! Мы уже знаем, как найти последние данные в yfinance. Чуть позже передадим их в наш алгоритм. Но для начала нужно с ним определиться, так что переходим к следующему этапу.
Добавляем ИИ
Не пытайтесь повторять это в домашних условиях. Я подобрал ОЧЕНЬ простую модель ARIMA для прогнозирования следующей цены на акцию, поэтому относитесь к ней как к учебной. Чтобы использовать эти наработки для настоящего трейдинга, советую поискать модель получше и помощнее. Но не теряйте бдительности: если бы это было просто, такие модели были бы у всех.
Для начала давайте разделим DataFrame на данные для обучения и для тестирования, чтобы можно было использовать тестовый набор для проверки результатов учебной модели. В качестве тестового набора я собираюсь использовать последние 10% данных.
X = df.index.values
y = df['Low'].values
# The split point is the 10% of the dataframe length
offset = int(0.10*len(df))
X_train = X[:-offset]
y_train = y[:-offset]
X_test = X[-offset:]
y_test = y[-offset:]
Построим график:
plt.plot(range(0,len(y_train)),y_train, label='Train')
plt.plot(range(len(y_train),len(y)),y_test,label='Test')
plt.legend()
plt.show()
Теперь добавим в модель данные для обучения и получим прогноз. Обратите внимание, что здесь гиперпараметры модели фиксированы, а в реальной жизни для получения оптимальных параметров нужно использовать кросс-валидацию. К вашим услугам замечательное руководство о том, как подбирать гиперпараметры ARIMA по сетке на Python. Я использую конфигурацию 5, 0, 1 и получаю прогноз на момент, который наступает сразу после данных для обучения.
from statsmodels.tsa.arima.model import ARIMAmodel = ARIMA(y_train, order=(5,0,1)).fit()
forecast = model.forecast(steps=1)[0]
Давайте посмотрим, хорошо ли сработала наша учебная модель:
print(f'Real data for time 0: {y_train[len(y_train)-1]}')
print(f'Real data for time 1: {y_test[0]}')
print(f'Pred data for time 1: {forecast}')
—
Real data for time 0: 1776.3199462890625
Real data for time 1: 1776.4000244140625
Pred data for time 1: 1776,392609828666
Что ж, неплохо, работать можно. С этой информацией получится определить набор правил, основанных на любых наших предпочтениях: не продавать, если акции растут в цене, или продавать, если цена падает. Но не будем формулировать правила — вдруг те, кто потеряют на них все деньги, подадут на меня в суд. Вам придется разработать собственный набор правил без моих подсказок :) А пока я перехожу ко второму этапу — подключению к брокеру.
Подключение к брокеру
Как вы, наверное, догадались, многое зависит от выбранного брокера. Я расскажу о подключении к RobinHood и Alpaca. Почему я выбрал именно их?
Есть публичный API.
Они не берут комиссию за трейдинговые операции.
В зависимости от типа учетной записи действуют те или иные ограничения. Например, у RobinHood можно совершать всего три трейдинговые операции в пять дней, если остаток на счете менее 25 000 долларов. У Alpaca ограничения не такие жесткие, но все же установлен лимит в 200 запросов в минуту на один ключ API.
RobinHood
Есть несколько библиотек, поддерживающих RobinHood API, но ни одна из них не является официальной. Библиотека Sanko была самой большой, с 1,5 тысяч звезд на GitHub, но ее закрыли. Библиотека LichAmnesia приняла эстафету, но пока что набрала только 99 звезд. Я собираюсь использовать robin_stocks, у которой на момент написания статьи чуть более 670 звезд. Давайте ее установим:
$ pip install robin_stocks
Для большинства действий нужен логин, так что для начала войдем в систему. RobinHood требует многофакторную аутентификацию, так что необходимо ее настроить. Войдите в свою учетную запись, включите двухфакторную аутентификацию и выберите «other» в ответе на вопрос «Какое приложение вы собираетесь использовать?». Вы получите буквенно-цифровой код, который мы применим:
import pyotp
import robin_stocks as robinhood
RH_USER_EMAIL = <<<YOUR EMAIL GOES HERE>>>
RH_PASSWORD = <<<YOUR PASSWORD GOES HERE>>>
RH_MFA_CODE = <<<THE ALPHANUMERIC CODE GOES HERE>>>
timed_otp = pyotp.TOTP(RH_MFA_CODE).now()
login = rh.login(RH_USER_EMAIL, RH_PASSWORD, mfa_code=totp)
Купить или продать — дело нехитрое:
# Buying 5 shares of Google
rh.order_buy_market('GOOG', 5)
# Selling 5 shares of Google
rh.order_sell_market('GOOG', 5)
Примеры и варианты использования для продвинутых пользователей можно посмотреть в документации.
Alpaca
Для Alpaca мы используем библиотеку alpaca-trade-api
, у которой на GitHub более 700 звезд. Устанавливаем:
$ pip install alpaca-trade-api
Войдите в учетную запись и получите API key ID и секретный ключ, они нужны для входа в систему:
import alpaca_trade_api as alpaca
ALPACA_KEY_ID = <<<YOUR KEY ID GOES HERE>>>
ALPACA_SECRET_KEY = <<<YOUR SECRET KEY GOES HERE>>>
# Change to https://api.alpaca.markets for live
BASE_URL = 'https://paper-api.alpaca.markets'
api = alpaca.REST(
ALPACA_KEY_ID, ALPACA_SECRET_KEY, base_url=BASE_URL)
Отправлять поручения здесь несколько сложнее, чем в RobinHood:
# Buying 5 shares of Google
api.submit_order(
symbol='GOOG',
qty='5',
side='buy',
type='market',
time_in_force='day'
)
# Selling 5 shares of Google
api.submit_order(
symbol='GOOG',
qty='5',
side='sell',
type='market',
time_in_force='day'
)
Готово! Напомню, что оставлять свои учетные данные в виде Plain Text — чрезвычайно плохая идея. Но не переживайте, на следующем этапе мы перейдем к переменным среды, это намного безопаснее. Теперь давайте развернем модель в облаке и настроим мониторинг.
Деплоймент и мониторинг
Мы собираемся задеплоить нашу систему в AWS Lambda. Для работы это не лучший вариант, поскольку в Lambda нет хранилища, а обученную модель пришлось бы где-то хранить, например в S3.
Но пока обойдемся и этим — запланируем ежедневный запуск Lambda и обучение модели на данных за текущий день. Для мониторинга настроим бот в Telegram, который отправляет сообщение с действием и его результатом. AWS Lambda можно пользоваться бесплатно, если не превышать заданные лимиты; но если вы хотите отправлять очень много сообщений, помните о квотах.
Для начала создадим бота. Я опирался на официальную инструкцию из телеграма:
Найдите в телеграме пользователя @BotFather.
Используйте команду
\newbot
, выберите название и имя пользователя для бота.Получите и сохраните в надежном месте токен, он вам скоро понадобится.
Следующий этап — деплоймент. Есть несколько способов деплоймента в Lambda. Я собираюсь использовать фреймворк serverless, так что давайте его установим и создадим шаблон.
$ npm install serverless --global
$ serverless create --template aws-python3 --path ai_trading_system
Мы создали папку scheduled_tg_bot
с тремя файлами: .gitignore
, serverless.yml
, и handler.py
. Serverless.yml
определяет деплоймент: что, когда и как будет запущено. А файл handler.py
содержит запускаемый код.
import telegram
import sys
import os
CHAT_ID = XXXXXXXX
TOKEN = os.environ['TELEGRAM_TOKEN']
# The global variables should follow the structure:
# VARIABLE = os.environ['VARIABLE']
# for instance:
# RH_USER_EMAIL = os.environ['RH_USER_EMAIL]
def do_everything():
# The previous code to get the data, train the model
# and send the order to the broker goes here.
return 'The action performed'
def send_message(event, context):
bot = telegram.Bot(token=TOKEN)
action_performed = do_everything() bot.sendMessage(chat_id=CHAT_ID, text=action_performed)
Нужно поменять CHAT_ID
на ID группы, канала или диалога, с которыми бот должен взаимодействовать. Здесь можно узнать, как получить ID канала, а здесь — ID группы.
Теперь давайте определим, как запускать код. Откройте serverless.yml
и напишите:
org: your-organization-name
app: your-app-name
service: ai_trading_system
frameworkVersion: “>=1.2.0 <2.0.0”
provider:
name: aws
runtime: python3.6
environment:
TELEGRAM_TOKEN: ${env:TELEGRAM_TOKEN}
# If using RobinHood
RH_USER_EMAIL: ${env:RH_USER_EMAIL}
RH_PASSWORD: ${env:RH_PASSWORD}
RH_MFA_CODE: ${env:RH_MFA_CODE}
# If using Alpaca
ALPACA_KEY_ID: ${env:ALPACA_KEY_ID}
ALPACA_SECRET_KEY: ${env:ALPACA_SECRET_KEY}
functions:
cron:
handler: handler.send_message
events:
# Invoke Lambda function at 21:00 UTC every day
- schedule: cron(00 21 * * ? *)
Этот код сообщает AWS, какая среда выполнения нам нужна, и берет токен телеграма из нашего собственного окружения, чтобы нам не пришлось его развертывать. После этого мы определяем Сron для ежедневного запуска функции в 21:00.
Единственное, что осталось сделать перед деплойментом, это получить учетные данные AWS и установить их как переменные среды вместе с токеном и остальными переменными как переменные среды. Получить учетные данные достаточно просто.
Из консоли AWS:
перейдите в My Security Credentials — Users — Add user;
выберите имя пользователя и Programmatic access;
на следующей странице выберите Attach existing policies directly — AdministratorAccess;
скопируйте и сохраните Access Key ID и Secret Access Key;
Вот и все. Теперь давайте экспортируем учетные данные AWS и токен телеграма. Откройте терминал и напишите:
$ export AWS_ACCESS_KEY_ID=[your key goes here]
$ export AWS_SECRET_ACCESS_KEY=[your key goes here]
$ export TELEGRAM_TOKEN=[your token goes here]#
If using RobinHood
$ export RH_USER_EMAIL=[your mail goes here]
$ export RH_PASSWORD=[your password goes here]
$ export RH_MFA_CODE=[your mfa code goes here]
# If using Alpaca
$ export ALPACA_KEY_ID=[your key goes here]
$ export ALPACA_SECRET_KEY=[your key goes here]
Установите необходимые пакеты локально и выполните деплоймент в AWS:
$ pip3 install -r requirements.txt -t . --system
$ serverless deploy
Готово! Бот будет торговать за нас каждый день в 21:00 и отправлять нам сообщения о совершенном действии. Для апробации концепции неплохо. Пожалуй, можно порадовать приятеля: теперь он может расслабиться и заниматься трейдингом, не заглядывая в смартфон сто раз в день :)
Напомню, что у всех ресурсов, которые мы использовали в этом руководстве, есть собственная документация. Вы можете углубиться в любом направлении, которое вас заинтересует, ведь мы опробовали всего-навсего игрушечную систему! И все-таки я думаю, что эта игрушечная система — хорошее начало для многофункционального сложного продукта. Удачи!
Команда VK Cloud Solutions развивает собственные ML-решения. Будем признательны, если вы их протестируете и дадите обратную связь. Для тестирования пользователям при регистрации начисляем 3000 бонусных рублей.
Читать по теме:
Как дата-сайентистам эффективно сотрудничать с дата-инженерами
Четыре хитрости в работе с пайплайнами данных, о которых знают не все
Настоящее и будущее дата-инжиниринга