1. Обзор
Торговый бот — это компьютерная программа, которая может автоматически размещать ордера на рынке или на бирже без необходимости вмешательства человека.
В этом уроке мы будем использовать Cassandre для создания простого бота для торговли криптовалютой, который будет генерировать позиции, когда мы сочтем, что это лучший момент.
2. Обзор бота
Торговля означает «обмен одного предмета на другой».
На финансовых рынках это покупка акций, фьючерсов, опционов, свопов, облигаций или, как в нашем случае, криптовалюты. Идея здесь состоит в том, чтобы купить криптовалюту по определенной цене и продать ее по более высокой цене, чтобы получить прибыль (даже если мы все еще можем получить прибыль, если цена упадет с короткой позицией).
Мы будем использовать обмен в песочнице; песочница — это виртуальная система, в которой у нас есть «фальшивые» активы, где мы можем размещать заказы и получать тикеры.
Во-первых, давайте посмотрим, что мы будем делать:
Добавьте стартер загрузки Cassandre spring в наш проект
Добавьте необходимую конфигурацию для подключения к бирже
Создайте стратегию:
Получение тикеров с биржи
Выберите, когда покупать
Когда пришло время покупать, проверяем, достаточно ли у нас активов и создаем позицию
Отображение журналов, чтобы увидеть, когда позиции открыты/закрыты и сколько прибыли мы получили
Проведите тесты на исторических данных, чтобы увидеть, сможем ли мы получить прибыль.
3. Зависимости Maven
Давайте начнем с добавления необходимых зависимостей в наш pom.xml
, сначала загрузчик Cassandre spring boot :
<dependency>
<groupId>tech.cassandre.trading.bot</groupId>
<artifactId>cassandre-trading-bot-spring-boot-starter</artifactId>
<version>4.2.1</version>
</dependency>
Cassandre использует XChange для подключения к криптобиржам. В этом уроке мы будем использовать библиотеку Kucoin XChange :
<dependency>
<groupId>org.knowm.xchange</groupId>
<artifactId>xchange-kucoin</artifactId>
<version>5.0.8</version>
</dependency>
Мы также используем hsqld
для хранения данных:
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.5.2</version>
</dependency>
Для тестирования нашего торгового бота на исторических данных мы также добавляем наш стартер загрузки Cassandre spring для тестов :
<dependency>
<groupId>tech.cassandre.trading.bot</groupId>
<artifactId>cassandre-trading-bot-spring-boot-starter-test</artifactId>
<version>4.2.1</version>
<scope>test</scope>
</dependency>
4. Конфигурация
Давайте отредактируем create application.properties
, чтобы установить нашу конфигурацию:
# Exchange configuration
cassandre.trading.bot.exchange.name=kucoin
cassandre.trading.bot.exchange.username=kucoin.cassandre.test@gmail.com
cassandre.trading.bot.exchange.passphrase=cassandre
cassandre.trading.bot.exchange.key=6054ad25365ac6000689a998
cassandre.trading.bot.exchange.secret=af080d55-afe3-47c9-8ec1-4b479fbcc5e7
# Modes
cassandre.trading.bot.exchange.modes.sandbox=true
cassandre.trading.bot.exchange.modes.dry=false
# Exchange API calls rates (ms or standard ISO 8601 duration like 'PT5S')
cassandre.trading.bot.exchange.rates.account=2000
cassandre.trading.bot.exchange.rates.ticker=2000
cassandre.trading.bot.exchange.rates.trade=2000
# Database configuration
cassandre.trading.bot.database.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
cassandre.trading.bot.database.datasource.url=jdbc:hsqldb:mem:cassandre
cassandre.trading.bot.database.datasource.username=sa
cassandre.trading.bot.database.datasource.password=
Конфигурация имеет четыре категории:
- Конфигурация биржи : учетные данные биржи, которые мы настроили для нас, для подключения к существующей учетной записи песочницы на Kucoin.
- Режимы : режимы, которые мы хотим использовать. В нашем случае мы просим Cassandre использовать данные песочницы.
- Скорость вызовов API биржи : указывает, с какой скоростью мы хотим получать данные (счета, ордера, сделки и тикеры) с биржи. Будь осторожен; у всех бирж есть максимальные ставки, по которым мы можем их коллировать
- Конфигурация базы данных : Cassandre использует базу данных для хранения позиций, ордеров и сделок. В этом руководстве мы будем использовать простую базу данных
hsqld
в памяти. Конечно, в продакшене мы должны использовать постоянную базу данных.
Теперь давайте создадим такой же файл в application.properties
в нашем тестовом каталоге, но мы изменим cassandre.trading.bot.exchange.modes.dry
на true
, потому что во время тестов мы не хотим отправлять реальные ордера в песочницу. Мы только хотим смоделировать их.
5. Стратегия
Торговая стратегия — это фиксированный план, предназначенный для получения прибыльной прибыли; мы можем сделать свой собственный, добавив класс Java с аннотацией @CassandreStrategy
и расширив BasicCassandreStrategy
.
Давайте создадим наш класс стратегии в MyFirstStrategy.java
:
@CassandreStrategy
public class MyFirstStrategy extends BasicCassandreStrategy {
@Override
public Set<CurrencyPairDTO> getRequestedCurrencyPairs() {
return Set.of(new CurrencyPairDTO(BTC, USDT));
}
@Override
public Optional<AccountDTO> getTradeAccount(Set<AccountDTO> accounts) {
return accounts.stream()
.filter(a -> "trade".equals(a.getName()))
.findFirst();
}
}
Реализация BasicCassandreStrategy
заставляет нас реализовать два метода getRequestedCurrencyPairs()
и getTradeAccount()
:
В getRequestedCurrencyPairs()
мы должны вернуть список обновлений валютных пар, которые мы хотим получать от биржи. Валютная пара — это котировка двух разных валют, при этом стоимость одной валюты котируется по отношению к другой. В нашем примере мы хотим работать с BTC/USDT.
Чтобы было понятнее, мы можем получить тикер вручную с помощью следующей команды curl
:
curl -s https://api.kucoin.com/api/v1/market/orderbook/level1?symbol=BTC-USDT
У нас получится что-то вроде этого:
{
"time": 1620227845003,
"sequence": "1615922903162",
"price": "57263.3",
"size": "0.00306338",
"bestBid": "57259.4",
"bestBidSize": "0.00250335",
"bestAsk": "57260.4",
"bestAskSize": "0.01"
}
Значение цены указывает, что 1 BTC стоит 57263,3 USDT.
Другой метод, который мы должны реализовать, это getTradeAccount()
. На бирже у нас обычно несколько аккаунтов, и Кассандре нужно знать, какой из аккаунтов является торговым. Для этого нам нужно реализовать метод getTradeAccount()
, который дает нам в качестве параметра список счетов, которыми мы владеем, и из этого списка мы должны вернуть тот, который мы хотим использовать для торговли.
В нашем примере наш торговый счет на бирже называется trade
, поэтому мы просто возвращаем его.
6. Создание позиций
Чтобы получать уведомления о новых данных, мы можем переопределить следующие методы BasicCassandreStrategy
:
onAccountUpdate()
для получения обновлений об учетной записиonTickerUpdate()
для получения новых тикеровonOrderUpdate()
для получения обновлений о заказахonTradeUpdate()
), чтобы получать обновления о сделкахonPositionUpdate()
для получения обновлений о позицияхonPositionStatusUpdate()
для получения обновлений об изменении статуса позиции
В этом уроке мы реализуем тупой алгоритм: мы проверяем каждый новый полученный тикер. Если цена 1 BTC опускается ниже 56 000 USDT, мы думаем, что пришло время покупать .
Чтобы упростить расчет прибыли, заказы, сделки и закрытие, Cassandre предоставляет класс для автоматического управления позициями.
Чтобы использовать его, первым делом необходимо создать правила для позиции благодаря классу PositionRulesDTO
, например:
PositionRulesDTO rules = PositionRulesDTO.builder()
.stopGainPercentage(4f)
.stopLossPercentage(25f)
.create();
Затем создадим позицию с этим правилом:
createLongPosition(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0.01"), rules);
В этот момент Cassandre создаст ордер на покупку 0,01 BTC. Статус позиции будет ОТКРЫТИЕ, и когда все соответствующие сделки поступят, статус изменится на ОТКРЫТАЯ. Отныне для каждого полученного тикера Cassandre будет автоматически вычислять с новой ценой, приведет ли закрытие позиции по этой цене к срабатыванию одного из наших двух правил (4% стоп-прибыли или 25% стоп-лосса).
Если сработает одно правило, Cassandre автоматически создаст ордер на продажу наших 0,01 BTC. Статус позиции изменится на ЗАКРЫТИЕ
, а когда все соответствующие сделки поступят, статус изменится на ЗАКРЫТО
.
Это код, который у нас будет:
@Override
public void onTickerUpdate(TickerDTO ticker) {
if (new BigDecimal("56000").compareTo(ticker.getLast()) == -1) {
if (canBuy(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0.01"))) {
PositionRulesDTO rules = PositionRulesDTO.builder()
.stopGainPercentage(4f)
.stopLossPercentage(25f)
.build();
createLongPosition(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0.01"), rules);
}
}
}
Подводить итоги:
Для каждого нового тикера мы проверяем, находится ли цена ниже 56000.
Если на нашем торговом счете достаточно USDT, мы открываем позицию на 0,01 BTC.
Отныне для каждого тикера:
Если рассчитанная прибыль с новой ценой превышает 4% прибыли или 25% убытка, Cassandre закроет позицию, которую мы создали, продав купленные 0,01 BTC.
7. Следите за эволюцией позиций в логах
Наконец, мы реализуем onPositionStatusUpdate()
, чтобы видеть, когда позиции открываются/закрываются:
@Override
public void onPositionStatusUpdate(PositionDTO position) {
if (position.getStatus() == OPENED) {
logger.info("> New position opened : {}", position.getPositionId());
}
if (position.getStatus() == CLOSED) {
logger.info("> Position closed : {}", position.getDescription());
}
}
8. Тестирование на истории
Проще говоря, тестирование стратегии на истории — это процесс тестирования торговой стратегии на предыдущих периодах. Торговый бот Cassandre позволяет моделировать реакцию ботов на исторические данные.
Первый шаг — поместить наши исторические данные (файлы CSV или TSV) в папку src/test/resources .
Если мы под Linux, вот простой скрипт для их генерации:
startDate=`date --date="3 months ago" +"%s"`
endDate=`date +"%s"`
curl -s "https://api.kucoin.com/api/v1/market/candles?type=1day&symbol=BTC-USDT&startAt=${startDate}&endAt=${endDate}" \
| jq -r -c ".data[] | @tsv" \
| tac $1 > tickers-btc-usdt.tsv
Он создаст файл с именем tickers-btc-usdt.tsv
, который содержит исторический курс BTC-USDT от startDate
(3 месяца назад) до endDate
(сейчас).
Второй шаг — создать балансы наших виртуальных счетов, чтобы смоделировать точную сумму активов, которые мы хотим инвестировать.
В этих файлах для каждой учетной записи мы устанавливаем баланс каждой криптовалюты. Например, это содержимое файла user-trade.csv, которое имитирует активы нашего торгового счета:
Этот файл также должен находиться в папке src/test/resources
.
BTC 1
USDT 10000
ETH 10
Теперь мы можем добавить тест:
@SpringBootTest
@Import(TickerFluxMock.class)
@DisplayName("Simple strategy test")
public class MyFirstStrategyLiveTest {
@Autowired
private MyFirstStrategy strategy;
private final Logger logger = LoggerFactory.getLogger(MyFirstStrategyLiveTest.class);
@Autowired
private TickerFluxMock tickerFluxMock;
@Test
@DisplayName("Check gains")
public void whenTickersArrives_thenCheckGains() {
await().forever().until(() -> tickerFluxMock.isFluxDone());
HashMap<CurrencyDTO, GainDTO> gains = strategy.getGains();
logger.info("Cumulated gains:");
gains.forEach((currency, gain) -> logger.info(currency + " : " + gain.getAmount()));
logger.info("Position still opened :");
strategy.getPositions()
.values()
.stream()
.filter(p -> p.getStatus().equals(OPENED))
.forEach(p -> logger.info(" - {} " + p.getDescription()));
assertTrue(gains.get(USDT).getPercentage() > 0);
}
}
@Import из TickerFluxMock
загрузит исторические данные из нашей папки
src/test/resources
и отправит их в нашу стратегию. Затем мы используем метод await()
, чтобы убедиться, что все тикеры, загруженные из файлов, были отправлены в нашу стратегию. Мы заканчиваем отображением закрытых позиций, все еще открытой позиции и общей прибыли.
9. Заключение
В этом руководстве показано, как создать стратегию, взаимодействующую с криптобиржей, и протестировать ее на исторических данных.
Конечно, наш алгоритм был прост; в реальной жизни цель состоит в том, чтобы найти перспективную технологию, хороший алгоритм и хорошие данные, чтобы знать, когда мы можем открыть позицию. Мы можем, например, использовать технический анализ, поскольку Cassandre интегрирует ta4j.
Весь код этой статьи доступен на GitHub .