В предыдущей статье мы использовали приложение MiniFIX для подключения и отправки сообщений на тестовую биржу с помощью протокола FIX. В этой статье напишем собственную реализацию клиента для получения рыночных данных в виде небольшого SpringBoot-приложения. Код доступен в репозитории.
Для реализации приложения нам понадобится:
- Java 8
- Maven
- Spring boot 2.2.5
- Lombok
- QuickFix/J
Содержание для упрощения навигации по статье:
- FIX-Engine и запуск тестового сервера
- Структура проекта
- Настройка параметров подключения
- Создание FIX-приложения
- Создание сервиса для подключения к серверу
- Отправка запроса на получение рыночных данных
- Обработка ответа и сохранение рыночных данных
- Запуск приложения
FIX-Engine и запуск тестового сервера
FIX-Engine, или FIX-движок, обеспечивает связь со сторонними системами по протоколу FIX. Он отвечает за преобразование данных в FIX-сообщения, а также за создание сессии и обеспечение ее работы: проверку валидности сообщений, генерацию контрольных сумм, восстановление работы после потери связи и т.д (здесь можно почитать более подробно).
В нашем случае в роли такого движка выступает QuickFix/J. В предыдущей части я использовала пример Executor из модуля examples, но в нем обрабатываются только сообщения на создание торговых заявок. В этом же модуле есть более подходящий пример — OrderMatch (quickfixj-examples-ordermatch), в нем помимо поддержки торговых заявок присутствует обработка сообщений на получение рыночных данных (MarketDataRequest).
Когда вы первый раз клонируете репозиторий, обязательно нужно выполнить сборку проекта, чтобы сгенерировались FIX-сообщения для различных версий протокола. В Readme проекта есть описание команд для различных видов сборки (с тестами и без), самый быстрый:
mvn clean package -Dmaven.javadoc.skip=true -DskipTests -PskipBundlePlugin
Процесс сборки длился у меня где-то минут 6-7, так что в это время можно заварить себе чашечку чая изучить настройки сервера и приступить к написанию клиента.
Как я уже описывала ранее, открываем файл resources/quickfix.examples.ordermatch/ordermatch.cfg, проверяем SocketAcceptPort и заполняем поле TargetCompID нужным значением для нашего клиента (можно оставить BANZAI, которое указано по умолчанию, можно написать любое другое на ваше усмотрение):
[default]
FileStorePath=target/data/ordermatch
DataDictionary=FIX42.xml
SocketAcceptPort=9876 // порт для подключения
BeginString=FIX.4.2 // версия FIX 4.2
[session]
SenderCompID=EXEC // идентификатор сервера
TargetCompID=FIX_CLIENT // идентификатор клиента
ConnectionType=acceptor
StartTime=00:00:00
EndTime=00:00:00
Если хотите поменять значение идентификатора клиента, то лучше, конечно, сделать это перед сборкой, чтобы не пришлось собирать еще раз.
Когда сборка завершится, заходим в quickfixj\quickfixj-examples\ordermatch\target, проверяем, что там появились *.jar файлы:
Запускаем файл quickfixj-examples-ordermatch-2.2.0-SNAPSHOT-standalone.jar, так как он содержит все необходимые для запуска зависимости:
java -jar quickfixj-examples-ordermatch-2.2.0-SNAPSHOT-standalone.jar
Если появилась запись "Started QFJ Message Processor" – значит, сервер запустился. Проверьте, что в строке "Listening for connections at … [FIX4.2:EXEC->FIX_CLIENT]" указано нужное значение идентификатора клиента.
Структура проекта
Вот так выглядит готовый проект (стандартная структура веб-приложений: сервисы, контроллеры, модельки и т.д):
Создаем maven-проект со стандартными зависимостями и добавляем библиотеку QuickFix/J для работы с протоколом FIX:
<properties>
<quickfixj.version>2.0.0</quickfixj.version>
</properties>
<dependency>
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-core</artifactId>
<version>${quickfixj.version}</version>
</dependency>
<dependency>
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-messages-fix42</artifactId>
<version>${quickfixj.version}</version>
</dependency>
Я подключила 2 модуля: quickfixj-core и quickfixj-messages-fix42 для работы с сообщениями только версии FIX 4.2.
Если в вашем приложении предполагаются сообщения различных версий протокола, можно подключить quickfixj-core + quickfixj-messages-all или просто quickfixj-all.
Полная версия pom.xml доступна в репозитории.
Настройка параметров подключения
По аналогии с файлом настроек на сервере, создадим файл resources/config/client.cfg с настройками нашего приложения.
В файле может быть один блок [default], в котором находятся параметры, общие для всех сессий, и несколько блоков [session] для описания параметров конкретной сессии (если сервер поддерживает сообщения различных версий протокола FIX, то для каждой версии создается отдельный блок [session]).
[default]
SenderCompID=FIX_CLIENT // идентификатор отправителя
TargetCompID=EXEC // идентификатор получателя
ConnectionType=initiator // приложение является клиентом
NonStopSession=Y
SocketConnectHost=localhost
ReconnectInterval=5
HeartBtInt=30
FileStorePath=target/data/banzai
UseDataDictionary=Y
DataDictionary=dictionary/fix4_2.xml
ValidateUserDefinedFields=N
AllowUnknownMsgFields=Y
ValidateUserDefinedFields=N
AllowUnknownMsgFields=Y
[session]
BeginString=FIX.4.2
ResetOnLogon=Y
Начнем с блока [default]:
- параметры сессии
— SenderCompID, TargetCompID – идентификатор отправителя и получателя сообщений соответственно (sender – наше приложение, target – сервер). Убедитесь, что эти значения совпадают со значениями параметров на сервере.
— ConnectionType (initiator/acceptor) – указывает, является наше приложение клиентом или сервером.
— С помощью параметров StartTime и EndTime можно указать время начала и соответственно завершения работы сессии (например, биржа работает с 9.00 до 18.00, поэтому нет смысла запускать сессию вне этого времени). Если же сессия будет работать весь день, то можно указать NonStopSession=Y, что будет равносильно варианту StartTime=00:00:00 и EndTime=00:00:00.
-параметры валидации сообщений
— UseDataDictionary=Y – можно использовать словарь сообщений, если вы работаете с биржей, спецификация сообщений которой отличается от стандартной (например, в словаре можно указать дополнительные теги или типы сообщений). При этом использование словаря обязательно, если есть сообщения с повторяющимися группами.
— DataDictionary – путь к словарю. - параметры клиента
— ReconnectInterval – интервал переподключения к серверу (в секундах).
— HeartBtInt – интервал проверочных сообщений типа HeartBeat (в секундах).
— LogonTimeout, LogoutTimeout – время ожидания Logon и Logout сообщений перед отключением сессии (в секундах).
— SocketConnectHost, SocketConnectPort – хост и порт подключения к acceptor-у. - параметры хранения сообщений и логов
Сообщения и логи можно хранить в файлах или в базе данных (сообщения можно нигде не хранить, если выставить параметр PersistMessages=N).
Я указала FileStorePath=target/data/banzai для хранения сообщений и номеров последовательностей в файле. Можно указать параметры базы данных (JdbcURL, JdbcUser, JdbcPassword и т.д), тогда сообщения будут храниться в базе данных.
В настройках конкретной сессии (в блоке [session]) главное – заполнить параметр BeginString, в котором указывается версия протокола FIX, использующегося в сообщениях.
Любые настройки можно указывать непосредственно при создании подключения в коде с помощью класса SessionSettings.
Подробнее о конфигурации клиента можно почитать в официальной документации.
Создание FIX-приложения
Теперь перейдем непосредственно к коду клиента. Чтобы создать FIX-приложение, нам нужно просто реализовать интерфейс Application:
public interface Application {
void onCreate(SessionID sessionId);
void onLogon(SessionID sessionId);
void onLogout(SessionID sessionId);
void toAdmin(Message message, SessionID sessionId);
void toApp(Message message, SessionID sessionId)
throws DoNotSend;
void fromAdmin(Message message, SessionID sessionId)
throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon;
void fromApp(Message message, SessionID sessionId)
throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType;
}
Эти методы вызываются в результате событий, происходящих в приложении (подробнее).
Метод fromApp
срабатывает при получении сообщений с сервера, то есть в нем происходит основная логика. Остальные методы в основном служебные. Для удобства я создала абстрактный базовый класс BaseFixService, который реализует служебные методы интерфейса Application, и его наследника FixClientService, который занимается обработкой сообщений с сервера и соответственно реализует метод fromApp
.
В приложении может быть установлено несколько сессий, поэтому в базовом классе будем хранить все сессии:
Map<SessionID, Session> sessions = new HashMap<>();
Так как метод onCreate
срабатывает при создании новой сессии, в нем будем сохранять сессию по полученному ID с помощью метода lookupSession
:
@Override
public void onCreate(SessionID sessionId) {
log.info(">> onCreate for session: {}", sessionId);
Session session = Session.lookupSession(sessionId);
if (session != null) {
sessions.put(sessionId, session);
} else {
log.warn("Requested session is not found.");
}
}
Когда сессия отключается от сервера (мы завершили сеанс сообщением Logout или произошли какие-то технические проблемы и связь оборвалась), мы удаляем её из нашего хранилища.
@Override
public void onLogout(SessionID sessionId) {
log.info(">> onLogout for session: {}", sessionId);
sessions.remove(sessionId);
}
В FixClientService у нас находится главный обработчик сообщений – метод fromApp
:
@Override
public void fromApp(Message message, SessionID sessionId) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType {
try {
String type = MessageUtils.getMessageType(message.toString()); // получение типа сообщения
switch (type) {
case MARKET_DATA_SNAPSHOT_FULL_REFRESH:
log.info("MarketData message: {}", message);
break;
case SECURITY_DEFINITION:
log.info("SecurityDefinition message: {}", message);
break;
default:
log.info("Unhandled message {} of type: {}", message, type);
}
} catch (Exception ex) {
log.debug("Unexpected exception while processing message.", ex);
}
}
С помощью класса MessageUtils библиотеки QuickFix/J можно получить тип входящего сообщения и далее обработать каждый случай (здесь для примера я указала несколько типов сообщений и вывела их в лог). В этой статье реализуем получение рыночных данных и их сохранение в кэш, остальные типы сообщений и их обработку более подробно разберем в следующих статьях и дополним логику нашего клиента.
Создание сервиса для подключения к серверу
Когда мы создали реализацию FIX-приложения, можно приступить к сервису для подключения к серверу – ConnectorService. При запуске приложения он будет создавать и запускать сокет для обмена сообщениями.
Для обмена сообщениями нужно создать SocketInitiator (на сервере аналогично создается SocketAcceptor). При создании передаются следующие параметры:
Application
– FIX-приложение (т.е. класс, реализующий интерфейс Application, FixClientService в нашем случае)MessageStoreFactory
– способ хранения сообщений, это может быть, например,JdbcStoreFactory
(хранение в базе данных),MemoryStoreFactory
(хранение в памяти),FileStoreFactory
(хранение в файле).SessionSettings
– настройки сессии, для их создания нужно передать файл с настройками (либо его название, либоInputStream
).LogFactory
– хранение логов (аналогично сообщениям это может бытьFileLogFactory
,JdbcLogFactory
), я использовалаSLF4JLogFactory
.MessageFactory
– используется для создания сообщений (можно использоватьDefaultMessageFactory
илиMessageFactory
для конкретной версии протокола FIX).
Путь к файлу настроек и дополнительные параметры (хост и порт подключения) для удобства я вынесла в конфигурацию приложения (application.yaml):
fix:
cfg: 'classpath:config/client.cfg'
socketConnectHost: localhost
socketConnectPort: 9876
Соответственно при создании настроек сессии я использую этот файл и с помощью метода sessionSettings.set(String key, String value)
добавляю параметры SocketConnectHost, SocketConnectPort:
try (InputStream inputStream = config.getCfg().getInputStream()) {
SessionSettings sessionSettings = new SessionSettings(inputStream);
sessionSettings.setString("SocketConnectHost", config.getSocketConnectHost());
sessionSettings.setString("SocketConnectPort", config.getSocketConnectPort());
MessageStoreFactory storeFactory = new FileStoreFactory(sessionSettings);
SLF4JLogFactory logFactory = new SLF4JLogFactory(sessionSettings);
MessageFactory messageFactory = new DefaultMessageFactory();
socketInitiator = new SocketInitiator(fixClientService, storeFactory, sessionSettings, logFactory, messageFactory);
socketInitiator.start();
} catch (Exception ex) {
log.error("Exception while establishing connection to FIX server.", ex);
throw new FixClientException("Exception while establishing connection to FIX server.", ex);
}
После создания настроек сессии объявляем LogFactory, MessageFactory, MessageStoreFactory и передаем их в конструктор SocketInitiator. Вызвав метод start()
запустим подключение и сможем получать сообщения.
Не забудьте закрыть сокет при завершении работы с помощью метода stop()
.
Отправка запроса на получение рыночных данных
Когда приложение запустится и установится соединение с сервером, мы сможем отправлять и получать сообщения. Так как взаимодействие у нас построено на сокетах, отправка сообщения и получение ответа на него происходят асинхронно. Поэтому у нас будет два контроллера:
- для инициации отправки сообщений
- для получения данных, сохраненных в результате обработки ответов на отправленные ранее сообщения.
Чтобы получить рыночные данные (например, цену покупки, цену продажи инструмента), нам нужно отправить сообщение-запрос на данные и соответственно обработать ответное сообщение в методе fromApp
.
Напишем метод для создания сообщения типа MarketDataRequest (о тегах сообщения можно почитать в спецификации).
public static Message createMarketDataRequest(String symbol) {
}
В библиотеке QuickFix/J все сообщения представляют собой классы, поля в которых соответствуют тегам. Можно создать экземпляр класса нужного нам сообщения и с помощью метода set()
заполнить теги. Теги также представляют собой классы с обязательным полем FIELD
, в котором хранится соответствующее числовое значение.
Например, тег symbol=55
:
public class Symbol extends StringField {
public static final int FIELD = 55;
// constructors
}
Стандартные теги (соответствующие спецификации конкретной версии FIX) обычно можно заполнить напрямую. Например, для сообщения MarketDataRequest (далее буду сокращенно писать MDR) определены методы
set(SubscriptionRequestType value)
set(MarketDepth value)
set(Symbol value)
// ...
Если же при работе с конкретной биржей в сообщении присутствуют дополнительные теги, их можно задать с помощью общего метода setField(int key, Field<?> field)
: например, setField(5020, new IntField(10))
— добавим в сообщение тег <5020> со значением 10: 5020=10
.
Создадим объект класса MarketDataRequest:
private static int mdReqID = 1;
MarketDataRequest marketDataRequest = new MarketDataRequest(
new MDReqID(format("FixClient-%s", mdReqID++)),
new SubscriptionRequestType(SNAPSHOT), //263
new MarketDepth(1) //264, 1 = top of book
);
В конструкторе передается три параметра:
- MDReqID – уникальный в рамках данной сессии идентификатор сообщения.
- SubscriptionRequestType:
— SNAPSHOT = '0' (текущие данные);
— SNAPSHOT_PLUS_UPDATES = '1' (текущие данные + подписка на обновление данных; при выборе этого типа каждый раз при изменении рыночных данных для инструмента, сервер будет отправлять сообщение типа MarketDataSnapshotFullRefresh с новыми данными);
— DISABLE_PREVIOUS_SNAPSHOT_PLUS_UPDATE_REQUEST = '2' (отписка от получения данных + получение обновленных данных); - MarketDepth – глубина рынка для типа SNAPSHOT (цены формируются исходя из размещенных и ожидающих размещения заявок на покупку и продажу инструмента, эти заявки записываются в “книгу” заявок. Если указываем параметр равным 0 – будут учитываться все значения “книги”, если 1 – только “верхние” значения).
То же самое в виде сообщения: 262=FixClient-1 263=0 264=1
.
Далее нужно указать параметры, которые мы хотим получить в результате запроса рыночных данных. Некоторые параметры в FIX-сообщениях задаются группами. При этом начинается такая часть сообщения с тега, в котором указывается количество последующих групп. В нашем случае параметр <267> NoMdEntryTypes хранит количество групп, а сами группы формируются из тегов <269> MdEntryType. Например, 269=0
означает, что мы хотим получить цену, по которой можно продать инструмент (Bid), а 269=1
– цену, по которой мы сможем купить инструмент (Ask, или Offer). Полный список стандартных значений этого тега можно посмотреть в спецификации. QuickFix/J автоматически заполняет в теге <267> количество параметров, мы можем только заполнить нужные нам поля и добавить каждую группу в сообщение:
MarketDataRequest.NoMDEntryTypes group = new MarketDataRequest.NoMDEntryTypes(); //267
group.set(new MDEntryType(MDEntryType.BID));
marketDataRequest.addGroup(group);
group.set(new MDEntryType(MDEntryType.OFFER));
marketDataRequest.addGroup(group);
В сообщении будет выглядеть так: 267=2 269=0 269=1
.
Можно делать MDR сразу для нескольких инструментов, в поле <146> NoRelatedSum передается их количество и далее заполняются группы тегов для каждого инструмента. Для простого запроса достаточно передать идентификатор инструмента в теге <55> (для более сложных запросов на фьючерсы или опционы нужно указывать дополнительные параметры, но для нашего базового случая это не нужно).
MarketDataRequest.NoRelatedSym instrument = new MarketDataRequest.NoRelatedSym();
instrument.set(new Symbol(symbol));
marketDataRequest.addGroup(instrument);
В сообщении: 146=1 55=AAPL
.
Наш полученный MDR теперь можно отправить на сервер с помощью метода session.send()
:
@Override
public void sendMarkedDataRequest(String symbol) {
sessions.forEach((sessionID, session) ->
session.send(MsgUtils.createMarketDataRequest(symbol)));
}
Аналогично можно реализовать методы отправки любого другого сообщения (на создание заявки, на получение детальной информации об инструменте и т.д).
Для полученного метода отправки запроса на получение рыночных данных создадим REST endpoint, чтобы мы могли инициировать его отправку:
@PostMapping(value = "/market-data-request")
public void sendMarketDataRequest(@RequestParam("symbol") String symbol) {
fixClientService.sendMarkedDataRequest(symbol);
}
Так будет выглядеть запрос, чтобы создать и отправить сообщение для получения данных об акциях Apple:
POST localhost:9090/fix-client/v1/market-data-request?symbol=APPL
.
В результате будет отправлено сообщение:
8=FIX.4.2 9=117 35=V 34=3 49=FIX_CLIENT 52=20200601-17:10:34.103 56=EXEC 262=FixClient-1 263=0 264=1 146=1 55=AAPL 267=2 269=0 269=1 10=018
Обработка ответа и сохранение рыночных данных
Создадим отдельный сервис (MarketDataService), который будет обрабатывать рыночные данные, полученные в результате отправки запроса. Он будет сохранять полученные данные в объект, записывать их в память и отдавать при запросе по идентификатору инструмента.
Класс для хранения рыночных данных:
@Data
@Accessors(chain = true)
public class MarketDataModel {
private String symbol;
private BigDecimal bid;
private BigDecimal ask;
}
Теперь нужно разобраться, как правильно обработать сообщение с данными и сохранить его.
Вот так выглядит сообщение, отправленное нам в ответ на запрос по символу AAPL:
8=FIX.4.2 9=104 35=W 34=3 49=EXEC 52=20200601-17:10:34.119 56=FIX_CLIENT 55=AAPL 262=FixClient-1 268=1 269=0 270=123.45 10=236
.
Так как при запросе мы указывали группы параметров (Bid, Ask и т.д), разбирать сообщение тоже будем по группам:
message.getGroups(NoMDEntries.FIELD).forEach(group -> {
int type = MsgUtils.getIntField(group, MDEntryType.FIELD).orElse(-1);
BigDecimal value = MsgUtils.getDecimalField(group, MDEntryPx.FIELD).orElse(BigDecimal.ZERO);
switch (type) {
case 0:
dataModel.setBid(value);
break;
case 1:
dataModel.setAsk(value);
break;
default:
log.warn("Invalid entry type: {}", type);
break;
}
});
В теге <269> хранится название параметра, а в теге <270> его значение. Соответственно, если тип параметра = 0 (т.е. Bid), то мы сохраняем значение соответствующего ему тега <270> в поле bid
нашего объекта.
Далее проверяем тег <55> – идентификатор инструмента, и сохраняем по нему наши данные:
MsgUtils.getStrField(message, Symbol.FIELD).ifPresent(s -> {
dataModel.setSymbol(s);
marketData.put(s, dataModel);
});
Осталось только добавить сохранение данных в метод fromApp
в случай обработки сообщения типа MarketDataSnapshotFullRefresh:
case MARKET_DATA_SNAPSHOT_FULL_REFRESH:
marketDataService.saveMarketData(message);
break;
Теперь при получении нашим приложением сообщения типа MarketDataSnapshotFullRefresh будет происходить обработка и сохранение данных в память приложения.
Соответственно в отдельный Rest-Controller добавляем метод получения данных по идентификатору:
@GetMapping
public ResponseEntity<MarketDataModel> getMarketData(@RequestParam("symbol") String symbol) {
return new ResponseEntity<>(marketDataService.getMarketData(symbol), HttpStatus.OK);
}
Вызвав метод GET localhost:9090/fix-client/v1/market-data?symbol=AAPL
получим ответ:
{
"symbol": "AAPL",
"bid": 123.45,
"ask": null
}
Запуск приложения
Наконец, можем запустить наше приложение, убедиться, что подключение к серверу осуществляется успешно, и попробовать отправить запрос на получение рыночных данных.
Если при запуске приложения в логах отображаются ошибки подключения (ConnectException), как на скриншоте ниже, проверьте, что сервер запущен и что вы указали правильные идентификаторы клиента и сервера и хост и порт для подключения:
В случае успешного запуска клиент и сервер должны обменяться Logon-сообщениями:
Отправим запрос POST localhost:9090/fix-client/v1/market-data-request?symbol=APPL
, чтобы вызвать отправку сообщения MDR и убедимся, что сообщение действительно отправлено и ответ на него получен:
Кстати, сообщения можно удобно парсить с помощью сайта – просто вставляете текст сообщения и получаете разбор по тегам и значениям:
Теперь вызвав метод GET localhost:9090/fix-client/v1/market-data?symbol=AAPL
мы должны получить ответ:
{
"symbol": "AAPL",
"bid": 123.45,
"ask": null
}
Работает!
Конечно, на таком “игрушечном” примере далеко не уедешь, но для начала он хорошо подходит. Для более сложных примеров и для работы с условиями, приближенными к реальной бирже, можно получить доступ к тестовому контуру Московской биржи (MOEX) — для этого нужно оставить заявку на сайте. Я не нашла аналогичных тестовых контуров у других крупных бирж (именно для подключения напрямую через FIX-протокол), кроме симуляторов биржевой торговли, где выдаются виртуальные деньги и с помощью терминалов осуществляется торговля. Если знаете, где найти хороший тестовый сервер для работы по протоколу FIX, — поделитесь в комментариях, буду благодарна.
В следующей статье я планирую рассмотреть основные виды FIX-сообщений (соответственно дополнить приложение методами для их создания) и далее перейти к подробному рассмотрению процесса создания торговых заявок и их обработки биржей. Все примеры сообщений по-прежнему можно создавать с помощью приложения MiniFIX, если не хотите писать реализацию своего клиента.
kocherman
Как я люблю торговлю на бирже
Daemon_Hell
А видос как этот товарищ в 2018 вогнал своих клиентов в долги будет?
bepas
Заранее спасибо за инсайды и инсайты.
Да, я понимаю разницу между разрабом и юзером, но разраб, который вообще(!(?)) не имеет опыта в той области, для которой разрабатывает — будет ли успешен так, как тот, который этот опыт все же имеет?
Да, это вопрос про коньяк по утрам…
VeronikaY Автор
Я пишу как разработчик, имеющий опыт в создании приложения для торговли, а не как успешный трейдер. Поэтому и пишу о протоколе FIX и его использовании в Java-приложениях, а не о том, как заработать свой первый миллион на бирже или разработать торгового робота за 30 минут.
У меня был небольшой опыт, поскольку я разрабатывала ПО для размещения заявок на MOEX и в процессе тестирования общалась со специалистами и разбиралась, как там все устроено, почему некоторые заявки проходят быстро, а некоторые "зависают", почему биржа отклоняет некоторые запросы и т.д. Именно в рамках выполнения этой задачи мне не хватало хорошей теоретической базы и про протокол FIX, и про основное устройство биржи и процесса торгов.
P.S. А про женщин-трейдеров нечем с вами поделиться, к сожалению...
bepas
1) спасибо за подробности
2) «не как успешный трейдер» — вот как раз истории провалов тоже интересны
3) «нечем с вами поделиться» — давно тут сижу, никуда не тороплюсь
kocherman
Да вот тут буквально полтора месяца назад.
Один незатейливый трейдер решил как то на одной известной бирже прикупить фьючерсов на поставку нефти по 50 баксов. Ситуация двоякая, там нефть падала всю неделю, по техническому анализу вот-вот должна была дать разворот. Но нефть падала… падала… падала… и уже на $36 вроде бы надо выходить из сделки. Но нельзя, биржа остановила торги. Потом прошло пару дней, а потом еще два выходных и вот уже нефть торгуется по $15-$16. А потом и $5-$6. Прошло еще 3 рабочих дня, и биржа наконец закрыла сделку по курсу $-37 (минус тридцать семь долларов). И ладно бы депозит на все свои сбережения просадить, да вот биржа просит возместить её убытки по сделке почти такую же сумму, как была на депозите.
И таких человек 700. Можно погуглить на Youtube.
webmascon
о. прикольно. отрицательные цены на фьючерсы! представляю сколько ПО пришлось срочно переписывать, чтобы это не вызвало краша систем
Daemon_Hell
Не факт что именно переписывать — для некоторых инструментов отрицательные цены были предусмотрены — спреды по фьючерсам например.
webmascon
спреды по фьючерсам — да. но если например валидатор девиации цены заточен именно только под валидацию фьючерса или опциона, то отрицательная цена для него будет неожиданной
Daemon_Hell
Между 50 баксами и -37 прошел не один день. Когда нефть падала с 50 до 36 — остановки торгов не было (был выходной в РФ).
Кстати остановка торгов — совершенно штатная ситуация, которая описана в спецификации контракта.
Биржа просит возместить не ее убытки (торгуете вы не с биржей, биржа только гарант расчетов)
VeronikaY Автор
"Не как успешный трейдер" было к тому, что я вообще не трейдер. Из "провалов" разве что был случай, когда мне надо было проверить обработку частично исполненных заявок и я просадила пол-миллиона рублей на тестовом счете. А если серьезно, то на бирже с целью заработка или проверки успешных стратегий я торговать не пробовала.
bepas
kocherman спасибо, у меня ограничение от умнейшего Денискина, поэтому валю все в 1 куч(к)у
ну, если постараться, то можно и !#$%VeronikaY
В том смысле, что на тестовых счетах обычно все всё выЙгрывают. white hat тоже такие, но иногда можно и "… шляпу сними!.." www.youtube.com/watch?v=skUcdPkhawE а вот тоже интересно: Вы как инсайдер, верите в такое именно чтобы без использования инсайда? Даже упомянутый сериал показывает, что без эНтого — никуды!..
kocherman
Если честно, лично моё мнение про все биржи ровно как и собственно все рынки ценных бумаг и цифр — это подобно сети казино, где собрались разные безумные ребята, с разными торговыми идеями из Кащенко. А технический анализ, как инструмент по эффективности проигрывает кофейной гуще. А с такими тяжеловесами как карты Таро даже сравнивать никто не будет — они в разных лигах.
Опять же, по меому мнению, трейдеры с большим опытом уже как лет 5 торгуют на биржах криптовалют. Такую волатильность, как на рынках криптовалют, не предоставит ни один другой рынок ценных бумаг. — Играть в рулетку нужно по крупному. На криптовалютах каждый может за день умножить капитал в 5-10 раз. Разумеется, сливают капитал абсолютно с той же скоростью.
Для программистов биржи криптовалют могут быть более интересны отсутствием всяких заумных FIX, других непонятных монстровидных инструментов как QUIK, MetaTrader. Там торговые операции можно совершать HTTPS-запросом хоть из браузера, хоть bash-скриптами, хоть непосредственно из терминала, например, командами curl или wget. А если нужна высокочастотная торговля или realtime-мониторинг рынка, подключаешься к биржам по websocket. Информация о торгах без задержек доступна без регистрации. — Все биржи криптовалют предоставляют инфу по открытым каналам. Пример https://www.bitfinex.com/trading.
Описание API https://docs.bitfinex.com/docs/derivatives.
Библиотеки для торгов https://docs.bitfinex.com/docs/open-source-libraries.
Лично для меня HTTPS/JSON-RPC/WebSocket куда ближе чем FIX/QUIK/MetaTrader.
webmascon
понимаете, криптовалюта и криптобиржи это отдельный мир. там все по-другому, и серьезным людям это неинтересно
kocherman
Серьезным трейдерам нужна волатильность — все остальное вторично.
webmascon
серьезным инвесторам волатильность не нужна
Daemon_Hell
Особенно прикольно что на том же bitfinex'e в свое время информацию об ордерах раздавали раз в 15 секунд, очень высокочастотно.
Kraken до сих пор отвечает на API-запросы за 500 мс, тоже безумно быстро.
Плюсом на криптовалютных площадках куда выше риск контрагента — историй типа mt.gox и btc-e можно найти вагон.
kocherman
Несколькими комментариями выше я привел пример как в своё время (середина апреля 2020г) московская биржа не давала трейдерам выйти из сделки по фьючерсам нефти целую неделю.
Daemon_Hell
Пример то привели, только по факту не было такого. Даже в 2008 году, когда реально отменяли торги полностью — никто торги на неделю не приостанавливал.
Итоги торгов по тому самому контракту — www.moex.com/ru/marketdata/#/secid=CLJ0&boardgroupid=45&mode_type=history&mode=instrument&date_from=2015-06-04&date_till=2020-06-04
Если биржа не давала выйти целую неделю — кто тогда каждый день торговал?
kocherman
Смотрите самый первый комментарий.
kocherman
Извиняюсь, вы уже посмотрели.
kocherman
Да и bitfinex предоставляет услуги collocation и простенькие VPS для уменьшения пинга.
Daemon_Hell
Речь идет не о пинге, а о производительности самих криптобирж.
kocherman
Да к я про что говорю, разные моменты случаются. Но, в основном 99% времени торги идут в штатном режиме.
Да и отклик в 500мс как по мне достаточен для моих торговых алгоритмов.
Daemon_Hell
status.kraken.com/#month
У них такая задержка — это как раз штатная ситуация.
Если достаточно — прекрасно, но не стоит говорить о высокочастотной торговле тогда.
kocherman
Так по вашей ссылке на страничке показывает пинг конкретно вашего соединения с web-сайтом и биржей. Разве нет?.. У меня показывает значение намного ниже чем 500мс. Аж 370мс.
Daemon_Hell
Нет. Приезжает json с данными.
370 мс среднее, 545 мс последнее.
Для сравнения — на срочном рынке мосбиржи 150-200 мкс. А она далеко не самая быстрая
kocherman
Извиняюсь, точно все так, как вы говорите. Я просто зашел по ссылке с компьютера и увидел одну цифру, а потом по той же ссылке (только без #month) с мобильного и увидел там цифру 517. И я подумал что он говорит разные цифры, а теперь догнал, что там показывает статистику за день и за месяц.
pamef
Спасибо всем участникам: мне жуть как интересно. А есть ли площадка на русском, чтобы про это про все почитать тамошние дискуссии, но чтобы без явных школоло? Я понимаю, что мечать не вредно, но…
Daemon_Hell
smart-lab.ru
Но публика там достаточно разношерстная
pamef
Спасибо! Если еще кто-то что-то вспомнит — еще раз спасибо напишу!
webmascon
> Я не нашла аналогичных тестовых контуров у других крупных бирж (именно для подключения напрямую через FIX-протокол), кроме симуляторов биржевой торговли
тестовый контур предоставляют все биржи. без них никакая разработка невозможна. только предоставляется он уже реальным клиентам биржи, которые не поиграться на тестовом контуре хотят, а будут реально торговать в PROD и тестовый контур им нужен для отладки своего ПО. я разрабатывал ПО для SFE, OSE, SGX, HKFE, KRX, TFEX, TAIFEX, MDEX и везде был тестовый контур. но логин туда выдается только если вы (т.е. ваш работодатель — банк, брокер, вендор) подпишете контракт с биржей на торговлю, т.е. все по-серьезному.
VeronikaY Автор
Да, логично
Но к MOEX, насколько я поняла, можно просто по заявке подключиться, без каких-то страшных контрактов. Но может я и ошибаюсь.