Compare commits

...
Author SHA1 Message Date
John Colvin 511aeaf648 check pt: fix deribit_emumba build 2025-01-12 19:35:13 +00:00
John Colvin a37c5b396a fix deribit_emumba build 2025-01-12 15:20:34 +00:00
John Colvin 039e836acc check pt: fix emumba deribit build
It works if you build --target deribit_emumba first.
2025-01-11 17:24:02 +00:00
John Colvin 7dee64ea70 refactor: Split MarketDataGateway up into providers/sources
MarketDataGateway still exists, but is greatly simplified. It creates
several `IMarketDataProvider`s, each of which generates a list of
`IMarketDataSource`s. The market data sources provide market data to an
`IMarketDataListener`.

* EmumbaMarketDataProvider
* CCApiMarketDataProvider
* PyMarketDataProvider
* OnixsMarketDataProvider
* ReplayMarketDataProvider

As a side effect, individual py market data sources are now explicitly
limited to a single venue (it probably should have been this way all
along, since the shared memory writers do not expect updates to involve
multiple venues).
2025-01-10 20:01:44 +00:00
John Colvin c30e23406c fix build: Trivial changes to fix emumba_deribit build
* Use "Unix Makefiles" generator when building websocket-library.
  - Ninja tries to build things in parallel, and doesn't build the
    ExternalProject dependencies in the right order. Consequently, the
    websocket-library build fails because it can't find "libspdlog.a".

* Replace a few leftover SPDLOG_ERROR macros w/ LOG_ERROR (the latter
  logs in our system, not spdlog).
2025-01-10 20:01:44 +00:00
Mohammad Arham 836f22597d scafolding 2025-01-10 20:01:44 +00:00
John Colvin 38c1ebcf01 cleanup: Remove erroneous include 2025-01-10 20:01:44 +00:00
127 changed files with 11952 additions and 876 deletions

No files matched your search

+9 -1
View File
@@ -34,6 +34,7 @@ else()
set(BUILD_ADMIN_WEB_APP ON CACHE BOOL "Build the Admin WebApp")
endif()
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_SOURCE_DIR}/cmake_modules)
include_directories(BEFORE SYSTEM ${SPARK_INCLUDE} ${EXTERNALS_ROOT}/include/zlib)
link_directories(${SPARK_LIB})
@@ -116,11 +117,18 @@ else()
# subdirs
if (BUILD_SPARKD)
# deribit_emumba deps
add_subdirectory(third_party/lib_flat_hash_map)
add_subdirectory(third_party/lib_spdlog)
add_subdirectory(src/lib)
add_subdirectory(src/lib_binance_execution)
add_subdirectory(src/lib_deribit_emumba)
add_subdirectory(src/server/sparkd)
add_subdirectory(src/data_files)
else()
add_subdirectory(src/lib)
endif()
add_subdirectory(src/lib)
if (BUILD_ADMIN_WEB_APP)
add_subdirectory(src/Spark.Admin.WebApp)
endif()
+34
View File
@@ -0,0 +1,34 @@
include_guard(GLOBAL)
include(${CMAKE_SOURCE_DIR}/cmake/SetFirstNonEmpty.cmake)
function(git_version_tag)
set(_options)
set(_one_value_args RESULT DEFAULT)
set(_multi_value_args)
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
set(_result "${_ARGS_RESULT}")
if (_result STREQUAL "")
foreach(_arg ${ARGN})
set(_result ${_arg})
break()
endforeach()
if (_result STREQUAL "")
set(_result "${PROJECT_NAME}_VERSION")
endif()
endif()
find_package(Git QUIET)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags --always --dirty
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE ${_result}
OUTPUT_STRIP_TRAILING_WHITESPACE)
endif()
if("${${_result}}" STREQUAL "")
set_first_non_empty(${_result} "${_ARGS_DEFAULT}" "0.0.0")
endif()
set(${_result} "${${_result}}" PARENT_SCOPE)
message("GIT VERSION: ${_result} = ${${_result}}")
endfunction()
+61
View File
@@ -0,0 +1,61 @@
include_guard(DIRECTORY)
include(GNUInstallDirs)
include(${CMAKE_SOURCE_DIR}/cmake/PackBasic.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/SetFirstDefined.cmake)
function(install_basic)
set(_options)
set(_one_value_args NAME INCLUDE_DIR BUILD_INCLUDE_DIR VERSION)
set(_multi_value_args)
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
set_first_defined(_name _ARGS_NAME PROJECT_NAME)
set_first_defined(_include_dir _ARGS_INCLUDE_DIR _name)
set_first_defined(_build_include_dir _ARGS_BUILD_INCLUDE_DIR _name)
set(_base_dirs)
# Look for headers in source dir and build dir
set(_headers)
if (_include_dir)
list(APPEND _base_dirs "${CMAKE_CURRENT_SOURCE_DIR}")
file(
GLOB_RECURSE _headers
${CMAKE_CURRENT_SOURCE_DIR}/${_include_dir}/*.h
${CMAKE_CURRENT_SOURCE_DIR}/${_include_dir}/*.hpp
)
endif()
set(_build_headers)
if(_build_include_dir)
list(APPEND _base_dirs "${CMAKE_CURRENT_BINARY_DIR}")
file(
GLOB_RECURSE _build_headers
${CMAKE_CURRENT_BINARY_DIR}/${_build_include_dir}/*.h
${CMAKE_CURRENT_BINARY_DIR}/${_build_include_dir}/*.hpp
)
endif()
target_sources(
${_name}
PUBLIC
FILE_SET HEADERS
BASE_DIRS ${_base_dirs}
FILES
${_headers}
${_build_headers}
)
install(
TARGETS ${_name}
EXPORT ${_name}Targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(
EXPORT ${_name}Targets
FILE ${_name}Targets.cmake
NAMESPACE ${_name}::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${_name})
pack_basic(NAME ${_name} VERSION "${_ARGS_VERSION}")
endfunction()
+34
View File
@@ -0,0 +1,34 @@
include_guard(DIRECTORY)
include(CMakePackageConfigHelpers)
include(${CMAKE_SOURCE_DIR}/cmake/GitVersionTag.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/SetFirstDefined.cmake)
function(pack_basic)
set(_options)
set(_one_value_args NAME VERSION)
set(_multi_value_args)
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
set_first_defined(_name _ARGS_NAME PROJECT_NAME)
set(_version "${_ARGS_VERSION}")
if (NOT _ARGS_VERSION)
git_version_tag(_version)
endif()
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${_name}ConfigVersion.cmake"
VERSION ${_version}
COMPATIBILITY AnyNewerVersion
)
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/${_name}Config.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${_name}
)
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/${_name}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${_name}ConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${_name})
endfunction()
+10
View File
@@ -0,0 +1,10 @@
include_guard(GLOBAL)
macro(set_first_defined RESULT)
foreach(_arg ${ARGN})
if (DEFINED ${_arg})
set(${RESULT} ${${_arg}})
break()
endif()
endforeach()
endmacro()
+10
View File
@@ -0,0 +1,10 @@
include_guard(GLOBAL)
macro(set_first_non_empty RESULT)
foreach(_arg ${ARGN})
if (NOT "${_arg}" STREQUAL "")
set(${RESULT} ${_arg})
break()
endif()
endforeach()
endmacro()
+14
View File
@@ -0,0 +1,14 @@
include(ExternalProject)
if(NOT TARGET flathashmap_project)
ExternalProject_Add(flathashmap_project
GIT_REPOSITORY https://github.com/skarupke/flat_hash_map.git
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/flathashmap
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
)
endif()
ExternalProject_Get_Property(flathashmap_project SOURCE_DIR)
set(flathashmap_INCLUDE_DIR ${SOURCE_DIR})
@@ -0,0 +1,35 @@
include(ExternalProject)
include(GNUInstallDirs)
if(NOT TARGET websocket-library_project)
ExternalProject_Add(
websocket-library_project
GIT_REPOSITORY
https://fintech.websocket:h_ubh-4dsWRB569wwKyg@gitlab.com/cryptoexchanges1/websocket-client-using-epoll.git
GIT_TAG enhancement/config-ssl
CMAKE_GENERATOR "Unix Makefiles"
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
-DCMAKE_BUILD_TYPE=Release
-DLOG_LEVEL=INFO
-DENABLE_ENCRYPTION=True
BUILD_BYPRODUCTS
"${CMAKE_CURRENT_BINARY_DIR}/websocket-library_project-prefix/src/websocket-library_project-build/src/libemumba-websocket-library.a"
)
endif()
ExternalProject_Get_Property(websocket-library_project BINARY_DIR)
ExternalProject_Get_Property(websocket-library_project SOURCE_DIR)
set(websocket-library_INCLUDE_DIR ${SOURCE_DIR}/include)
set(websocket-library_LIB_DIR ${BINARY_DIR}/src)
add_library(websocket-library STATIC IMPORTED)
set_property(
TARGET websocket-library
PROPERTY IMPORTED_LOCATION
${websocket-library_LIB_DIR}/libemumba-websocket-library.a
)
add_dependencies(websocket-library websocket-library_project)
install(
FILES ${websocket-library_LIB_DIR}/libemumba-websocket-library.a
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#define BTS_LOCK_GUARD(lockable) \
std::lock_guard<decltype(lockable)> __lock_guard { lockable }
#define BTS_LOCK_GUARD_AS(lockable, name) \
std::lock_guard<decltype(lockable)> __lock_guard_##name { lockable }
+13 -10
View File
@@ -281,8 +281,7 @@ struct GeneralMarketDataListener::PImpl {
const bool use_given_sec_key = false;
const std::string cc_name_prefix = "cc_security";
size_t n_bk_updates = 0;
bts::persistence::IDGenerator &id_gen =
bts::persistence::IDGenerator::instance();
bts::persistence::IDGenerator *id_gen;
GeneralSecurityIndex &index;
MDProducer &md_producer;
const std::shared_ptr<bts::orderman::SimExecutionGateway> sim_ex_gwy =
@@ -300,10 +299,10 @@ struct GeneralMarketDataListener::PImpl {
api::domain::Venue venue,
GeneralSecurityIndex &index,
MDProducer &md_producer,
std::shared_ptr<bts::orderman::SimExecutionGateway> sim_ex_gwy)
: venue(venue), index(index),
md_producer(md_producer),
sim_ex_gwy(std::move(sim_ex_gwy)) {
std::shared_ptr<bts::orderman::SimExecutionGateway> sim_ex_gwy,
bts::persistence::IDGenerator *sec_id_store)
: venue(venue), id_gen(sec_id_store), index(index),
md_producer(md_producer), sim_ex_gwy(std::move(sim_ex_gwy)) {
if (this->sim_ex_gwy != nullptr) {
this->sim_ex_gwy->set_producers(&this->index);
}
@@ -365,13 +364,17 @@ void copy_books(
// interface functions ////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
GeneralMarketDataListener::GeneralMarketDataListener(
GeneralSecurityIndex &index, MDProducer &md_producer, api::Venue venue)
GeneralSecurityIndex &index,
MDProducer &md_producer,
api::Venue venue,
persistence::IDGenerator *sec_id_store)
: me(std::make_unique<PImpl>(
domain::DomainSecurityUtils::convert(venue),
index,
md_producer,
bts::orderman::SimExecutionGatewayFactory::get()->get_getayeway(
venue))) {
venue),
sec_id_store)) {
LOG(md, Info,
"GeneralMarketDataListener::GeneralMarketDataListener venue: {}",
bts::api::Venue_Name(venue));
@@ -508,7 +511,7 @@ CCSecSearchResult GeneralMarketDataListener::_find_sec(
if (add_if_absent) {
uint64_t bts_id = cc_id.value();
if (!me->use_given_sec_key) {
bts_id = me->id_gen.get_or_add(me->venue, sec.type(), sec.desc());
bts_id = me->id_gen->get_or_add(me->venue, sec.type(), sec.desc());
}
me->cc_sec_id_map.insert({cc_id, {bts_id}});
LOG(md, Info,
@@ -655,7 +658,7 @@ uint64_t GeneralMarketDataListener::_generate_security_key(
auto logical_key = get_logical_key(sec);
// if (me->use_given_sec_key)
// return sec.security_common.id.value();
return me->id_gen.get_or_add(me->venue, sec.type(), sec.desc());
return me->id_gen->get_or_add(me->venue, sec.type(), sec.desc());
}
void GeneralMarketDataListener::_possibly_match_with_sim_orders(
+5 -1
View File
@@ -5,6 +5,7 @@
#include "lib/md/GeneralSecurityIndex.h"
#include "lib/md/IMarketDataListener.h"
#include "lib/md/Shm/FixedSecurity.h"
#include "lib/persistence/IDGenerator.h"
namespace bts::md {
class MDProducer;
@@ -16,7 +17,10 @@ namespace bts::md {
class GeneralMarketDataListener : public IMarketDataListener {
public:
GeneralMarketDataListener(
GeneralSecurityIndex &index, MDProducer &md_producer, api::Venue venue);
GeneralSecurityIndex &index,
MDProducer &md_producer,
api::Venue venue,
persistence::IDGenerator *sec_id_store);
virtual ~GeneralMarketDataListener();
void on_securities(const std::vector<domain::Security const *>
&complete_security_list) override;
@@ -22,7 +22,6 @@
#include "lib/utils/bts_assert.h"
#include "lib/utils/bts_chrono.h"
#include "lib/utils/helper_macros.h"
#include "server/sparkd/md/platforms/emumba/IEMOrderMuxer.h"
#include <atomic>
#include <boost/pool/object_pool.hpp>
@@ -55,7 +54,6 @@ struct FakeExecutionGateway::PImpl {
std::vector<OrderMsg> buffered_msgs = {};
unsigned long exchange_oid = 0;
std::vector<ExecutionReport> exe_reports = {};
IEMOrderMuxer *_order_mux = nullptr;
};
FakeExecutionGateway::FakeExecutionGateway(md::SecurityOracle o)
@@ -196,4 +194,4 @@ OnExchangeOpenOrdersState &FakeExecutionGateway::on_exchange_open_orders() {
}
} // namespace orderman
} // namespace bts
} // namespace bts
@@ -31,7 +31,6 @@
#include "lib/utils/bts_chrono.h"
#include "lib/utils/helper_macros.h"
#include "lib/utils/thread_manager.h"
#include "server/sparkd/md/platforms/emumba/IEMOrderMuxer.h"
#include <bits/stdint-uintn.h>
#include <boost/pool/object_pool.hpp>
@@ -30,7 +30,6 @@
#include "lib/utils/PBUtils.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/bts_chrono.h"
#include "server/sparkd/md/platforms/emumba/EmMDListener.h"
#include <algorithm>
#include <bits/stdint-uintn.h>
#include <optional>
+31
View File
@@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.23)
project(deribit_emumba)
include(FetchContent)
file(
GLOB_RECURSE ${PROJECT_NAME}_SOURCES
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
*.cpp)
add_library(${PROJECT_NAME} STATIC ${${PROJECT_NAME}_SOURCES})
# Websocket
find_package(websocket-library REQUIRED)
target_include_directories(${PROJECT_NAME} PUBLIC ${websocket-library_INCLUDE_DIR})
target_link_directories(${PROJECT_NAME} PUBLIC ${websocket-library_LIB_DIR})
add_dependencies(${PROJECT_NAME} websocket-library)
target_link_libraries(${PROJECT_NAME} PUBLIC websocket-library)
target_include_directories(
${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(
${PROJECT_NAME}
PUBLIC
libspark
flat_hash_map
spdlog
)
@@ -0,0 +1,19 @@
/**
* @file common.h
* @brief Provides user the ease to include both the market_data_common and order_entry_common header files using a single
* header file.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*/
#include "market_data_common.h"
#include "order_entry_common.h"
@@ -0,0 +1,45 @@
#include "deribit_exception.h"
#include <iostream>
using namespace emumba::deribit_connector;
std::string deribit_exception::errors_to_string(deribit_exception::error_code error) const noexcept
{
switch (error)
{
case deribit_exception::error_code::uri_failure:
return "URI for connection is incorrect";
case deribit_exception::error_code::authentication_failure:
return "Authentication credentials are incorrect";
case deribit_exception::error_code::thread_binding:
return "Thread binding to specific core failed. Check configuration";
case deribit_exception::error_code::connection_error:
return "Cannot connect to websocket";
case deribit_exception::error_code::no_instruments:
return "No instruments selected. Check configuration";
default:
return "Incorrect error code.";
}
}
deribit_exception::deribit_exception(deribit_exception::error_code error_code)
: _error_code(error_code)
{}
deribit_exception::~deribit_exception() noexcept
{}
deribit_exception::error_code deribit_exception::get_error() const noexcept
{
return _error_code;
}
void deribit_exception::print() const noexcept
{
std::cout << "connector.deribit.exception." << (int)_error_code << ": " << errors_to_string(_error_code) << std::endl;
}
@@ -0,0 +1,75 @@
/**
* @file deribit_exception.h
* @brief Provides a class for exception handling.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*/
#ifndef DERIBIT_EXCEPTION_H
#define DERIBIT_EXCEPTION_H
#include <exception>
#include <string>
namespace emumba {
namespace deribit_connector {
/**
* @brief Provides exception handling for the connector.
*
*/
class deribit_exception : public std::exception
{
public:
/**
* @brief enum for exception handling.
*
*/
enum class error_code : int
{
uri_failure, /**< Value = 0. URI for connection is incorrect. */
authentication_failure, /**< Value = 1. Authentication credentials are incorrect. */
thread_binding, /**< Value = 2. Thread binding to specific core failed. */
connection_error, /**< Value = 3. Cannot connect to websocket. */
no_instruments /**< Value = 4. No instruments selected. */
};
explicit deribit_exception(error_code error_code);
~deribit_exception() noexcept;
/**
* @brief Returns an error code corresponding to an exception.
*
* @return error_code
*/
error_code get_error() const noexcept;
/**
* @brief Prints the error message on console.
*
*/
void print() const noexcept;
private:
/**
* @brief Return error_message against an error_code.
*
* @param error An error code corresponding to an exception.
* @return std::string
*/
std::string errors_to_string(error_code error) const noexcept;
protected:
error_code _error_code;
};
} // namespace deribit_connector
} // namespace emumba
#endif
@@ -0,0 +1,162 @@
#include "deribit_handler.h"
#include "deribit_exception.h"
#include "orders_handler.h"
#include "session_manager.h"
#include "spdlogger.h"
#include "symbol_handler.h"
#include "trader_session.h"
using namespace emumba::deribit_connector;
#ifdef LICENSE_CHECK
#include "license_validator.h"
license_validator license_obj;
#endif
std::atomic<int> print_books = 0;
deribit_handler::deribit_handler(
std::list<configuration::symbol_config> symbol_config_list, const configuration::trading_session_config &trading_config,
const std::map<uint, emumba::deribit_connector::configuration::credential_config> &cred_config_list,
const configuration::logging_config &log_config, const std::string deribit_uri, deribit_listener *trad_listener)
: _orders_handler(std::make_shared<orders_handler>(trading_config.orders_tracker, trad_listener))
{
// (void)log_config;
emumba::logger::init_spdlog(log_config.severity, log_config.is_console_enabled, log_config.is_file_enabled);
#ifdef LICENSE_CHECK
license_obj.check_license();
#endif
#ifdef CONNECTOR_VERSION
LOG_INFO("Deribit connector version: {}", CONNECTOR_VERSION);
#else
LOG_WARNING("Deribit connector version not defined");
#endif
_trad_listener = trad_listener;
_sess_manager = std::make_shared<session_manager>(deribit_uri, trading_config);
for (auto _symbol : symbol_config_list)
{
auto cred_config = cred_config_list.find(_symbol.cred_index);
if (cred_config != cred_config_list.end())
{
auto symbol_handl = std::make_shared<symbol_handler>(
_symbol, _sess_manager, _orders_handler, trading_config.heartbeat_interval, _trad_listener, cred_config->second);
_symbol_session_list.push_back(symbol_handl);
}
else
{
LOG_ERROR("Invalid Credential index");
exit(-1);
}
}
}
trading_session_interface deribit_handler::create_new_trading_session(
const emumba::deribit_connector::configuration::credential_config &credential_config)
{
std::vector<std::string> configured_symbols;
for (const auto &each_symbol : _symbol_session_list)
{
configured_symbols.push_back(each_symbol->get_symbol_name());
}
auto _trad = std::make_shared<trader_session>(_sess_manager, _orders_handler, credential_config, configured_symbols,
_trad_listener);
_trading_session_list.push_back(_trad);
trading_session_interface tr_api(_trad, _orders_handler);
return tr_api;
}
void deribit_handler::run(const bool &detach)
{
_connector_thread = std::thread(std::bind(&emumba::deribit_connector::session_manager::run, _sess_manager.get()));
pthread_setname_np(_connector_thread.native_handle(), "deribit_connector");
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(_sess_manager->get_core_id(), &cpuset);
int rc = pthread_setaffinity_np(_connector_thread.native_handle(), sizeof(cpu_set_t), &cpuset);
if (rc != 0)
{
std::cerr << "Error calling pthread_setaffinity_np: " << rc << "\n";
throw deribit_exception(deribit_exception::error_code::thread_binding);
}
if (detach == false)
{
_connector_thread.join();
}
}
void emumba::deribit_connector::dump_books()
{
print_books.store(1);
}
void deribit_handler::reconfigure_instrument_list(const std::string & symbol_name,
const std::list<std::string> instrument_list, const bool &append)
{
for (auto &symbol : _symbol_session_list)
{
if (symbol->get_symbol_name() == symbol_name)
{
symbol->reconfigure_instrument_list(instrument_list, append);
}
}
}
const std::string &deribit_handler::get_uuid() const
{
return _orders_handler->get_uuid();
}
const uint deribit_handler::get_session_id(const std::string &symbol_name) const
{
uint session_id = 0;
for (auto &symbol : _symbol_session_list)
{
if (symbol->get_symbol_name() == symbol_name)
{
session_id = symbol->get_session_id();
}
}
if (session_id == 0)
{
LOG_ERROR("get_session_id | symbol_name | Incorrect value.");
}
return session_id;
}
std::list<std::string> deribit_handler::get_future_expiries(const std::string &currency_symbol, const int &expiries,
const std::string &expiry_cycle)
{
std::list<std::string> future_list;
for (auto &symbol : _symbol_session_list)
{
if (symbol->get_symbol_name() == currency_symbol)
{
future_list = symbol->get_future_expiries(currency_symbol, expiries, expiry_cycle);
}
}
return future_list;
}
std::list<std::string> deribit_handler::get_option_expiries(const std::string &currency_symbol, const int &expiries,
const std::string &expiry_cycle)
{
std::list<std::string> options_list;
for (auto &symbol : _symbol_session_list)
{
if (symbol->get_symbol_name() == currency_symbol)
{
options_list = symbol->get_options_expiries(currency_symbol, expiries, expiry_cycle);
}
}
return options_list;
}
@@ -0,0 +1,138 @@
/**
* @file deribit_handler.h
* @brief Provides a handler class used to create and maintain multiple trading sessions with the Deribit exchange.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*/
#ifndef DERIBIT_HANDLER_H
#define DERIBIT_HANDLER_H
#include "deribit_listener.h"
#include "deribit_utils.h"
#include "trading_session_interface.h"
#include <list>
#include <memory>
#include <thread>
namespace emumba {
namespace deribit_connector {
class session_manager;
class symbol_handler;
class trader_session;
class orders_handler;
/**
* @brief Creates and maintains multiple trading sessions with the Deribit exchange.
*
*/
class deribit_handler
{
public:
/**
* @brief Construct a new deribit handler object.
*
* @param symbol_config_list List of "symbol_config" objects.
* @param trading_config "trading_session_config" object required to setup a trading session.
* @param cred_config_list list of credential_config objects.
* @param log_config "logging_config" object used to configure the logging.
* @param deribit_uri uri used to connect to the exchange.
* @param trad_listener Pointer to a "deribit_listener" derived object containing registered callbacks.
*/
deribit_handler(std::list<configuration::symbol_config> symbol_config_list,
const configuration::trading_session_config & trading_config,
const std::map<uint, emumba::deribit_connector::configuration::credential_config> &cred_config_list,
const configuration::logging_config &log_config, const std::string deribit_uri,
deribit_listener *trad_listener);
/**
* @brief Creates a new trading session and returns the corresponding trading interface used to place orders.
* @param cred_config credential_config structure containing the login credential required for trading.
* @return trading_session_interface
*/
trading_session_interface create_new_trading_session(
const emumba::deribit_connector::configuration::credential_config &credential_config);
/**
* @brief Reconfigure the instrument list for subscriptions
*
* @param symbol Symbol for instruments | Must be specified initially through config.json otherwise function won't work
* @param instrument_list List of instruments to be subscribed
* @param append Set to true if you want to append the previous list
* @return void
*/
void reconfigure_instrument_list(const std::string &symbol, const std::list<std::string> instrument_list,
const bool &append = false);
/**
* @brief Get the uuid object used by the connector to track orders.
*
* @return const std::string& uuid
*/
const std::string &get_uuid() const;
/**
* @brief Get session id of market data session. Returns 0 incase incorrect symbol_name is given.
*
* @param symbol_name symbol e.g BTC, ETH etc.
* @return const uint& session id of market data session.
*/
const uint get_session_id(const std::string &symbol_name) const;
/**
* @brief Get Future Expiries for an underlying instrument | Will be valid only after get_instruments callback in
* deribit_listener
*
* @param currency_symbol Base currency
* @param expiries Number of expiries
* @param expiry_cycle expiry cycle, defaults to week
* @return std::list<std::string>
*/
std::list<std::string> get_future_expiries(const std::string &currency_symbol, const int &expiries,
const std::string &expiry_cycle = "week");
/**
* @brief Get Future Expiries for an underlying instrument | Will be valid only after get_instruments callback in
* deribit_listener
*
* @param currency_symbol Base currency
* @param expiries Number of expiries
* @param expiry_cycle expiry cycle, defaults to week
* @return std::list<std::string>
*/
std::list<std::string> get_option_expiries(const std::string &currency_symbol, const int &expiries,
const std::string &expiry_cycle = "week");
/**
* @brief Runs the deribit connector in a thread binded to the specified core and joins it.
*
* @param detach Boolean flag. If set to true, the connector thread will run in detached mode. Default value is false.
*/
void run(const bool &detach = false);
private:
std::shared_ptr<session_manager> _sess_manager;
std::list<std::shared_ptr<symbol_handler>> _symbol_session_list;
std::list<std::shared_ptr<trader_session>> _trading_session_list;
deribit_listener * _trad_listener;
std::shared_ptr<orders_handler> _orders_handler;
std::thread _connector_thread;
};
/**
* @brief Prints the books of all configured instruments of a symbol into a file.
*
*/
void dump_books();
} // namespace deribit_connector
} // namespace emumba
#endif
@@ -0,0 +1,146 @@
#include "deribit_listener.h"
void emumba::deribit_connector::deribit_listener::on_order_update(__attribute__((unused))
const emumba::order_entry::order_key &order_key,
__attribute__((unused))
const emumba::order_entry::order_state &order_state)
{}
void emumba::deribit_connector::deribit_listener::on_get_all_positions(
__attribute__((unused)) const emumba::deribit_connector::data_types::error & ec,
__attribute__((unused)) const std::vector<emumba::order_entry::position const *> &positions)
{}
void emumba::deribit_connector::deribit_listener::on_get_all_open_orders(
__attribute__((unused)) const emumba::deribit_connector::data_types::error & ec,
__attribute__((unused)) const std::vector<emumba::order_entry::open_order const *> &open_orders)
{}
void emumba::deribit_connector::deribit_listener::on_get_user_trades(
__attribute__((unused)) const emumba::order_entry::user_trades_response &usr_trades_response)
{}
void emumba::deribit_connector::deribit_listener::on_mass_cancellation(
__attribute__((unused)) const emumba::deribit_connector::data_types::cancellation_update &order_cancellation)
{}
void emumba::deribit_connector::deribit_listener::on_account_summary(__attribute__((unused))
const emumba::deribit_connector::data_types::error &ec,
__attribute__((unused)) const std::string &msg_result)
{}
void emumba::deribit_connector::deribit_listener::on_cancel_all_positions(
__attribute__((unused)) const emumba::deribit_connector::data_types::error &ec,
__attribute__((unused)) const std::string & msg_result)
{}
void emumba::deribit_connector::deribit_listener::on_get_position(__attribute__((unused))
const emumba::deribit_connector::data_types::error &ec,
__attribute__((unused)) const std::string &msg_result)
{}
void emumba::deribit_connector::deribit_listener::on_get_open_orders(__attribute__((unused))
const emumba::deribit_connector::data_types::error &ec,
__attribute__((unused)) const std::string &msg_result)
{}
void emumba::deribit_connector::deribit_listener::on_get_mmp_config(__attribute__((unused))
const emumba::deribit_connector::data_types::error &ec,
__attribute__((unused)) const std::string &msg_result)
{}
void emumba::deribit_connector::deribit_listener::on_set_mmp_config(__attribute__((unused))
const emumba::deribit_connector::data_types::error &ec,
__attribute__((unused)) const std::string &msg_result)
{}
void emumba::deribit_connector::deribit_listener::on_reset_mmp(__attribute__((unused))
const emumba::deribit_connector::data_types::error &ec,
__attribute__((unused)) const std::string &msg_result)
{}
void emumba::deribit_connector::deribit_listener::on_book_state_change(__attribute__((unused))
const std::string &instrument_name,
__attribute__((unused))
const data_types::book_status & status_of_book,
__attribute__((unused)) const void *ptr)
{}
void emumba::deribit_connector::deribit_listener::on_quote(__attribute__((unused)) const emumba::market_data::quote &quote,
__attribute__((unused)) const void * ptr)
{}
void emumba::deribit_connector::deribit_listener::on_security_statistics(
__attribute__((unused)) const emumba::market_data::market_data_statistics &market_data_stats,
__attribute__((unused)) const void * ptr)
{}
void emumba::deribit_connector::deribit_listener::on_subscription_public_markprice(__attribute__((unused))
const std::string &symbol_name,
__attribute__((unused))
const std::string &markprice_message)
{}
void emumba::deribit_connector::deribit_listener::on_subscription_public_index(__attribute__((unused))
const std::string &symbol_name,
__attribute__((unused))
const std::string &index_message)
{}
void emumba::deribit_connector::deribit_listener::on_subscription_private_user_portfolio(
__attribute__((unused)) const std::string &symbol_name, __attribute__((unused)) const std::string &user_portfolio_message)
{}
void emumba::deribit_connector::deribit_listener::on_response_get_instruments(
__attribute__((unused)) const emumba::deribit_connector::data_types::error &ec,
__attribute__((unused)) const std::string & symbol_name,
__attribute__((unused)) const std::string & get_instruments_message)
{}
void emumba::deribit_connector::deribit_listener::on_securities(
__attribute__((unused)) const std::vector<emumba::market_data::security const *> &complete_security_list)
{}
void emumba::deribit_connector::deribit_listener::on_book(__attribute__((unused))
const emumba::market_data::order_book &book,
__attribute__((unused)) const void * ptr)
{}
void emumba::deribit_connector::deribit_listener::on_security_state(__attribute__((unused))
const emumba::market_data::security_state &sec_state,
__attribute__((unused)) const void * ptr)
{}
void emumba::deribit_connector::deribit_listener::on_trades(
__attribute__((unused)) const std::vector<emumba::market_data::trade_record const *> &trades,
__attribute__((unused)) const void * ptr)
{}
void emumba::deribit_connector::deribit_listener::on_private_trade(__attribute__((unused))
const emumba::order_entry::order_key &order_key,
__attribute__((unused))
const emumba::market_data::trade_record &trade)
{}
void emumba::deribit_connector::deribit_listener::on_error(__attribute__((unused))
const emumba::market_data::market_data_error &error)
{}
void emumba::deribit_connector::deribit_listener::on_disconnection(
__attribute__((unused)) const emumba::market_data::disconnected_session &disconnected_sess)
{}
void emumba::deribit_connector::deribit_listener::on_request_for_quote(
__attribute__((unused)) const emumba::order_entry::rfq_response &rfq_response)
{}
void emumba::deribit_connector::deribit_listener::on_create_combo(
__attribute__((unused)) const emumba::order_entry::combo_detail &combo_detail)
{}
#ifdef E2E_PERF
void emumba::deribit_connector::deribit_listener::e2e_perf_on_book(__attribute__((unused))
emumba::market_data::order_book & book,
__attribute__((unused)) const double &change_id)
{}
#endif
@@ -0,0 +1,308 @@
/**
* @file deribit_listener.h
* @brief Provides a class with the basic interface for callbacks. These callbacks are registered for various events which
* get triggered based on configured subscriptions or posted requests.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*/
#ifndef DERIBIT_LISTENER_H
#define DERIBIT_LISTENER_H
#include "common.h"
#include "deribit_utils.h"
#include "error.h"
#include "instrument_data_interface.h"
#include "price_level.h"
#include <string>
#include <vector>
namespace emumba {
namespace deribit_connector {
/**
* @brief Provides the basic interface for callbacks to be registered for various events that can get triggered
* based on configured subscriptions or posted requests.
*
*/
class deribit_listener
{
public:
// Order Entry Callbacks
/**
* @brief Handle response related to an order placed by the user. It is called whenever a response for a new order,
* edit order or cancel order is received.
*
* @param order_key Unique key assigned to order at the time of placing order.
* @param order_state Provide the current state of the order.
*/
virtual void on_order_update(const emumba::order_entry::order_key & order_key,
const emumba::order_entry::order_state &order_state);
/**
* @brief Handle respons of positions for a symbol.
*
* @param ec Error code.
* @param positions Vector of structures containing useful information related to positions.
*/
virtual void on_get_all_positions(const emumba::deribit_connector::data_types::error & ec,
const std::vector<emumba::order_entry::position const *> &positions);
/**
* @brief Handle respons of positions for a symbol.
*
* @param ec Error code.
* @param open_orders Vector of structures containing useful information related to open orders.
*/
virtual void on_get_all_open_orders(const emumba::deribit_connector::data_types::error & ec,
const std::vector<emumba::order_entry::open_order const *> &open_orders);
/**
* @brief Handle response of get user trades request.
*
* @param usr_trades_response Structure containing the response of the get user trades request.
*/
virtual void on_get_user_trades(const emumba::order_entry::user_trades_response &usr_trades_response);
/**
* @brief Handle Response for Cancel All Orders by Instrument and by currency Request.
*
* @param order_cancellation Structure containing information related to cancel orders.
*/
virtual void on_mass_cancellation(
const emumba::deribit_connector::data_types::cancellation_update &order_cancellation);
/**
* @brief Handle Response for Accounts Summary Request.
*
* @param ec Error code.
* @param msg_result Json formatted string for `result` field extracted from the response message
* of "/private/get_account_summary" request.
* @return void
*/
virtual void on_account_summary(const emumba::deribit_connector::data_types::error &ec, const std::string &msg_result);
/**
* @brief Handle Response for Cancel All Positions by Instrument Request.
*
* @param ec Error code.
* @param msg_result Json formatted string for `result` field extracted from the response message
* of "private/close_position" request for all positions against an instrument.
* @return void
*/
virtual void on_cancel_all_positions(const emumba::deribit_connector::data_types::error &ec,
const std::string & msg_result);
/**
* @brief Handle Response for Get Position for an instrument Request.
*
* @param ec Error code.
* @param msg_result Json formatted string for `result` field extracted from the response message
* of "private/get_position" request against an instrument.
* @return void
*/
virtual void on_get_position(const emumba::deribit_connector::data_types::error &ec, const std::string &msg_result);
/**
* @brief Handle Response for Get Open Orders for an instrument Request.
*
* @param ec Error code.
* @param msg_result Json formatted string for `result` field extracted from the response message
* of "private/get_open_orders_by_instrument" request against an instrument.
* @return void
*/
virtual void on_get_open_orders(const emumba::deribit_connector::data_types::error &ec, const std::string &msg_result);
/**
* @brief Handle Response for Get MMP Config for a currency
*
* @param ec Error code.
* @param msg_result Json formatted string for `result` field extracted from the response message
* of "private/get_mmp_config" request against a currency.
* @return void
*/
virtual void on_get_mmp_config(const emumba::deribit_connector::data_types::error &ec, const std::string &msg_result);
/**
* @brief Handle Response for Set MMP Config for a currency
*
* @param ec Error code.
* @param msg_result Json formatted string for `result` field extracted from the response message
* of "private/set_mmp_config" request for a currency.
* @return void
*/
virtual void on_set_mmp_config(const emumba::deribit_connector::data_types::error &ec, const std::string &msg_result);
/**
* @brief Handle Response for Reset MMP Config for a currency
*
* @param ec Error code.
* @param msg_result Json formatted string for `result` field extracted from the response message
* of "private/reset_mmp" request against a currency.
* @return void
*/
virtual void on_reset_mmp(const emumba::deribit_connector::data_types::error &ec, const std::string &msg_result);
// Market Data Callbacks
/**
* @brief Handle definition of Instrument.
*
* @param instrument_name Instrument name which has been configured and whose updates will be
* provided through callbacks.
* @return void* This function can return a pointer to a custom data structure made by user. The pointer will be
* returned in the callback as a parameter for all the updates that happen for this instrument.
* This can avoid searching of data structures on user's side if multiple instruments are configured.
*/
virtual void *on_instrument_definition(const std::string & instrument_name,
emumba::deribit_connector::instrument_data_interface *interface) = 0;
/**
* @brief Handle On Book State Change for an Instrument in case state of book changes. Trading should be done when
* book status is Green.
*
* @param instrument_name Instrument name.
* @param status_of_book Enum for book status corresponds to 0 for Red, 1 for Green.
* @param ptr User defined ptr to be returned on updates for a specific instrument. Defaults to null.
* @return void
*/
virtual void on_book_state_change(const std::string &instrument_name, const data_types::book_status &status_of_book,
const void *ptr = nullptr);
/**
* @brief Provide Quote Update Messages for an Instrument.
*
* @param quote Structure contianing quote info of an Instrument.
* @param ptr User defined ptr to be returned on updates for a specific instrument. Defaults to null.
* @return void
*/
virtual void on_quote(const emumba::market_data::quote &quote, const void *ptr = nullptr);
/**
* @brief Provide security statistics info for an Instrument.
*
* @param market_data_statistics Structure containing statistics information related to a specific instrument.
* @param ptr User defined ptr to be returned on updates for a specific instrument. Defaults to null.
* @return void
*/
virtual void on_security_statistics(const emumba::market_data::market_data_statistics &market_data_stats,
const void * ptr = nullptr);
/**
* @brief Handle Public MarkPrice Subscription Message for a Symbol.
*
* @param symbol_name Symbol name.
* @param markprice_message Json formatted string for one entry of `data` field extracted from the subscription message
* received against "markprice.options.{index_name}" subscription for a symbol.
* @return void
*/
virtual void on_subscription_public_markprice(const std::string &symbol_name, const std::string &markprice_message);
/**
* @brief Handle Public Index Subscription Message for a Symbol.
*
* @param symbol_name Symbol name.
* @param index_message Json formatted string for `data` field extracted from the subscription message
* received against "deribit_price_index.{index_name}" subscription for a symbol.
* @return void
*/
virtual void on_subscription_public_index(const std::string &symbol_name, const std::string &index_message);
/**
* @brief Handle User Portfolio Subscription Message.
*
* @param symbol_name Symbol name.
* @param index_message Json formatted string for `data` field extracted from the subscription message
* received against "user.portfolio.{currency}" subscription for a symbol.
* @return void
*/
virtual void on_subscription_private_user_portfolio(const std::string &symbol_name,
const std::string &user_portfolio_message);
/**
* @brief Handle Response Message for Get Instruments Request.
*
* @param ec Error code.
* @param symbol_name Symbol name.
* @param get_instruments_message Json formatted string for `result` field extracted from response message of
* "/public/get_instruments" request.
* @return void
*/
virtual void on_response_get_instruments(const emumba::deribit_connector::data_types::error &ec,
const std::string &symbol_name, const std::string &get_instruments_message);
/**
* @brief Provides user all instruments being traded on the exchange.
*
* @param complete_security_list Vector containing all instruments currently being traded on the exchange.
*/
virtual void on_securities(const std::vector<emumba::market_data::security const *> &complete_security_list);
/**
* @brief Handle updates in order book of an instrument.
*
* @param book Structure containing order book of an instrument.
* @param ptr User defined ptr to be returned on updates for a specific instrument. Defaults to null.
*/
virtual void on_book(const emumba::market_data::order_book &book, const void *ptr = nullptr);
/**
* @brief Handle change in the state of an instrument.
*
* @param sec_state State of an instrument.
*/
virtual void on_security_state(const emumba::market_data::security_state &sec_state, const void *ptr = nullptr);
/**
* @brief Handle trade updates for instruments.
*
* @param trades Vector of structures containing useful information related to trade updates of different instruments.
*/
virtual void on_trades(const std::vector<emumba::market_data::trade_record const *> &trades,
const void * ptr = nullptr);
/**
* @brief Handle trade updates received against an order placed by the user.
*
* @param order_key Unique key assigned to order at the time of placing order.
* @param trade Structure containing trade information related to a specific order placed by the user.
*/
virtual void on_private_trade(const emumba::order_entry::order_key & order_key,
const emumba::market_data::trade_record &trade);
/**
* @brief Handle different errors which can occur while the connector is running.
*
* @param error Structure containing the type of error which occured.
*/
virtual void on_error(const emumba::market_data::market_data_error &error);
/**
* @brief Handle disconnection of a session.
*
* @param disconnected_sess Structure containing information regarding session which has been disconnected.
*/
virtual void on_disconnection(const emumba::market_data::disconnected_session &disconnected_sess);
virtual void on_request_for_quote(__attribute__((unused)) const emumba::order_entry::rfq_response &rfq_response);
virtual void on_create_combo(__attribute__((unused)) const emumba::order_entry::combo_detail &combo_detail);
#ifdef E2E_PERF
virtual void e2e_perf_on_book(__attribute__((unused)) emumba::market_data::order_book &book,
__attribute__((unused)) const double & change_id);
#endif
};
} // namespace deribit_connector
} // namespace emumba
#endif
@@ -0,0 +1,35 @@
#include "deribit_utils.h"
emumba::deribit_connector::configuration::logging_config::logging_config()
{
severity = "info";
is_console_enabled = true;
is_file_enabled = true;
queue_size = 8192;
}
emumba::deribit_connector::configuration::trading_session_config::trading_session_config()
{
heartbeat_interval = 10;
cancel_on_disconnect = true;
}
emumba::deribit_connector::configuration::reconnection_config::reconnection_config()
{
retry_count = 3;
retry_interval = 1;
reception_timeout = 60;
}
emumba::deribit_connector::configuration::credential_config::credential_config()
{
client_name = "";
client_id = "";
secret_key = "";
index = 0;
}
emumba::deribit_connector::configuration::order_tracker::order_tracker()
{
uuid = "";
start_id = 0;
}
@@ -0,0 +1,333 @@
/**
* @file deribit_utils.h
* @brief Provides structures used in APIs by the Deribit connector.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*/
#ifndef DERIBIT_UTILS_H
#define DERIBIT_UTILS_H
#include "error.h"
#include <cstdint>
#include <unordered_set>
#define UNUSED_PARAM __attribute__((unused))
#define ALWAYS_INLINE inline __attribute__((always_inline))
#define UNUSED(x) (void)(x)
namespace emumba {
namespace deribit_connector {
namespace data_types {
/**
* @brief Determines the status of order book of an instrument.
*
*/
enum class book_status
{
red, /**< Value = 0. Instrument is not ready for trading. */
green /**< Value = 1. Instrument is ready for trading. */
};
/**
* @brief Determines the order cancellation request type.
*
*/
enum class cancellation_type
{
by_instrument, /**Value = 0. Orders cancellation by instrument*/
by_currency /**Value = 1. Orders cancellation by currency*/
};
/**
* @brief Structure containing information related to cancel orders.
*
*/
struct cancellation_update
{
cancellation_type type;
emumba::deribit_connector::data_types::error ec;
uint no_of_orders_cancelled;
uint subaccount_index;
};
} // namespace data_types
namespace configuration {
/**
* @brief Holds the subscription information of the trading client.
*
*/
struct subscription_config
{
/**
* @brief If true, subscribes to the "book.{instrument_name}.{interval}" for configured instruments.
*
*/
bool public_book = false;
/**
* @brief If true, subscribes to "quote.{instrument_name}" for configured instruments.
*
*/
bool public_quote = false;
/**
* @brief If true, subscribes to "trades.{instrument_name}.{interval}" for configured instruments.
*
*/
bool public_trade = false;
/**
* @brief If true, subscribes to "ticker.{instrument_name}.{interval}" for configured instruments.
*
*/
bool public_ticker = false;
/**
* @brief If true, subscribes to "markprice.options.{index_name}" for configured symbol.
*
*/
bool public_markprice = false;
/**
* @brief If true, subscribes to "deribit_price_index.{index_name}" for configured symbols.
*
*/
bool public_index = false;
/**
* @brief If true, subscribes to "user.portfolio.{currency}" for configured symbols.
*
*/
bool private_portfolio = false;
/**
* @brief If true, no aggregation will be applied on the notification of events.
*
*/
bool use_raw_interval = true;
/**
* @brief Configured depth for book updates.
*
*/
uint book_depth = 5;
/**
* @brief Max number of subscription requests that can be sent in succession.
*
*/
uint requests_limit = 20;
};
/**
* @brief Holds the configuration for the desired currency symbol.
*
*/
struct symbol_config
{
/**
* @brief Name of the currency symbol.
*
*/
std::string symbol_name;
/**
* @brief Currency type. Either "usd" or "usdt".
*
*/
std::string currency_type = "usd";
/**
* @brief Index of credential to use for subscription
*
*
*/
uint cred_index;
/**
* @brief List of unique instrument identifiers that the trading client is interested in.
*
*/
std::unordered_set<std::string> interested_instruments;
/**
* @brief Configuration of interested channels for subscription.
*
*/
subscription_config interested_subscriptions;
/**
* @brief Set true if interested in future instruments. All instruments will be of interest incase all
* intereseted_in_... flags are set to false.
*
*/
bool interested_in_future = false;
/**
* @brief Set true if interested in future combo instruments. All instruments will be of interest incase all
* intereseted_in_... flags are set to false.
*
*/
bool interested_in_future_combo = false;
/**
* @brief Set true if interested in option instruments. All instruments will be of interest incase all
* intereseted_in_... flags are set to false.
*
*/
bool interested_in_option = false;
/**
* @brief Set true if only interested in option combo instruments. All instruments will be of interest incase all
* intereseted_in_... flags are set to false.
*
*/
bool interested_in_option_combo = false;
};
/**
* @brief Holds the configuration used for logging.
*
*/
struct logging_config
{
/**
* @brief The level of logging i.e info, warning or error.
*
*/
std::string severity;
/**
* @brief If true, logs will be printed to the console.
*
*/
bool is_console_enabled;
/**
* @brief If true, logs will be printed to the log file.
*
*/
bool is_file_enabled;
/**
* @brief Sets the buffer size for async logging. Defaults to 8192.
*
*/
std::uint32_t queue_size;
logging_config();
};
/**
* @brief Holds the configuration for reconnection attempts in case the connection to Deribit server is lost.
* Initializing this struct through config file is optional. If not initialized, the struct fields will be
* assigned the following default values
*
* retry_count = 3;
* retry_interval = 1 second;
* reception_timeout = 60 seconds;
*/
struct reconnection_config
{
/**
* @brief Numbers of retries to reconnect to the server.
*
*/
int retry_count;
/**
* @brief Time to wait (in seconds) in between reconnection attempts.
*
*/
int retry_interval;
/**
* @brief Maximum time (in seconds) to wait for reception on a connected websocket session before re-connecting the
* session
*
*/
int reception_timeout;
reconnection_config();
};
/**
* @brief Holds uuid and internal order id used to track individual orders placed by an instance of the connector.
*
*/
struct order_tracker
{
/**
* @brief Unique identifier used as label in new order placement to identify the instance to which the order
* belongs.
*
*/
std::string uuid;
/**
* @brief Order id used internally by the connector to track each order individually.
*
*/
uint start_id;
order_tracker();
};
/**
* @brief Holds the configuration required to set up a trading session.
*
*/
struct trading_session_config
{
/**
* @brief Structure used to track orders placed by the connector.
*
*/
order_tracker orders_tracker;
/**
* @brief Binds the application to the user-specified processor core.
*
*/
uint core;
/**
* @brief The heartbeat interval in seconds. Can not be less than 10.
*
*/
int heartbeat_interval;
/**
* @brief Specifies if Cancel On Disconnect should be applied/checked for the current connection.
*
*/
bool cancel_on_disconnect; // TODO: remove this flag. This is no longer configurable by user.
/**
* @brief structure containing the settings related to reconnection i.e. retry_count, retry_interval and
* reception_timeout.
*
*/
reconnection_config reconnect_config;
trading_session_config();
};
struct credential_config
{
/**
* @brief Name of the client's trading software.
*/
std::string client_name;
/**
* @brief Client identifier used for authentication.
*/
std::string client_id;
/**
* @brief Client secret used for authentication.
*
*/
std::string secret_key;
/**
* @brief Credential config id
*
*/
uint index;
credential_config();
};
} // namespace configuration
} // namespace deribit_connector
} // namespace emumba
#endif /* DERIBIT_UTILS_H */
@@ -0,0 +1,37 @@
#ifndef DOCUMENT_TO_STRING_H
#define DOCUMENT_TO_STRING_H
#include "rapidjson/document.h"
#include "rapidjson/istreamwrapper.h"
#include "rapidjson/ostreamwrapper.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
namespace emumba {
namespace deribit_connector {
class document_to_string
{
public:
document_to_string()
: _writer(_string_buffer)
{
_string_buffer.Reserve(1000);
}
template <typename T>
const std::string to_string(const T &doc)
{
_string_buffer.Clear();
_writer.Reset(_string_buffer);
doc.Accept(_writer);
return std::string(_string_buffer.GetString());
}
private:
rapidjson::StringBuffer _string_buffer;
rapidjson::Writer<rapidjson::StringBuffer> _writer;
};
} // namespace deribit_connector
} // namespace emumba
#endif
@@ -0,0 +1,24 @@
#ifndef EMUMBA_ENUM_H
#define EMUMBA_ENUM_H
#define _EMUMBA_ENUM_DECLARE_VALUE(t, n, x) x = n,
#define _EMUMBA_ENUM_CONCAT(x, y) x##y
#define _EMUMBA_ENUM_NAME_CASE(type, n, value) \
case type::value: \
return #value;
// clang-format off
#define EMUMBA_ENUM(type, for_all) \
enum class type { for_all(_EMUMBA_ENUM_DECLARE_VALUE) }; \
inline const char *_EMUMBA_ENUM_CONCAT(type, _name)(type value) { \
switch (value) { \
for_all(_EMUMBA_ENUM_NAME_CASE) default : return "ENUM_UNKNOWN"; \
} \
}
// clang-format on
// #undef _NAME_CASE
// #undef _IDENTITY
// #undef _DECLARE_VALUE
// #undef _CONCAT
#endif
@@ -0,0 +1,22 @@
#include "error.h"
emumba::deribit_connector::data_types::error::error(const int &err_code, const std::string &message,
const std::string &details)
: _code(err_code)
, _msg(message)
, _details(details)
{}
const int emumba::deribit_connector::data_types::error::value() const
{
return _code;
}
const std::string emumba::deribit_connector::data_types::error::message() const
{
return _msg;
}
const std::string emumba::deribit_connector::data_types::error::details() const
{
return _details;
}
@@ -0,0 +1,59 @@
/**
* @file error.h
* @brief Provides a class to represent occurrence of an error in incoming messages.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*/
#ifndef EMUMBA_DERIBIT_ERROR_H
#define EMUMBA_DERIBIT_ERROR_H
#include <string>
namespace emumba {
namespace deribit_connector {
namespace data_types {
/**
*
* @brief Represents error in incoming messages.
*/
class error
{
public:
error(const int &err_code = 0, const std::string &message = "Success", const std::string &details = "");
/**
* @brief Returns the error code for a message.
*
* @return const int
*/
const int value() const;
/**
* @brief Returns the error message.
*
* @return const std::string
*/
const std::string message() const;
/**
* @brief Returns the details for error message.
*
* @return const std::string
*/
const std::string details() const;
private:
int _code;
std::string _msg;
std::string _details;
};
} // namespace data_types
} // namespace deribit_connector
} // namespace emumba
#endif
@@ -0,0 +1,289 @@
#include "instrument_data.h"
#include <iomanip>
#include <iostream>
#include <memory>
using namespace emumba::deribit_connector;
using namespace std;
instrument_data::instrument_data(
const std::string &instrument_name,
const uint &book_depth,
instrument_info inst_info,
deribit_listener * /*trade_listener*/)
: _instrument_name(instrument_name), _previous_change_id(-1),
_lowest_possible_bid(std::numeric_limits<double>::lowest()),
_highest_possible_ask(std::numeric_limits<double>::max()),
_bid_price_at_depth(_lowest_possible_bid),
_ask_price_at_depth(_highest_possible_ask), _bid_map_size(0),
_ask_map_size(0), _book_depth(book_depth), _instrument_info(inst_info),
_is_inst_closed(false) {
_book_status = data_types::book_status::red;
_last_traded_price = 0;
_last_traded_quantity = 0;
_total_traded_quantity = 0;
_total_traded_volume = 0;
LOG_TRACE("Constructed instrument_data object");
_book.bids.level.reserve(_book_depth);
_book.asks.level.reserve(_book_depth);
_book.sec_id.value() = _instrument_name;
_current_sec_state = emumba::market_data::security_state{};
}
bool instrument_data::check_message_loss(const double &previous_change_id)
{
LOG_TRACE("Checking message loss. Message change id: {}, Expected changed id: {}", previous_change_id,
_previous_change_id);
return previous_change_id == _previous_change_id;
}
void instrument_data::reset_instrument()
{
change_book_status(data_types::book_status::red);
_bid.clear();
_ask.clear();
_bid_map_size = 0;
_ask_map_size = 0;
_last_traded_price = 0;
_last_traded_quantity = 0;
_bid_price_at_depth = _lowest_possible_bid;
_ask_price_at_depth = _highest_possible_ask;
LOG_DEBUG("Reset instrument: {}.", _instrument_name);
}
void *instrument_data::get_api_handler()
{
return _api_handler;
}
// Print levels of both books till assigned book depth
std::ostream &instrument_data::print_order_book(std::ostream &out, int level)
{
if (level == 0)
{
level = _book_depth;
}
int index = 0;
out << std::fixed << "Change ID: " << std::setprecision(0) << _previous_change_id << std::endl;
out << "Status: " << (_book_status == data_types::book_status::green ? "GREEN" : "RED") << std::endl;
out << "\nBid Book:" << std::endl;
out << "#,Price,Size" << std::endl;
auto _bid_itr = _bid.begin();
for (; index < level && _bid_itr != _bid.end();)
{
out << ++index << "," << std::setprecision(9) << _bid_itr->second.get_price().to_double();
out << "," << std::setprecision(9) << _bid_itr->second.get_amount() << std::endl;
_bid_itr++;
}
while (index < level)
{
out << ++index << "," << std::setprecision(1) << 0.0;
out << "," << std::setprecision(1) << 0.0 << std::endl;
}
out << std::endl;
out << "Ask Book:" << std::endl;
out << "#,Price,Size" << std::endl;
index = 0;
auto _ask_itr = _ask.begin();
for (; index < level && _ask_itr != _ask.end();)
{
out << ++index << "," << std::setprecision(9) << _ask_itr->second.get_price().to_double();
out << "," << std::setprecision(9) << _ask_itr->second.get_amount() << std::endl;
_ask_itr++;
}
while (index < level)
{
out << ++index << "," << std::setprecision(1) << 0.0;
out << "," << std::setprecision(1) << 0.0 << std::endl;
}
return out;
}
// Print all levels of both books
std::ostream &instrument_data::print_order_book_all_levels(std::ostream &out)
{
int index;
out << std::fixed << "Change ID: " << std::setprecision(0) << _previous_change_id << std::endl;
out << "Status: " << (_book_status == data_types::book_status::green ? "GREEN" : "RED") << std::endl;
out << "\nBid Book:" << std::endl;
out << "#,Price,Size" << std::endl;
index = 0;
for (auto &bid_price_level : _bid)
{
out << ++index << "," << std::setprecision(9) << bid_price_level.second.get_price().to_double();
out << "," << std::setprecision(9) << bid_price_level.second.get_amount() << std::endl;
}
out << std::endl;
out << "Ask Book:" << std::endl;
out << "#,Price,Size" << std::endl;
index = 0;
for (auto &ask_price_level : _ask)
{
out << ++index << "," << std::setprecision(9) << ask_price_level.second.get_price().to_double();
out << "," << std::setprecision(9) << ask_price_level.second.get_amount() << std::endl;
}
return out;
}
const double instrument_data::get_previous_change_id()
{
return _previous_change_id;
}
const uint64_t instrument_data::get_exchange_ts()
{
return _exchange_update_ts;
}
const std::string instrument_data::get_name()
{
return _instrument_name;
}
void instrument_data::update_trade_info(const double &ltp, const double &ltq)
{
_last_traded_price = ltp;
_last_traded_quantity = ltq;
_total_traded_quantity = _total_traded_quantity + ltq;
_total_traded_volume = _total_traded_volume + (_last_traded_price * _last_traded_quantity);
}
const double instrument_data::get_last_traded_price()
{
return _last_traded_price;
}
const double instrument_data::get_last_traded_quantity()
{
return _last_traded_quantity;
}
const double instrument_data::get_total_traded_quantity()
{
return _total_traded_quantity;
}
const long double instrument_data::get_total_traded_value()
{
return _total_traded_volume;
}
data_types::price_level::iterator instrument_data::get_top(int side)
{
if (side == 0)
{
return data_types::price_level::iterator(&_bid, 0);
}
else
{
return data_types::price_level::iterator(&_ask, 1);
}
}
data_types::price_level::iterator instrument_data::get_level(int side, unsigned lvl)
{
if (side == 0)
{
data_types::price_level::iterator itr(&_bid, 0);
for (uint i = 0; i < lvl; i++)
{
itr++;
}
return itr;
}
else
{
data_types::price_level::iterator itr(&_ask, 1);
for (uint i = 0; i < lvl; i++)
{
itr++;
}
return itr;
}
}
bool instrument_data::is_iterator_valid(data_types::price_level::iterator it)
{
if (it.side() == 0)
{
data_types::price_level::iterator bid_itr(&_bid, 0);
for (int i = 0; i < get_depth(0); i++)
{
if (bid_itr == it)
{
return true;
}
bid_itr++;
}
}
else if (it.side() == 1)
{
data_types::price_level::iterator ask_itr(&_ask, 1);
for (int i = 0; i < get_depth(1); i++)
{
if (ask_itr == it)
{
return true;
}
ask_itr++;
}
}
return false;
}
int instrument_data::get_depth(int side)
{
if (side == 0)
{
return _bid.size();
}
else if (side == 1)
{
return _ask.size();
}
else
{
return 0;
}
}
void instrument_data::set_instrument_data_interface(
std::shared_ptr<emumba::deribit_connector::instrument_data_interface> interface)
{
_interface = interface;
}
void instrument_data::set_api_handler(void *ref_ptr)
{
_api_handler = ref_ptr;
}
emumba::market_data::security_state::state instrument_data::get_current_state()
{
return _current_sec_state.state_;
}
void instrument_data::set_current_state(emumba::market_data::security_state::state new_state)
{
_current_sec_state.state_ = new_state;
}
void instrument_data::set_inst_closed_flag(const bool &instrument_closed)
{
_is_inst_closed = instrument_closed;
}
const bool &instrument_data::get_inst_closed_flag() const
{
return _is_inst_closed;
}
@@ -0,0 +1,290 @@
#ifndef INSTRUMENT_DATA_H
#define INSTRUMENT_DATA_H
#include "deribit_listener.h"
#include "deribit_utils.h"
#include "instrument_data_interface.h"
#include "market_data_common.h"
#include "price_level.h"
#include "spdlogger.h"
#include "utils.h"
#include <limits>
#include <map>
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include <rapidjson/ostreamwrapper.h>
#include <rapidjson/stringbuffer.h>
#include <vector>
namespace emumba {
namespace deribit_connector {
class instrument_data
{
public:
instrument_data(const std::string &instrument_name, const uint &book_depth, instrument_info inst_info,
deribit_listener *trade_listener);
ALWAYS_INLINE bool update_order_book(const rapidjson::Value &book_message)
{
std::string action;
double price;
double quantity;
_depth_levels_updated = false;
for (rapidjson::Value::ConstValueIterator bid_update_itr = book_message["data"]["bids"].Begin();
bid_update_itr != book_message["data"]["bids"].End(); ++bid_update_itr)
{
auto bid_update = bid_update_itr->GetArray();
action = bid_update[0].GetString();
price = bid_update[1].GetDouble();
quantity = bid_update[2].GetDouble();
LOG_TRACE("Bid Book Update | Action: {}, Price: {}, Quantity: {}", action, price, quantity);
if (price >= _bid_price_at_depth)
{
_depth_levels_updated = true;
}
if (action == "new")
{
if (__builtin_expect(_bid.insert(std::make_pair(price, data_types::price_level(
emumba::market_data::price(price), quantity)))
.second == false,
false))
{
LOG_ERROR("PriceLevel already exists. Could not insert new PriceLevel.");
return false;
}
++_bid_map_size;
}
else if (action == "change")
{
_iterator = _bid.find(price);
if (__builtin_expect(_iterator != _bid.end(), true))
{
_iterator->second.set_amount(quantity);
}
else
{
LOG_ERROR("PriceLevel not found.");
return false;
}
}
else if (action == "delete")
{
_iterator = _bid.find(price);
if (__builtin_expect(_iterator != _bid.end(), true))
{
_bid.erase(_iterator);
--_bid_map_size;
}
else
{
LOG_ERROR("PriceLevel not found.");
return false;
}
}
else
{
LOG_ERROR("Unknow Update Action Received: {}", action);
return false;
}
}
for (rapidjson::Value::ConstValueIterator ask_update_itr = book_message["data"]["asks"].Begin();
ask_update_itr != book_message["data"]["asks"].End(); ++ask_update_itr)
{
auto ask_update = ask_update_itr->GetArray();
action = ask_update[0].GetString();
price = ask_update[1].GetDouble();
quantity = ask_update[2].GetDouble();
LOG_TRACE("Ask Book Update | Action: {}, Price: {}, Quantity: {}", action, price, quantity);
if (price <= _ask_price_at_depth)
{
_depth_levels_updated = true;
}
if (action == "new")
{
if (__builtin_expect(_ask.insert(std::make_pair(price, data_types::price_level(
emumba::market_data::price(price), quantity)))
.second == false,
false))
{
LOG_ERROR("PriceLevel already exists. Could not insert new PriceLevel.");
return false;
}
++_ask_map_size;
}
else if (action == "change")
{
_iterator = _ask.find(price);
if (__builtin_expect(_iterator != _ask.end(), true))
{
_iterator->second.set_amount(quantity);
}
else
{
LOG_ERROR("PriceLevel not found.");
return false;
}
}
else if (action == "delete")
{
_iterator = _ask.find(price);
if (__builtin_expect(_iterator != _ask.end(), true))
{
_ask.erase(_iterator);
--_ask_map_size;
}
else
{
LOG_ERROR("PriceLevel not found.");
return false;
}
}
else
{
LOG_ERROR("Unknown Update Action Received: {}", action);
return false;
}
}
set_previous_change_id(book_message["data"]["change_id"].GetDouble());
// API ENABLED ONLY
if (_depth_levels_updated == true)
{
_book.bids.level.assign(_book_depth, emumba::market_data::book_level());
_book.asks.level.assign(_book_depth, emumba::market_data::book_level());
uint index = 0;
for (_iterator = _bid.begin(); index != _book_depth && _iterator != _bid.end(); ++_iterator, ++index)
{
_book.bids.level[index].direct_qty = _iterator->second.get_amount();
_book.bids.level[index].total_qty = _iterator->second.get_amount();
_book.bids.level[index].px = _iterator->second.get_price();
}
index = 0;
for (_iterator = _ask.begin(); index != _book_depth && _iterator != _ask.end(); ++_iterator, ++index)
{
_book.asks.level[index].direct_qty = _iterator->second.get_amount();
_book.asks.level[index].total_qty = _iterator->second.get_amount();
_book.asks.level[index].px = _iterator->second.get_price();
}
#ifndef E2E_PERF
#ifdef ENABLE_API
_trade_listener->on_book(_book, _api_handler);
#endif
#endif
LOG_TRACE("Book Update API Called, Instrument: {}", _instrument_name);
assert(_bid.size() == _bid_map_size);
assert(_ask.size() == _ask_map_size);
_bid_price_at_depth =
_bid_map_size >= _book_depth ? _book.bids.level[_book_depth - 1].px.to_double() : _lowest_possible_bid;
_ask_price_at_depth =
_ask_map_size >= _book_depth ? _book.asks.level[_book_depth - 1].px.to_double() : _highest_possible_ask;
LOG_TRACE("Instrument: {}. Bid price at depth: {}, Ask price at depth: {}", _instrument_name,
_bid_price_at_depth, _ask_price_at_depth);
}
#ifdef E2E_PERF
#ifdef ENABLE_API
_trade_listener->e2e_perf_on_book(_book, book_message["data"]["change_id"].GetDouble());
#endif
#endif
return true;
}
bool check_message_loss(const double &previous_change_id);
ALWAYS_INLINE data_types::book_status get_book_status()
{
return _book_status;
}
ALWAYS_INLINE void change_book_status(const data_types::book_status &book_status)
{
_book_status = book_status;
LOG_DEBUG(
"Changed book status: {}",
std::string{
_book_status == data_types::book_status::green ? "GREEN"
: "RED"});
#ifdef ENABLE_API
_trade_listener->on_book_state_change(_instrument_name, _book_status, _api_handler);
#endif
}
void reset_instrument();
void * get_api_handler();
std::ostream & print_order_book(std::ostream &out, int level = 0);
std::ostream & print_order_book_all_levels(std::ostream &out);
const double get_previous_change_id();
const uint64_t get_exchange_ts();
const std::string get_name();
const double get_last_traded_price();
const double get_last_traded_quantity();
const double get_total_traded_quantity();
const long double get_total_traded_value();
void update_trade_info(const double &ltp, const double &ltq);
data_types::price_level::iterator get_top(int side);
data_types::price_level::iterator get_level(int side, unsigned lvl);
bool is_iterator_valid(data_types::price_level::iterator it);
int get_depth(int side);
void set_instrument_data_interface(std::shared_ptr<emumba::deribit_connector::instrument_data_interface> interface);
void set_api_handler(void *ref_ptr);
emumba::market_data::security_state::state get_current_state();
void set_current_state(emumba::market_data::security_state::state new_state);
void set_inst_closed_flag(const bool &instrument_closed);
const bool & get_inst_closed_flag() const;
friend std::ostream & operator<<(std::ostream &out, instrument_data &instrument_data)
{
return instrument_data.print_order_book(out);
}
private:
ALWAYS_INLINE void set_previous_change_id(const double &change_id)
{
_previous_change_id = change_id;
LOG_TRACE("Updated _previous_change_id: {}", _previous_change_id);
}
std::string _instrument_name;
std::map<double, data_types::price_level, std::greater<double>>
_bid; // High to low order required therefore std::greater is used.
std::map<double, data_types::price_level> _ask;
std::map<double, data_types::price_level>::iterator _iterator;
double _previous_change_id;
double _lowest_possible_bid;
double _highest_possible_ask;
double _bid_price_at_depth;
double _ask_price_at_depth;
size_t _bid_map_size;
size_t _ask_map_size;
data_types::book_status _book_status;
uint _book_depth;
instrument_info _instrument_info;
// deribit_listener * _trade_listener;
void * _api_handler;
bool _depth_levels_updated;
std::shared_ptr<instrument_data_interface> _interface;
uint64_t _exchange_update_ts;
double _last_traded_price;
double _last_traded_quantity;
double _total_traded_quantity;
long double _total_traded_volume;
emumba::market_data::security_state _current_sec_state;
bool _is_inst_closed;
// API ENABLED ONLY
emumba::market_data::order_book _book;
};
} // namespace deribit_connector
} // namespace emumba
#endif /* INSTRUMENT_DATA_H */
@@ -0,0 +1,90 @@
#include "instrument_data_interface.h"
#include "deribit_utils.h"
#include "instrument_data.h"
#include "spdlogger.h"
#include <sstream>
using namespace emumba::deribit_connector;
emumba::deribit_connector::instrument_data_interface::instrument_data_interface(
emumba::deribit_connector::instrument_data *instrument)
{
_instrument = instrument;
LOG_TRACE("Created interface object for instrument: {}", get_instrument_name());
}
const uint64_t emumba::deribit_connector::instrument_data_interface::get_exchange_ts()
{
return _instrument->get_exchange_ts();
}
const uint64_t emumba::deribit_connector::instrument_data_interface::get_last_change_id()
{
return _instrument->get_previous_change_id();
}
const double emumba::deribit_connector::instrument_data_interface::get_last_traded_price()
{
return _instrument->get_last_traded_price();
}
const long double emumba::deribit_connector::instrument_data_interface::get_total_traded_value()
{
return _instrument->get_total_traded_value();
}
const double emumba::deribit_connector::instrument_data_interface::get_total_traded_quantity()
{
return _instrument->get_total_traded_quantity();
}
const std::string emumba::deribit_connector::instrument_data_interface::get_instrument_name()
{
return _instrument->get_name();
}
const std::string emumba::deribit_connector::instrument_data_interface::get_exchange()
{
return "Deribit";
}
const data_types::book_status emumba::deribit_connector::instrument_data_interface::get_trading_state()
{
return _instrument->get_book_status();
}
data_types::price_level::iterator emumba::deribit_connector::instrument_data_interface::top(int side)
{
return _instrument->get_top(side);
}
data_types::price_level::iterator emumba::deribit_connector::instrument_data_interface::level(int side, unsigned lvl)
{
return _instrument->get_level(side, lvl);
}
bool emumba::deribit_connector::instrument_data_interface::valid(data_types::price_level::iterator it)
{
return _instrument->is_iterator_valid(it);
}
uint16_t emumba::deribit_connector::instrument_data_interface::depth(int side)
{
return _instrument->get_depth(side);
}
const std::string emumba::deribit_connector::instrument_data_interface::get_book_to_string()
{
std::ostringstream ss;
_instrument->print_order_book_all_levels(ss);
return ss.str();
}
const std::string emumba::deribit_connector::instrument_data_interface::get_book_to_string(int lvl)
{
std::ostringstream ss;
_instrument->print_order_book(ss, lvl);
return ss.str();
}
@@ -0,0 +1,129 @@
/**
* @file instrument_data_interface.h
* @brief Provides a class with an interface used to get important information (maintained by the connector) related to an
* instrument.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*
*/
#ifndef INSTRUMENT_DATA_INTERFACE_H
#define INSTRUMENT_DATA_INTERFACE_H
#include "deribit_utils.h"
#include "price_level.h"
#include <string>
namespace emumba {
namespace deribit_connector {
class instrument_data;
/**
* @brief Provides functions which can be used to get useful information related to an instrument. This information is
* maintained by the connector.
*
*/
class instrument_data_interface
{
public:
instrument_data_interface(instrument_data *instrument);
/**
* @brief Get exchange timestamp for last book update of instrument
*
* @return uint64_t
*/
const uint64_t get_exchange_ts();
/**
* @brief Get Change ID last book update of instrument
*
* @return uint64_t
*/
const uint64_t get_last_change_id();
/**
* @brief Get Last Traded price for the instrument
*
* @return double
*/
const double get_last_traded_price();
/**
* @brief Get Total Traded Value uptil now for the instrument
*
* @return double
*/
const long double get_total_traded_value();
/**
* @brief Get Total Traded Quantity uptil now for the instrument
*
* @return long double
*/
const double get_total_traded_quantity();
/**
* @brief Get instrument name
*
* @return std::string
*/
const std::string get_instrument_name();
/**
* @brief Get Exchange name
*
* @return std::string
*/
const std::string get_exchange();
/**
* @brief Get Trading state for instrument
*
* @return std::string
*/
const data_types::book_status get_trading_state();
/**
* @brief Get top iterator of book
*
* @return data_types::price_level::iterator
*/
data_types::price_level::iterator top(int side);
/**
* @brief Get iterator of book for the specified level
*
* @return data_types::price_level::iterator
*/
data_types::price_level::iterator level(int side, unsigned lvl);
/**
* @brief Check validity of iterator
*
* @return bool
*/
bool valid(data_types::price_level::iterator it);
/**
* @brief Check depth of book
*
* @return uint16_t
*/
uint16_t depth(int side);
/**
* @brief Get book of instrument in pretty format
*
* @return std::string
*/
const std::string get_book_to_string();
/**
* @brief Get book of instrument up to specified level in pretty format
*
* @return std::string
*/
const std::string get_book_to_string(int lvl);
private:
instrument_data *_instrument;
};
} // namespace deribit_connector
} // namespace emumba
#endif
@@ -0,0 +1,63 @@
#ifdef LICENSE_CHECK
#include "license_validator.h"
#include "spdlogger.h"
#include <licensecc/licensecc.h>
#include <unordered_map>
emumba::deribit_connector::license_validator::license_validator()
{
is_valid = false;
}
bool emumba::deribit_connector::license_validator::check_license()
{
std::unordered_map<LCC_EVENT_TYPE, std::string> stringByEventType = {
{LICENSE_OK, "OK "},
{LICENSE_FILE_NOT_FOUND, "License file not found "},
{LICENSE_SERVER_NOT_FOUND, "License server can't be contacted "},
{ENVIRONMENT_VARIABLE_NOT_DEFINED, "Environment variable not defined "},
{FILE_FORMAT_NOT_RECOGNIZED, "License file has invalid format (not .ini file) "},
{LICENSE_MALFORMED, "Some mandatory field are missing, or data can't be fully read. "},
{PRODUCT_NOT_LICENSED, "This product was not licensed "},
{PRODUCT_EXPIRED, "License expired "},
{LICENSE_CORRUPTED, "License signature didn't match with current license "},
{IDENTIFIERS_MISMATCH, "Calculated identifier and the one provided in license didn't match"}};
LicenseInfo licenseInfo;
size_t pc_id_sz = LCC_API_PC_IDENTIFIER_SIZE + 1;
char pc_identifier[LCC_API_PC_IDENTIFIER_SIZE + 1];
LCC_EVENT_TYPE result = acquire_license(nullptr, nullptr, &licenseInfo);
if (result == LICENSE_OK)
{
LOG_INFO("License authentication successful.");
return true;
}
else
{
LOG_ERROR("License authentication failed. Error: {}", stringByEventType[result].c_str());
if (!(identify_pc(STRATEGY_DEFAULT, pc_identifier, &pc_id_sz, nullptr)))
{
LOG_TRACE("Error in identifying PC signature.");
}
exit(1);
}
return false;
}
void emumba::deribit_connector::license_validator::set_license_valid_status()
{
is_valid = true;
}
bool emumba::deribit_connector::license_validator::is_license_file_valid()
{
return is_valid;
}
#endif // LICENSE_CHECK
@@ -0,0 +1,24 @@
#ifndef _LICENSE_VALIDATOR_H
#define _LICENSE_VALIDATOR_H
#ifdef LICENSE_CHECK
namespace emumba {
namespace deribit_connector {
class license_validator
{
public:
license_validator();
bool check_license();
void set_license_valid_status();
bool is_license_file_valid();
private:
bool is_valid;
};
} // namespace deribit_connector
} // namespace emumba
#endif // LICENSE_CHECK
#endif /*_LICENSE_VALIDATOR_H*/
@@ -0,0 +1,469 @@
/**
* @file market_data_common.h
* @brief Provide structures used in market data API callbacks.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*
*/
#ifndef EMUMBA_MARKET_DATA_COMMON_H
#define EMUMBA_MARKET_DATA_COMMON_H
#include "price.h"
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <ctime>
#include <limits>
#include <ostream>
#include <string>
#include <variant>
#include <vector>
namespace emumba {
namespace market_data {
/**
* @brief A templated wrapper class with overloaded operators which can be used to define objects with different data
* types.
*
* @tparam Tag
* @tparam ValueT Data type of the templated class object.
*/
template <class Tag, class ValueT>
class value_wrapper
{
public:
constexpr value_wrapper() = default;
~value_wrapper() = default;
constexpr explicit value_wrapper(ValueT const &v)
: _v(v)
{}
constexpr explicit value_wrapper(ValueT &&v)
: _v(std::move(v))
{}
constexpr value_wrapper(value_wrapper const &v) = default;
constexpr value_wrapper(value_wrapper &&v) noexcept = default;
value_wrapper &operator=(value_wrapper const &v) = default;
value_wrapper &operator=(value_wrapper &&v) noexcept = default;
constexpr bool operator==(value_wrapper const &v) const
{
return _v == v._v;
}
constexpr bool operator!=(value_wrapper const &v) const
{
return _v != v._v;
}
constexpr bool operator<(value_wrapper const &v) const
{
return _v < v._v;
}
constexpr bool operator<=(value_wrapper const &v) const
{
return _v <= v._v;
}
constexpr bool operator>(value_wrapper const &v) const
{
return _v > v._v;
}
constexpr bool operator>=(value_wrapper const &v) const
{
return _v >= v._v;
}
constexpr ValueT const &value() const
{
return _v;
}
constexpr ValueT &value()
{
return _v;
}
private:
ValueT _v;
};
using quantity = value_wrapper<struct quantity_tag, std::variant<std::uint32_t, double>>;
using security_id = value_wrapper<struct secuirty_id_tag, std::variant<std::uint32_t, std::string>>;
// Before converting security id to integer value, make sure the orders can be placed properly since string value is
// required.
using MarketDataListenerID = value_wrapper<struct market_data_listened_id_tag, std::uint32_t>;
/**
* @brief Enum class used for the type of instrument.
*
*/
enum class security_type
{
index, /**< Value = 0. Index instrument. */
future, /**< Value = 1. Future instrument. */
spread, /**< Value = 2. Spread instrument. */
option /**< Value = 3. Option instrument. */
};
/**
* @brief Enum class used for the currency symbol.
*
*/
enum class currency
{
btc, /**< Value = 0. BTC. */
eth, /**< Value = 1. Ethereum. */
usd /**< Value = 2. US Dollar. */
};
/**
* @brief Enum class used for the exchange venue.
*
*/
enum class venue
{
glbx = 0, /**< Value = 0. CME Globex exchange. */
deribit = 1, /**< Value = 1. Deribit exchange. */
huobi = 2, /**< Value = 2. Huobi exchange. */
binance = 3, /**< Value = 3. Binance exchange. */
ftx = 4 /**< Value = 4. FTX exchange. */
};
/**
* @brief Enum class used for the type of option contract.
*
*/
enum class option_type
{
call, /**< Value = 0. Call type option. */
put /**< Value = 1. Put type option. */
};
/**
* @brief Stores exchange specific information required to configure the connector for CME.
*
*/
struct glbx_market_data_subscription
{
};
/**
* @brief Stores exchange specific information required to configure the connector for Deribit.
*
*/
struct deribit_market_data_subscription
{
};
/**
* @brief Used to configure a specific exchange for the connector. (Currently not in use)
*
*/
struct market_data_gateway_subscription
{
const venue exchange;
const std::variant<glbx_market_data_subscription, deribit_market_data_subscription> subscription = {
deribit_market_data_subscription{}};
};
/**
* @brief Used for handling the timestamp received in different update messages.
*
*/
struct exchange_timestamp
{
const uint64_t &get_timestamp() const
{
return _v;
}
void set_timestamp(uint64_t t)
{
_v = t;
}
private:
// posix time
uint64_t _v{};
};
/**
* @brief Stores exchange specific information received in the Deribit instrument definition.
*
*/
struct security_deribit
{
uint32_t contract_size;
bool is_active;
double min_trade_amount;
std::string settlement_period;
double block_trade_commission;
double maker_commission;
double taker_commission;
};
/**
* @brief Stores exchange specific information received in the CME instrument definition.
*
*/
struct security_cme
{
double display_factor{};
double cash_per_point{};
std::string asset;
bool implied_matching = false;
};
/**
* @brief Stores important fields received in the definition of the instruments which are common among different
* exchanges.
*
*/
class security_common
{
public:
venue exchange;
security_id sec_id{};
security_type sec_type;
currency ccy;
std::string symbol;
std::string security_group;
price min_price_increment{};
security_cme sec_cme;
security_deribit sec_deribit;
};
/**
* @brief Stores important fields related to future instrument.
*
*/
class future
{
public:
uint32_t leverage;
exchange_timestamp expiration_ts;
exchange_timestamp creation_ts;
};
/**
* @brief Stores important fields related to options instrument.
*
*/
class option
{
public:
option_type op_type;
price strike;
exchange_timestamp expiration_ts;
exchange_timestamp creation_ts;
};
/**
* @brief Stores important fields related to leg definition.
*
*/
struct leg_definition
{
security_id sec_id;
quantity leg_mult;
};
/**
* @brief Stores legs of a spread definition.
*
*/
class spread
{
public:
std::vector<leg_definition> legs;
};
/**
* @brief Stores important fields related to index instrument.
*
*/
class index
{
};
/**
* @brief Stores information related to an instrument.
*
*/
struct security
{
security_common sec_common;
std::variant<future, option, spread, index> sec;
security_type sec_type() const
{
if (std::holds_alternative<future>(sec))
{
return security_type::future;
}
else if (std::holds_alternative<option>(sec))
{
return security_type::option;
}
else if (std::holds_alternative<spread>(sec))
{
return security_type::spread;
}
return security_type::index;
}
};
/**
* @brief Used to handle a price level of a book.
*
*/
struct book_level
{
price px = {}; // original declarations were const
double total_qty = {};
double direct_qty = {};
size_t n_orders = 0;
double implied_qty() const
{
return double{};
}
};
/**
* @brief Stores all price levels of a book.
*
*/
struct book_side
{
std::vector<book_level> level;
};
/**
* @brief Stores books of an instrument.
*
*/
struct order_book
{
security_id sec_id;
book_side bids;
book_side asks;
};
/**
* @brief Enum class used for the aggressor side received in a trade update.
*
*/
enum class aggressor_side
{
buy = 0, /**< Value = 0. Buy. */
sell = 1, /**< Value = 1. Sell. */
none = 2 /**< Value = 2. None. */
};
/**
* @brief Stores important informantion related to a trade update.
*
*/
struct trade_record
{
security_id sec_id = {};
exchange_timestamp timestamp = {};
aggressor_side agg_side = {};
double trade_quantity;
price trade_price;
std::string exchange_order_id;
};
/**
* @brief Stores the state of an instrument.
*
*/
struct security_state
{
security_id sec_id{};
enum class state
{
preopen, /**< Value = 0. Preopen. */
open, /**< Value = 1. Open. */
closed /**< Value = 2. Closed. */
};
state state_ = state::open;
};
/**
* @brief Used for handling error messages received from the exchanges.
*
*/
struct market_data_error
{
enum class error_type
{
disconnection, /**< Value = 0. Disconnection. */
whitelist_empty /**< Value = 1. Whitelist empty. No instrument selected. Possible reason: Invalid instruments
specified in "interested_instruments" list. */
};
error_type err_type = error_type::disconnection;
std::string err_text = {};
};
/**
* @brief Stores important information related to statistics messages.
*
*/
struct market_data_statistics
{
security_id sec_id;
price low;
price high;
price open;
price settle;
price mark_price;
double volume;
};
/**
* @brief Stores important information related to a quote update.
*
*/
struct quote
{
security_id sec_id;
price best_bid_price;
price best_ask_price;
double best_bid_amount;
double best_ask_amount;
exchange_timestamp timestamp = {};
};
/**
* @brief Enum used for identifying the type of session.
*
*/
enum class session_type
{
market_data,
order_entry
};
/**
* @brief Stores information related to the disconnected session
*
*/
struct disconnected_session
{
uint session_id;
std::string name;
session_type sess_type;
};
} // namespace market_data
} // namespace emumba
#endif /*EMUMBA_MARKET_DATA_COMMON_H*/
@@ -0,0 +1,45 @@
#ifndef METHOD_IDENTIFIERS_H
#define METHOD_IDENTIFIERS_H
enum class method_id : int
{
gettime = 1000, // Supporting
hello,
test,
auth, // Authentication
exchange_token,
fork_token,
logout,
set_heartbeat, // Session Management
disable_heartbeat,
enable_cancel_on_disconnect,
disable_cancel_on_disconnect,
get_cancel_on_disconnect,
public_subscribe, // Subscription Management
public_unsubscribe,
private_subscribe,
private_unsubscribe,
get_account_summary, // Account Management
get_open_orders_by_instrument, // Trading
get_instruments, // Market Data
get_combos,
get_instrument,
get_positions,
get_open_orders_by_currency,
get_user_trades_by_currency_and_time,
new_order,
edit_order,
cancel_order,
cancel_all_by_currency,
cancel_all_by_instrument,
cancel_all_positions,
get_position,
set_mmp_config,
get_mmp_config,
reset_mmp_config,
rfq_request,
create_combo,
};
#endif /* METHOD_IDENTIFIERS_H */
@@ -0,0 +1,17 @@
#include "order_entry_common.h"
emumba::order_entry::order_state::order_state()
{
trades.reserve(128);
}
emumba::order_entry::rfq_request::rfq_request()
{
qty.value() = 0.0;
}
emumba::order_entry::combo_trade::combo_trade()
{
sec_id.value() = "";
qty = 0;
}
@@ -0,0 +1,317 @@
/**
* @file order_entry_common.h
* @brief Provide structures used in order entry APIs.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*
*/
#ifndef EMUMBA_ORDER_ENTRY_COMMON_H
#define EMUMBA_ORDER_ENTRY_COMMON_H
#include "deribit_emumba/emumba_enum.h"
#include "error.h"
#include "market_data_common.h"
#include "price.h"
#include <optional>
#include <string>
#include <vector>
namespace emumba {
namespace order_entry {
using order_key = emumba::market_data::value_wrapper<struct order_key_tag, std::int64_t>;
using user_id = emumba::market_data::value_wrapper<struct user_id_tag, std::uint64_t>;
using account_id = emumba::market_data::value_wrapper<struct account_id_tag, std::uint64_t>;
// clang-format off
#define ENUM_STATUS(op) \
op(status, 0, working) \
op(status, 1, completed) \
op(status, 2, canceled) \
op(status, 3, rejected) \
op(status, 4, untriggered)
EMUMBA_ENUM(status, ENUM_STATUS)
#undef ENUM_STATUS
// clang-format on
// clang-format off
#define ENUM_UPDATE_TYPE(op) \
op(update_type, 0, new_order_confirm) \
op(update_type, 1, new_order_reject) \
op(update_type, 2, edit_order_confirm) \
op(update_type, 3, edit_order_reject) \
op(update_type, 4, cancel_order_confirm) \
op(update_type, 5, cancel_order_reject) \
op(update_type, 6, subscription_update)
EMUMBA_ENUM(update_type, ENUM_UPDATE_TYPE)
#undef ENUM_UPDATE_TYPE
// clang-format on
/**
* @brief Stores exchange specific information received in the Deribit order update message.
*
*/
struct deribit_order_state
{
std::string instrument_name;
std::string exchange_order_id;
std::string direction;
emumba::market_data::exchange_timestamp timestamp;
};
/**
* @brief Stores exchange specific information received in the CME order update message.
*
*/
struct glbx_order_state
{
};
/**
* @brief Stores important information received in the order update message which is commong among different
* exchanges.
*
*/
struct order_state
{
uint subaccount_index;
emumba::deribit_connector::data_types::error error;
update_type order_update_type;
status order_status;
emumba::market_data::quantity qty;
emumba::market_data::quantity total_fills;
emumba::market_data::price px;
emumba::market_data::price avg_price;
std::vector<emumba::market_data::trade_record const *> trades;
std::variant<deribit_order_state, glbx_order_state> exchange_order_state;
order_state();
};
// clang-format off
#define ENUM_TIME_IN_FORCE(op) \
op(time_in_force, 48, day) /* Good till Day. */ \
op(time_in_force, 49, gtc) /* Good till Cancelled. */ \
op(time_in_force, 51, ioc) /* Immediate or Cancel. */ \
op(time_in_force, 52, date) /* Good till Date. */ \
op(time_in_force, 53, fill_or_kill) /* Fill or Kill. */
EMUMBA_ENUM(time_in_force, ENUM_TIME_IN_FORCE)
#undef EMUMBA_TIME_IN_FORCE
#define ENUM_ORDER_TYPE(op) \
op(order_type, 1, limit_order) /* Limit Order. */ \
op(order_type, 2, stop_limit_order) /* Stop Limit Order. */ \
op(order_type, 3, take_limit_order) /* Take Limit Order. */ \
op(order_type, 4, market_order) /* Market Order. */ \
op(order_type, 5, stop_market_order) /* Stop Market Order. */ \
op(order_type, 6, take_market_order) /* Take Market Order. */ \
op(order_type, 7, market_limit_order) /* Market Limit Order. */
EMUMBA_ENUM(order_type, ENUM_ORDER_TYPE)
#undef EMUMBA_ORDER_TYPE
#define ENUM_ORDER_TRIGGER(op) \
op(order_trigger, 0, index_price) /* Index Price. */ \
op(order_trigger, 1, mark_price) /* Mark Price. */ \
op(order_trigger, 2, last_price) /* Last Price. */
EMUMBA_ENUM(order_trigger, ENUM_ORDER_TRIGGER)
#undef EMUMBA_ORDER_TRIGGER
#define ENUM_ORDER_ADVANCED(op) \
op(order_advanced, 0, usd) /* USD. */ \
op(order_advanced, 1, implv) /* IMPLV. */
EMUMBA_ENUM(order_advanced, ENUM_ORDER_ADVANCED)
#undef EMUMBA_ORDER_ADVANCED
// clang-format on
/**
* @brief Stores optional fields required to place an order.
*
*/
struct deribit_order_def_optional
{
std::optional<double> max_show;
std::optional<emumba::market_data::price> trigger_price;
std::optional<order_trigger> trigger;
std::optional<order_advanced> advanced;
bool post_only = false;
bool reject_post_only = false;
bool reduce_only = false;
bool mmp = false;
};
/**
* @brief Stores information required to place an order on Deribit exchange.
*
*/
struct deribit_order_definition
{
order_type ord_type;
std::optional<deribit_order_def_optional> optional_fields;
};
/**
* @brief Stores information required to place an order on CME exchange.
*
*/
struct glbx_order_definition
{
};
/**
* @brief Stores information (which is common among different exchanges) required to place an order.
*
*/
struct order_definition
{
emumba::market_data::security_id sec_id;
emumba::market_data::side order_side;
std::string user;
user_id usr_id;
std::string account;
account_id acc_id;
time_in_force time_in_frc;
std::variant<deribit_order_definition, glbx_order_definition> exchange_order;
};
/**
* @brief Stores structures and fields required to place a new order.
*
*/
struct new_order
{
order_definition order_def;
std::optional<emumba::market_data::price> px;
emumba::market_data::quantity qty;
};
/**
* @brief Stores important information required to edit an order placed by the user.
*
*/
struct order_update
{
order_key ord_key;
std::optional<emumba::market_data::price> px;
emumba::market_data::quantity qty;
};
struct position
{
uint subaccount_index;
emumba::market_data::security_id sec_id;
emumba::market_data::quantity qty;
emumba::market_data::price average_price;
emumba::market_data::side position_side;
};
struct open_order
{
uint subaccount_index;
emumba::market_data::security_id sec_id;
std::string exchange_order_id;
std::uint64_t internal_order_id;
status order_status;
time_in_force time_in_frc;
emumba::market_data::side order_side;
emumba::market_data::quantity qty;
emumba::market_data::quantity filled_qty;
emumba::market_data::price px;
emumba::market_data::price average_price;
emumba::market_data::exchange_timestamp last_update_timestamp;
emumba::market_data::exchange_timestamp creation_timestamp;
bool api;
bool web;
};
struct new_limit_order
{
emumba::market_data::security_id sec_id;
emumba::market_data::quantity qty;
emumba::market_data::price px;
time_in_force time_in_frc;
};
struct new_market_order
{
emumba::market_data::security_id sec_id;
emumba::market_data::quantity qty;
time_in_force time_in_frc;
};
#define ENUM_SORT(op) \
op(sort, 0, ascending) \
op(sort, 1, decending) \
op(sort, 2, default_)
EMUMBA_ENUM(sort, ENUM_SORT)
#undef EMUMBA_SORT
struct user_trades_request
{
std::string currency;
emumba::market_data::exchange_timestamp start_timestamp;
emumba::market_data::exchange_timestamp end_timestamp;
std::optional<uint> count;
std::optional<bool> include_old;
std::optional<sort> sorting;
};
struct user_trades_response
{
uint subaccount_index;
emumba::deribit_connector::data_types::error error;
bool has_more;
std::vector<emumba::market_data::trade_record const *> trades;
};
struct rfq_request
{
emumba::market_data::security_id sec_id;
emumba::market_data::quantity qty;
emumba::market_data::side rfq_side;
rfq_request();
};
struct rfq_response
{
emumba::deribit_connector::data_types::error error;
bool status;
uint subaccount_index;
};
struct combo_trade
{
emumba::market_data::security_id sec_id;
emumba::market_data::side side;
uint64_t qty;
combo_trade();
};
struct leg_detail
{
emumba::market_data::security_id instrument_name;
int amount;
};
struct combo_detail
{
emumba::deribit_connector::data_types::error error;
std::vector<emumba::order_entry::leg_detail> legs;
emumba::market_data::security_id sec_id;
uint subaccount_index;
};
} // namespace order_entry
} // namespace emumba
#endif /*EMUMBA_ORDER_ENTRY_COMMON_H*/
@@ -0,0 +1,826 @@
#include "orders_handler.h"
#include "spdlogger.h"
#include "utils.h"
emumba::deribit_connector::orders_handler::orders_handler(const configuration::order_tracker &orders_tracker,
deribit_listener * trad_listener)
: _trading_listener(trad_listener)
, _empty_string("")
{
_order_states.reserve(128);
if (orders_tracker.uuid == "")
{
_uuid = uuid::generate_uuid_v4();
}
else
{
_uuid = orders_tracker.uuid;
}
_internal_order_id = orders_tracker.start_id;
_uuid_length = _uuid.length();
strcpy(_label, _uuid.c_str());
_label[_uuid_length] = '-';
create_next_label_value();
}
void emumba::deribit_connector::orders_handler::process_new_order(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value &result, order_queues &order_queues,
const uint &subaccount_index)
{
_order_state.error = ec;
_order_state.subaccount_index = subaccount_index;
if (ec.value() == 0)
{
_label_value = result["order"]["label"].GetString();
LOG_DEBUG("Processing New Order Response. Label: {}", _label_value);
parse_label_value(_label_value, _label_uuid, _label_order_id);
_order_details[_label_order_id].order_id = result["order"]["order_id"].GetString();
_order_key = emumba::order_entry::order_key(_label_order_id);
if (_order_details[_label_order_id].is_order_confirm == false)
{
LOG_DEBUG("New Order Confirmation | Order Response | Internal Order ID: {}", _label_order_id);
_order_state.order_update_type = emumba::order_entry::update_type::new_order_confirm;
_order_details[_label_order_id].is_order_confirm = true;
}
else
{
_order_state.order_update_type = emumba::order_entry::update_type::subscription_update;
}
process_response_msg(ec, result);
}
else
{
LOG_DEBUG("New order message rejected");
_order_key = order_queues.new_order_queue.front();
_label_order_id = _order_key.value();
_order_state.order_update_type = emumba::order_entry::update_type::new_order_reject;
_order_state.order_status = emumba::order_entry::status::rejected;
process_request_rejection();
LOG_DEBUG("Order rejected | Deleting order detail structure | Internal Order ID: {}", _label_order_id);
_order_details.erase(_label_order_id);
}
order_queues.new_order_queue.pop();
LOG_DEBUG("New Order Queue | Pop | Queue size: {}", order_queues.new_order_queue.size());
}
void emumba::deribit_connector::orders_handler::process_edit_order(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value &result,
order_queues &order_queues, const uint &subaccount_index)
{
_order_state.error = ec;
_order_state.subaccount_index = subaccount_index;
if (ec.value() == 0)
{
_label_value = result["order"]["label"].GetString();
LOG_DEBUG("Processing Edit Order Response. Label: {}", _label_value);
parse_label_value(_label_value, _label_uuid, _label_order_id);
_order_key = emumba::order_entry::order_key(_label_order_id);
_order_state.order_update_type = emumba::order_entry::update_type::edit_order_confirm;
process_response_msg(ec, result);
}
else
{
LOG_DEBUG("Edit order message rejected");
_order_key = order_queues.edit_queue.front();
_label_order_id = _order_key.value();
_order_state.order_update_type = emumba::order_entry::update_type::edit_order_reject;
_order_state.order_status = emumba::order_entry::status::working;
process_request_rejection();
}
order_queues.edit_queue.pop();
LOG_DEBUG("Edit Order Queue | Pop | Queue size: {}", order_queues.edit_queue.size());
}
void emumba::deribit_connector::orders_handler::process_cancel_order(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value &result,
order_queues & order_queues,
const uint & subaccount_index)
{
_order_state.error = ec;
_order_state.subaccount_index = subaccount_index;
if (ec.value() == 0)
{
_label_value = result["label"].GetString();
LOG_DEBUG("Processing Cancel Order Response. Label: {}", _label_value);
parse_label_value(_label_value, _label_uuid, _label_order_id);
_order_key = emumba::order_entry::order_key(_label_order_id);
_order_state.order_update_type = emumba::order_entry::update_type::cancel_order_confirm;
process_response_msg(ec, result);
}
else
{
LOG_DEBUG("Cancel Order Message Rejected");
_order_key = order_queues.cancel_queue.front();
_label_order_id = _order_key.value();
_order_state.order_update_type = emumba::order_entry::update_type::cancel_order_reject;
_order_state.order_status = emumba::order_entry::status::working;
process_request_rejection();
}
order_queues.cancel_queue.pop();
LOG_DEBUG("Cancel Order Queue | Pop | Queue size: {}", order_queues.cancel_queue.size());
}
void emumba::deribit_connector::orders_handler::process_response_msg(
UNUSED_PARAM const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result)
{
// Note: order_state.ec, order_state.order_update_type, _order_key and _label_order_id should be set before calling this
// function
if (result.HasMember("trades")) // No trades member in cancel response
{
const rapidjson::Value &order_trades = result["trades"];
// Check for trades
if (order_trades.GetArray().Size() != 0)
{
process_trade(order_trades);
}
else
{
// Timestamp is only updated using trade messages (order response and trade sub) so in case response is received
// first and there are no trades, the timestamp should be updated using order msg.
uint64_t response_msg_timestamp_value = result["order"]["last_update_timestamp"].GetUint64();
if (_order_details[_label_order_id].timestamp < response_msg_timestamp_value)
{
_order_details[_label_order_id].timestamp = response_msg_timestamp_value;
}
}
}
if (_order_state.order_update_type != emumba::order_entry::update_type::cancel_order_confirm)
{
_order_details[_label_order_id].price = result["order"]["price"].GetDouble();
_order_details[_label_order_id].total_amount = result["order"]["amount"].GetDouble();
}
else
{
_order_details[_label_order_id].price = result["price"].GetDouble();
_order_details[_label_order_id].total_amount = result["amount"].GetDouble();
}
_order_details[_label_order_id].is_order_response_received = true;
process_order_state(_order_state);
if (_order_details[_label_order_id].is_order_complete == false)
{
if (_order_state.order_update_type == emumba::order_entry::update_type::cancel_order_confirm)
{
_order_details[_label_order_id].is_order_complete = true;
_order_details[_label_order_id].total_filled_amount = result["filled_amount"].GetDouble();
if (_order_details[_label_order_id].total_filled_amount == 0)
{
_order_details[_label_order_id].is_trade_sub_msg_received = true;
}
LOG_DEBUG("Cancel Order | Order completed | Internal Order ID: {}, Filled amount: {}, Total filled amount: {}",
_label_order_id, _order_details[_label_order_id].filled_amount,
_order_details[_label_order_id].total_filled_amount);
remove_order_if_complete(_label_order_id);
}
else
{
std::string order_state = result["order"]["order_state"].GetString();
if (order_state == "filled" || order_state == "cancelled" || order_state == "rejected")
{
if (result["trades"].GetArray().Size() == 0)
{
if ((_order_state.order_update_type != emumba::order_entry::update_type::edit_order_confirm) ||
((_order_state.order_update_type == emumba::order_entry::update_type::edit_order_confirm) &&
(result["order"]["filled_amount"].GetDouble() == 0)))
{
// New Order Response. Order is complete and it has no fills so we won't be receiving any message on the
// trade subscription Edit Order Response. Order is complete and if it has no fills before and after
// editing the order, we won't be receiving any message on the trade subscription
_order_details[_label_order_id].is_trade_sub_msg_received = true;
}
}
_order_details[_label_order_id].is_order_complete = true;
_order_details[_label_order_id].total_filled_amount = result["order"]["filled_amount"].GetDouble();
LOG_DEBUG("{} | Order completed | Internal Order ID: {}, Filled amount: {}, Total filled amount: {}",
std::string(_order_state.order_update_type == emumba::order_entry::update_type::edit_order_confirm ? "Edit Order"
: "New Order"),
_label_order_id, _order_details[_label_order_id].filled_amount,
_order_details[_label_order_id].total_filled_amount);
remove_order_if_complete(_label_order_id);
}
}
}
else
{
// In case order is complete but response msg processing and/or trade callbacks are pending. Once order response msg
// is processed and all trade callbacks are called, check whether or not order completion message needs to be sent.
if ((_order_details[_label_order_id].is_order_sub_msg_received == true) &&
((_order_details[_label_order_id].total_filled_amount - _order_details[_label_order_id].filled_amount) <=
DOUBLE_LIMIT))
{
_order_state.order_status = _order_details[_label_order_id].state;
_order_state.order_update_type = emumba::order_entry::update_type::subscription_update;
#ifdef ENABLE_API
_trading_listener->on_order_update(_order_key, _order_state);
#endif
}
remove_order_if_complete(_label_order_id);
}
}
void emumba::deribit_connector::orders_handler::process_order_state(emumba::order_entry::order_state &order_state,
const bool & is_final_callback)
{
// Note: order_state.ec, order_state.order_update_type, _order_key and _label_order_id should be set before calling this
// function
if (is_final_callback == false)
{
order_state.order_status = emumba::order_entry::status::working;
}
else
{
order_state.order_status = _order_details[_label_order_id].state;
}
order_state.qty = emumba::market_data::quantity(_order_details[_label_order_id].total_amount);
order_state.total_fills = emumba::market_data::quantity(_order_details[_label_order_id].filled_amount);
order_state.px = emumba::market_data::price(_order_details[_label_order_id].price);
order_state.avg_price = emumba::market_data::price(_order_details[_label_order_id].avg_price);
_deribit_order_state.instrument_name = _order_details[_label_order_id].instrument_name;
_deribit_order_state.exchange_order_id = _order_details[_label_order_id].order_id;
_deribit_order_state.direction = _order_details[_label_order_id].direction;
_deribit_order_state.timestamp.set_timestamp(_order_details[_label_order_id].timestamp);
order_state.exchange_order_state = _deribit_order_state;
#ifdef ENABLE_API
_trading_listener->on_order_update(_order_key, order_state);
#endif
order_state.trades.clear();
_order_details[_label_order_id].trade_records_list.clear();
}
void emumba::deribit_connector::orders_handler::process_request_rejection()
{
_order_details[_label_order_id].is_order_response_received = true;
_order_state.qty = emumba::market_data::quantity(0.0);
_order_state.total_fills = emumba::market_data::quantity(0.0);
_order_state.px = emumba::market_data::price(0.0);
_order_state.avg_price = emumba::market_data::price(0.0);
_deribit_order_state.instrument_name = _order_details[_label_order_id].instrument_name;
_deribit_order_state.direction = _order_details[_label_order_id].direction;
_deribit_order_state.exchange_order_id = "";
_deribit_order_state.timestamp.set_timestamp(0);
_order_state.exchange_order_state = _deribit_order_state;
#ifdef ENABLE_API
_trading_listener->on_order_update(_order_key, _order_state);
#endif
if (_order_state.order_update_type == emumba::order_entry::update_type::edit_order_reject ||
_order_state.order_update_type == emumba::order_entry::update_type::cancel_order_reject)
{
if ((_order_details[_label_order_id].is_order_sub_msg_received == true) &&
((_order_details[_label_order_id].total_filled_amount - _order_details[_label_order_id].filled_amount) <=
DOUBLE_LIMIT))
{
_order_state.order_status = _order_details[_label_order_id].state;
_order_state.order_update_type = emumba::order_entry::update_type::subscription_update;
#ifdef ENABLE_API
_trading_listener->on_order_update(_order_key, _order_state);
#endif
remove_order_if_complete(_label_order_id);
}
}
}
void emumba::deribit_connector::orders_handler::process_trade(const rapidjson::Value &order_trades)
{
for (rapidjson::Value::ConstValueIterator trade_itr = order_trades.Begin(); trade_itr != order_trades.End(); ++trade_itr)
{
auto trade = trade_itr->GetObject();
_trade_id = trade["trade_id"].GetString();
auto &trade_ids = _order_details[_label_order_id].trade_ids;
if (std::find(trade_ids.begin(), trade_ids.end(), _trade_id) == trade_ids.end())
{
fill_trade_record(_trade_record, trade);
LOG_DEBUG("Calling Trade callback | Trade ID: {} | Internal Order ID: {}", _trade_id, _label_order_id);
_order_details[_label_order_id].trade_records_list.push_back(_trade_record);
_order_state.trades.push_back(&_order_details[_label_order_id].trade_records_list.back());
trade_ids.push_back(_trade_id);
_order_details[_label_order_id].filled_amount =
_order_details[_label_order_id].filled_amount + _trade_record.trade_quantity;
// Stop updating timestamp & avg price using trades when final order sub msg is received
if (_order_details[_label_order_id].is_order_sub_msg_received != true)
{
_order_details[_label_order_id].timestamp = _trade_record.timestamp.get_timestamp();
_trade_detail.value = _trade_record.trade_quantity / _trade_record.trade_price.to_double();
_trade_detail.price = _trade_record.trade_price.to_double();
_order_details[_label_order_id].sum_of_trade_values =
_order_details[_label_order_id].sum_of_trade_values + _trade_detail.value;
_order_details[_label_order_id].trades_detail.push_back(_trade_detail);
_avg_price = 0;
for (auto &each_trade_detail : _order_details[_label_order_id].trades_detail)
{
_avg_price = _avg_price + ((each_trade_detail.value / _order_details[_label_order_id].sum_of_trade_values) *
each_trade_detail.price);
}
_order_details[_label_order_id].avg_price = round_two(_avg_price);
}
LOG_TRACE("Trade ID: {}, Total Amount: {}, Avg Price (Calculated): {}, Avg Price (Round off): {}", _trade_id,
_order_details[_label_order_id].filled_amount, _avg_price, _order_details[_label_order_id].avg_price);
}
else
{
LOG_DEBUG("Trade callback already called | Trade ID: {} | Internal Order ID: {}", _trade_id, _label_order_id);
}
}
}
void emumba::deribit_connector::orders_handler::process_private_user_trades_subscription(
const rapidjson::Value &user_trades_message, const uint &subaccount_index)
{
for (rapidjson::Value::ConstValueIterator user_trades_msg_itr = user_trades_message["data"].Begin();
user_trades_msg_itr != user_trades_message["data"].End(); ++user_trades_msg_itr)
{
auto user_trade_message = user_trades_msg_itr->GetObject();
LOG_DEBUG("Subscription | Private | User Trades | Instrument: {}", std::string(user_trade_message["instrument_name"].GetString()));
if (user_trade_message.HasMember("label"))
{
_label_value = user_trade_message["label"].GetString();
parse_label_value(_label_value, _label_uuid, _label_order_id);
if ((_label_uuid == _uuid) && user_trade_message.HasMember("combo_trade_id") == false &&
(is_internal_order_id_active(_label_order_id) == true))
{
_trade_id = user_trade_message["trade_id"].GetString();
auto &trade_ids = _order_details[_label_order_id].trade_ids;
if (std::find(trade_ids.begin(), trade_ids.end(), _trade_id) == trade_ids.end())
{
fill_trade_record(_trade_record, user_trade_message);
LOG_DEBUG("Trade Message | Trade ID: {} | Internal Order ID: {}", _trade_id, _label_order_id);
_order_details[_label_order_id].trade_records_list.push_back(_trade_record);
_order_states[_label_order_id].trades.push_back(&_order_details[_label_order_id].trade_records_list.back());
trade_ids.push_back(_trade_id);
_order_details[_label_order_id].filled_amount =
_order_details[_label_order_id].filled_amount + _trade_record.trade_quantity;
if (_order_details[_label_order_id].is_order_sub_msg_received != true)
{
_order_details[_label_order_id].timestamp = _trade_record.timestamp.get_timestamp();
_trade_detail.value = _trade_record.trade_quantity / _trade_record.trade_price.to_double();
_trade_detail.price = _trade_record.trade_price.to_double();
_order_details[_label_order_id].sum_of_trade_values =
_order_details[_label_order_id].sum_of_trade_values + _trade_detail.value;
_order_details[_label_order_id].trades_detail.push_back(_trade_detail);
_avg_price = 0;
for (auto &each_trade_detail : _order_details[_label_order_id].trades_detail)
{
_avg_price =
_avg_price + ((each_trade_detail.value / _order_details[_label_order_id].sum_of_trade_values) *
each_trade_detail.price);
}
_order_details[_label_order_id].avg_price = round_two(_avg_price);
}
LOG_TRACE("Trade ID: {}, Total Amount: {}, Avg Price (Calculated): {}, Avg Price (Round off): {}", _trade_id,
_order_details[_label_order_id].filled_amount, _avg_price,
_order_details[_label_order_id].avg_price);
if (_order_details[_label_order_id].is_order_confirm == false)
{
_order_details[_label_order_id].last_trade.order_id = user_trade_message["order_id"].GetString();
_order_details[_label_order_id].last_trade.price = user_trade_message["price"].GetDouble();
}
if (_order_details[_label_order_id].is_order_complete != true)
{
_order_details[_label_order_id].last_trade.state = user_trade_message["state"].GetString();
}
}
else
{
LOG_DEBUG("Trade callback already called | Trade ID: {} | Internal Order ID: {}", _trade_id, _label_order_id);
_order_ids_without_trades.emplace(_label_order_id);
}
}
else
{
LOG_DEBUG(
"Trade does not belong to trader session or internal order id is no longer being tracked | UUID: {}, "
"Internal order id: {}",
_uuid, _label_order_id);
}
}
else
{
LOG_DEBUG("Label value is empty");
}
}
for (auto &order_state : _order_states)
{
_label_order_id = order_state.first;
_order_details[_label_order_id].is_trade_sub_msg_received = true;
if (_order_details[_label_order_id].is_order_confirm == false)
{
LOG_DEBUG("New Order Confirmation | Pvt Trade Msg | Internal Order ID: {}", _label_order_id);
order_state.second.order_update_type = emumba::order_entry::update_type::new_order_confirm;
_order_details[_label_order_id].order_id = _order_details[_label_order_id].last_trade.order_id;
_order_details[_label_order_id].price = _order_details[_label_order_id].last_trade.price;
_order_details[_label_order_id].is_order_confirm = true;
}
else
{
order_state.second.order_update_type = emumba::order_entry::update_type::subscription_update;
}
order_state.second.error = emumba::deribit_connector::data_types::error(0, "Success");
order_state.second.subaccount_index = subaccount_index;
_order_key = emumba::order_entry::order_key(_label_order_id);
process_order_state(order_state.second);
_order_ids_without_trades.erase(_label_order_id); // Remove since this label order id contain some trades.
if (_order_details[_label_order_id].is_order_complete != true)
{
std::string last_trade_order_state = _order_details[_label_order_id].last_trade.state;
if (last_trade_order_state == "filled" || last_trade_order_state == "cancelled" ||
last_trade_order_state == "rejected")
{
_order_details[_label_order_id].is_order_complete = true;
}
}
// Order might be already complete or it might complete after final trade message. This if should be separate from the
// above if condition.
if (_order_details[_label_order_id].is_order_complete)
{
LOG_DEBUG(
"Trade sub msg | Order completed | Internal Order ID: {}, Filled amount: {}, Total filled amount: "
"{}",
_label_order_id, _order_details[_label_order_id].filled_amount,
_order_details[_label_order_id].total_filled_amount);
if (((_order_details[_label_order_id].total_filled_amount - _order_details[_label_order_id].filled_amount) <=
DOUBLE_LIMIT) &&
(_order_details[_label_order_id].is_order_response_received == true) &&
(_order_details[_label_order_id].is_order_sub_msg_received == true))
{
order_state.second.order_status = _order_details[_label_order_id].state;
order_state.second.order_update_type = emumba::order_entry::update_type::subscription_update;
#ifdef ENABLE_API
_trading_listener->on_order_update(_order_key, order_state.second);
#endif
remove_order_if_complete(_label_order_id);
}
}
}
if (_order_ids_without_trades.empty() == false)
{
for (const auto &order_id : _order_ids_without_trades)
{
_order_details[order_id].is_trade_sub_msg_received = true;
remove_order_if_complete(order_id);
}
}
_order_ids_without_trades.clear();
_order_states.clear();
}
void emumba::deribit_connector::orders_handler::process_private_user_orders_subscription(
const rapidjson::Value &user_orders_message, const uint &subaccount_index)
{
LOG_DEBUG("Subscription | Private | User Orders | Instrument: {}",
std::string(user_orders_message["data"]["instrument_name"].GetString()));
if (user_orders_message["data"].HasMember("label"))
{
_label_value = user_orders_message["data"]["label"].GetString();
LOG_DEBUG("Processing user order message. Label: {}", _label_value);
parse_label_value(_label_value, _label_uuid, _label_order_id);
if ((_label_uuid == _uuid) && user_orders_message["data"].HasMember("combo_order_id") == false &&
(is_internal_order_id_active(_label_order_id) == true))
{
std::string order_state = user_orders_message["data"]["order_state"].GetString();
emumba::order_entry::status order_status = emumba::order_entry::status::working;
if (strcmp(order_state.c_str(), "open") == 0)
{
order_status = emumba::order_entry::status::working;
}
else if (strcmp(order_state.c_str(), "filled") == 0)
{
order_status = emumba::order_entry::status::completed;
}
else if (strcmp(order_state.c_str(), "cancelled") == 0)
{
order_status = emumba::order_entry::status::canceled;
}
else if (strcmp(order_state.c_str(), "rejected") == 0)
{
order_status = emumba::order_entry::status::rejected;
}
else if (strcmp(order_state.c_str(), "untriggered") == 0)
{
order_status = emumba::order_entry::status::untriggered;
}
else
{
LOG_WARNING("Unknown order state received: {}", order_state);
}
if (order_status == emumba::order_entry::status::completed ||
order_status == emumba::order_entry::status::canceled || order_status == emumba::order_entry::status::rejected)
{
LOG_DEBUG("Subscription | Private | User Orders | Order Completed");
_order_details[_label_order_id].is_order_complete = true;
_order_details[_label_order_id].is_order_sub_msg_received = true;
_order_details[_label_order_id].state = order_status;
_order_details[_label_order_id].total_filled_amount = user_orders_message["data"]["filled_amount"].GetDouble();
_order_details[_label_order_id].avg_price = user_orders_message["data"]["average_price"].GetDouble();
_order_details[_label_order_id].timestamp = user_orders_message["data"]["last_update_timestamp"].GetUint64();
_order_details[_label_order_id].price = user_orders_message["data"]["price"].GetDouble();
if ((_order_details[_label_order_id].is_order_response_received == true) &&
((_order_details[_label_order_id].total_filled_amount - _order_details[_label_order_id].filled_amount) <=
DOUBLE_LIMIT))
{
_order_state.order_update_type = emumba::order_entry::update_type::subscription_update;
_order_state.error = emumba::deribit_connector::data_types::error(0, "Success");
_order_state.subaccount_index = subaccount_index;
_order_key = emumba::order_entry::order_key(_label_order_id);
process_order_state(_order_state, true);
LOG_DEBUG(
"Order sub msg | Order completed | Internal Order ID: {}, Filled amount: {}, Total filled amount: {}",
_label_order_id, _order_details[_label_order_id].filled_amount,
_order_details[_label_order_id].total_filled_amount);
remove_order_if_complete(_label_order_id);
}
else
{
LOG_DEBUG("Order completed but waiting for all trades to be processed");
}
}
else
{
LOG_DEBUG("Order sub msg | Order not complete. Ignoring message");
}
}
else
{
LOG_DEBUG(
"User order does not belong to trader session or internal order id is no longer being tracked | UUID: {}, "
"Internal order id: {}",
_uuid, _label_order_id);
}
}
else
{
LOG_TRACE("label not found in User Orders Msg | Skipping message");
}
}
void emumba::deribit_connector::orders_handler::fill_trade_record(emumba::market_data::trade_record & trade_record,
const rapidjson::Value::ConstObject &user_trade_message)
{
trade_record.sec_id.value() = user_trade_message["instrument_name"].GetString();
trade_record.timestamp.set_timestamp(user_trade_message["timestamp"].GetUint64());
if ((strcmp(user_trade_message["direction"].GetString(), "buy") == 0))
{
trade_record.agg_side = emumba::market_data::aggressor_side::buy;
}
else if ((strcmp(user_trade_message["direction"].GetString(), "sell") == 0))
{
trade_record.agg_side = emumba::market_data::aggressor_side::sell;
}
else
{
trade_record.agg_side = emumba::market_data::aggressor_side::none;
}
trade_record.trade_quantity = user_trade_message["amount"].GetDouble();
trade_record.trade_price = emumba::market_data::price(user_trade_message["price"].GetDouble());
trade_record.exchange_order_id = user_trade_message["order_id"].GetString();
}
const bool emumba::deribit_connector::orders_handler::is_internal_order_id_active(const std::uint64_t &internal_order_id)
{
return _order_details.find(internal_order_id) != _order_details.end();
}
const std::string &emumba::deribit_connector::orders_handler::get_exchange_order_id(const std::uint64_t &internal_order_id)
{
if (is_internal_order_id_active(internal_order_id) == true)
{
return _order_details[internal_order_id].order_id;
}
LOG_WARNING("Order ID for Order key {} not found in record.", internal_order_id);
return _empty_string;
}
const std::uint64_t &emumba::deribit_connector::orders_handler::get_internal_order_id() const
{
return _internal_order_id;
}
void emumba::deribit_connector::orders_handler::parse_label_value(std::string label_value, std::string &label_uuid,
std::uint64_t &label_order_id)
{
LOG_TRACE("Parsing Label: {}", label_value);
std::string delimiter = "-";
size_t pos = 0;
pos = label_value.find(delimiter);
label_uuid = label_value.substr(0, pos);
LOG_TRACE("LABEL UUID: {}, Position start: {}, Position End: {}", label_uuid, 0, pos);
char *end;
auto string_order_id = label_value.substr(pos + delimiter.length(), label_value.length());
label_order_id = strtoull(string_order_id.c_str(), &end, 10);
LOG_TRACE("STRING ORDER UUID: {}, Position start: {}, Position End: {}", string_order_id, pos + delimiter.length(),
label_value.length());
}
void emumba::deribit_connector::orders_handler::create_next_label_value()
{
uint8_t label_length = _uuid_length + 1;
// count numbers of digits
for (uint64_t internal_order_id = ++_internal_order_id; internal_order_id > 0; internal_order_id /= 10, ++label_length)
;
_label[label_length] = '\0';
for (uint64_t internal_order_id = _internal_order_id; internal_order_id > 0; internal_order_id /= 10)
{
_label[--label_length] = internal_order_id % 10 + '0';
}
}
const char *emumba::deribit_connector::orders_handler::get_label()
{
return _label;
}
const std::string &emumba::deribit_connector::orders_handler::get_uuid() const
{
return _uuid;
}
double emumba::deribit_connector::orders_handler::round_two(const double &value)
{
// _rounded_value = std::ceil(value * 100.0) / 100.0;
// return _rounded_value;
_rounded_value = (int)(value * 100.0 + 0.5) / 100.0;
return _rounded_value;
}
void emumba::deribit_connector::orders_handler::reset_flag_order_response_received(const uint64_t &label_order_id)
{
LOG_DEBUG("Order response received flag reset | Internal Order ID: {}", label_order_id);
_order_details[label_order_id].is_order_response_received = false;
}
void emumba::deribit_connector::orders_handler::create_order_detail(const uint &session_id, const uint64_t &label_order_id,
const std::string &instrument_name,
const std::string &direction, const double &quantity)
{
LOG_DEBUG("Order detail object created | Internal order id: {}", label_order_id);
_order_details[label_order_id] = orders_handler::order_detail();
_order_details[label_order_id].instrument_name = instrument_name;
_order_details[label_order_id].direction = direction;
_order_details[label_order_id].total_amount = quantity;
_order_details[label_order_id].session_id = session_id;
_order_details[label_order_id].trades_detail.reserve(128);
}
void emumba::deribit_connector::orders_handler::on_trader_sess_disconnect(const uint &session_id,
const uint &subaccount_index)
{
LOG_DEBUG("Orders handler | Trader session {} disconnected.", session_id);
for (auto &each_order_detail : _order_details)
{
emumba::deribit_connector::orders_handler::order_detail &order_detail = each_order_detail.second;
if (order_detail.session_id == session_id)
{
_label_order_id = each_order_detail.first;
order_detail.state = emumba::order_entry::status::canceled;
_order_state.error = emumba::deribit_connector::data_types::error(0, "Success", "");
_order_state.subaccount_index = subaccount_index;
_order_key = emumba::order_entry::order_key(_label_order_id);
process_order_state(_order_state, true);
_order_ids_to_remove.emplace(_label_order_id);
}
}
// Remove all those order ids which have been cancelled by us due to disconnection
for (auto &each_order_id : _order_ids_to_remove)
{
LOG_DEBUG("Orders handler | Removing internal order id {} which belongs to session {}.", _label_order_id, session_id);
_order_details.erase(each_order_id);
}
LOG_DEBUG("Orders handler | Order Details size: {}", _order_details.size());
_order_ids_to_remove.clear();
}
void emumba::deribit_connector::orders_handler::remove_order_if_complete(const uint64_t &label_order_id)
{
LOG_TRACE("Remove order detail called | Internal Order ID: {}", label_order_id);
if (_order_details[label_order_id].is_order_complete) // Can be removed if called only when order is complete
{
LOG_TRACE("Remove order detail | Internal Order ID: {}, is_order_complete: {}", label_order_id,
_order_details[label_order_id].is_order_complete);
if (_order_details[label_order_id].is_order_response_received &&
_order_details[label_order_id].is_order_sub_msg_received &&
_order_details[label_order_id].is_trade_sub_msg_received)
{
LOG_TRACE("Remove order detail | Msg received from all sources | Internal Order ID: {}", label_order_id);
if ((_order_details[_label_order_id].total_filled_amount - _order_details[_label_order_id].filled_amount) <=
DOUBLE_LIMIT)
{
LOG_DEBUG("Order completed | Deleting order detail structure | Order ID: {}, Internal Order ID: {}",
_order_details[label_order_id].order_id, label_order_id);
_order_details.erase(_label_order_id);
}
}
}
}
@@ -0,0 +1,132 @@
#ifndef ORDERS_HANDLER_H
#define ORDERS_HANDLER_H
#include "deribit_handler.h"
#include "deribit_listener.h"
#include "deribit_utils.h"
#include "rapidjson/document.h"
#include "utils.h"
#include <list>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace emumba {
namespace deribit_connector {
class orders_handler
{
public:
orders_handler(const configuration::order_tracker &orders_tracker, deribit_listener *trad_listener);
void process_new_order(const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result,
order_queues &order_queues, const uint &subaccount_index);
void process_edit_order(const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result,
order_queues &order_queues, const uint &subaccount_index);
void process_cancel_order(const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result,
order_queues &order_queues, const uint &subaccount_index);
void process_private_user_trades_subscription(const rapidjson::Value &user_trades_message,
const uint & subaccount_index);
void process_private_user_orders_subscription(const rapidjson::Value &user_orders_message,
const uint & subaccount_index);
const std::string & get_exchange_order_id(const std::uint64_t &internal_order_id);
const std::uint64_t &get_internal_order_id() const;
const std::string & get_uuid() const;
void parse_label_value(std::string label_value, std::string &label_uuid, std::uint64_t &label_order_id);
void create_next_label_value();
const char *get_label();
void reset_flag_order_response_received(const uint64_t &label_order_id);
void create_order_detail(const uint &session_id, const uint64_t &label_order_id, const std::string &instrument_name,
const std::string &direction, const double &quantity);
void on_trader_sess_disconnect(const uint &session_id, const uint &subaccount_index);
static void fill_trade_record(emumba::market_data::trade_record & trade_record,
const rapidjson::Value::ConstObject &user_trade_message);
private:
void process_response_msg(const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result);
void process_order_state(emumba::order_entry::order_state &order_state, const bool &is_final_callback = false);
void process_request_rejection();
void process_trade(const rapidjson::Value &order_trades);
double round_two(const double &value);
void remove_order_if_complete(const uint64_t &label_order_id);
const bool is_internal_order_id_active(const std::uint64_t &internal_order_id); // Return true if we're tracking
struct trade_detail
{
double price;
double value;
};
struct last_trade_info
{
std::string state;
std::string order_id;
double price;
};
struct order_detail
{
uint session_id; // Used to track session from which this order was placed.
std::string order_id;
std::string instrument_name;
std::string direction;
last_trade_info last_trade;
emumba::order_entry::status state = emumba::order_entry::status::working;
// filled amount, timestamp and avg_price is calculated using trade updates. The avg price value is assigned using
// order subscription msg when order is completed
uint64_t timestamp = 0;
double filled_amount = 0;
double total_filled_amount = 0;
double total_amount = 0;
double avg_price = 0;
double price = 0;
double sum_of_trade_values = 0;
bool is_order_confirm = false;
bool is_order_complete = false;
bool is_order_response_received = false;
bool is_order_sub_msg_received = false;
bool is_trade_sub_msg_received = false;
std::vector<std::string> trade_ids; // List of trade IDs for which callback has been called
std::vector<trade_detail> trades_detail; // List containing trade price & value of each trade
std::list<emumba::market_data::trade_record>
trade_records_list; // Store data of those trades which are passed to the user in the callbacks
};
deribit_listener * _trading_listener;
emumba::order_entry::order_state _order_state;
emumba::order_entry::deribit_order_state _deribit_order_state;
emumba::order_entry::order_key _order_key;
emumba::market_data::trade_record _trade_record;
std::string _uuid;
std::string _empty_string;
std::string _label_value;
std::string _label_uuid;
std::string _trade_id;
std::uint64_t _label_order_id;
std::uint64_t _internal_order_id;
char _label[60];
uint8_t _uuid_length;
double _rounded_value;
double _avg_price;
trade_detail _trade_detail;
// In user trade updates, the following variable is used to track those order ids whose trade callbacks are already
// called but order detail structure has not been deleted yet.
std::unordered_set<uint64_t /*internal order_id*/> _order_ids_without_trades;
// Store internal order ids which will be removed incase of disconnection
std::unordered_set<uint64_t /*internal order_id*/> _order_ids_to_remove;
std::unordered_map<uint64_t /*internal order_id*/, emumba::order_entry::order_state /*order state update*/>
_order_states;
std::unordered_map<uint64_t /*internal order_id*/, order_detail /*order_detail*/> _order_details;
};
} // namespace deribit_connector
} // namespace emumba
#endif
@@ -0,0 +1,278 @@
#include "deribit_utils.h"
#include <assert.h>
#include <cstdint>
#include <limits>
namespace emumba::market_data {
#ifdef PRICE_AS_DOUBLE
inline constexpr double price::to_double() const noexcept
{
return _price;
}
inline price price::from_double(double price) noexcept
{
return {price};
}
#else
#define IS_NAN(x) (x == std::numeric_limits<std::int64_t>::min())
#define EXPONENT -9
inline constexpr price::price(std::int64_t mantissa, UNUSED_PARAM std::int8_t exponent) noexcept
: _mantissa(mantissa)
{
assert(exponent == price::exponent());
}
inline price price::from_double(double price) noexcept
{
if (std::isnan(price))
{
return nan();
}
double temp; // NOLINT
if (fabs(modf(price, &temp)) != 0.0)
{
const double rounding = 0.5 * _pow10();
price += (price > 0) ? rounding : -rounding;
price *= __pow10();
return {(std::int64_t)price};
}
return {(std::int64_t)(price * __pow10())};
}
inline constexpr double price::to_double() const noexcept
{
return is_nan() ? std::numeric_limits<double>::quiet_NaN() : _mantissa * _pow10();
}
inline constexpr float price::to_float() const noexcept
{
return is_nan() ? std::numeric_limits<float>::quiet_NaN() : _mantissa * _pow10();
}
inline constexpr price price::zero() noexcept
{
return price(std::int64_t(0));
}
inline constexpr price price::nan() noexcept
{
return price(NAN_SENTINEL);
}
inline constexpr std::int8_t price::exponent() noexcept
{
return EXPONENT;
}
inline constexpr std::int64_t price::mantissa() const noexcept
{
return _mantissa;
}
inline constexpr price price::abs() const noexcept
{
if (_mantissa >= 0)
{
return *this;
}
return price{-_mantissa};
}
inline price price::square() const noexcept
{
auto d = to_double();
return from_double(d * d);
}
inline constexpr bool price::is_nan() const noexcept
{
return IS_NAN(_mantissa);
}
inline constexpr bool price::is_better(side s, price p2) const noexcept
{
if (is_nan())
{
return false;
}
if (p2.is_nan())
{
return true;
}
if (s == side::buy)
{
return *this > p2;
}
return *this < p2;
}
inline constexpr bool price::is_worse(side s, price p2) const noexcept
{
return p2.is_better(s, *this);
}
inline constexpr bool price::is_worse_or_equal(side s, price p2) const noexcept
{
return p2.is_better_or_equal(s, *this);
}
inline constexpr bool price::is_better_or_equal(side s, price p2) const noexcept
{
return is_better_or_equal(s, *this, p2);
}
inline constexpr bool price::is_better_or_equal(side s, price p1, price p2) noexcept
{
if (p1.is_nan())
{
// This case is always false (even if p2 is nan), because nan != nan.
return false;
}
if (p2.is_nan())
{
return true;
}
if (s == side::buy)
{
return p1 >= p2;
}
return p1 <= p2;
}
inline double price::_pow10() noexcept
{
return std::pow(10.0, exponent());
}
inline double price::__pow10() noexcept
{
return std::pow(10.0, -exponent());
}
inline constexpr price price::operator-() const noexcept
{
if (is_nan())
{
return *this;
}
return {-_mantissa};
}
// If a number is nan, all comparisons are false. This is well-established
// behavior; even nan == nan evaluates to false.
// NOLINTNEXTLINE
#define PRICE_PRICE_COMPARISON(op, nan_part) \
inline constexpr bool price::operator op(price factor) const noexcept \
{ \
return nan_part _mantissa op factor._mantissa; \
}
// NOLINTNEXTLINE
#define PRICE_PRICE_BIN_OP(op) \
inline constexpr price price::operator op(price factor) const noexcept \
{ \
if (is_nan() || factor.is_nan()) \
{ \
return nan(); \
} \
return _mantissa op factor._mantissa; \
}
// NOLINTNEXTLINE
#define PRICE_PRICE_BIN_REF_OP(op) \
inline constexpr price &price::operator op(price factor) noexcept \
{ \
if (factor.is_nan()) \
{ \
_mantissa = nan()._mantissa; \
} \
else if (!is_nan()) \
{ \
_mantissa op factor._mantissa; \
} \
return *this; \
}
// NOLINTNEXTLINE
#define PRICE_INT64_COMPARISON(op, nan_part) \
inline constexpr bool price::operator op(std::int64_t factor) const noexcept \
{ \
return nan_part _mantissa op factor; \
}
// NOLINTNEXTLINE
#define PRICE_INT64_BIN_OP(op) \
inline constexpr price price::operator op(std::int64_t factor) const noexcept \
{ \
if (is_nan()) \
{ \
return *this; \
} \
return _mantissa op factor; \
}
// NOLINTNEXTLINE
#define PRICE_INT64_BIN_REF_OP(op) \
inline constexpr price &price::operator op(std::int64_t factor) noexcept \
{ \
if (!is_nan()) \
{ \
_mantissa op factor; \
} \
return *this; \
}
PRICE_PRICE_COMPARISON(!=, is_nan() ||)
PRICE_PRICE_COMPARISON(==, !is_nan() &&)
PRICE_PRICE_COMPARISON(<, !is_nan() &&)
PRICE_PRICE_COMPARISON(<=, !is_nan() &&)
PRICE_PRICE_COMPARISON(>, !is_nan() &&)
PRICE_PRICE_COMPARISON(>=, !is_nan() &&)
PRICE_PRICE_BIN_OP(+)
PRICE_PRICE_BIN_OP(-)
PRICE_PRICE_BIN_OP(*)
PRICE_PRICE_BIN_OP(/)
PRICE_PRICE_BIN_REF_OP(+=)
PRICE_PRICE_BIN_REF_OP(-=)
PRICE_PRICE_BIN_REF_OP(*=)
PRICE_PRICE_BIN_REF_OP(/=)
PRICE_INT64_COMPARISON(!=, is_nan() ||)
PRICE_INT64_COMPARISON(==, !is_nan() &&)
PRICE_INT64_COMPARISON(<, !is_nan() &&)
PRICE_INT64_COMPARISON(<=, !is_nan() &&)
PRICE_INT64_COMPARISON(>, !is_nan() &&)
PRICE_INT64_COMPARISON(>=, !is_nan() &&)
PRICE_INT64_BIN_OP(+)
PRICE_INT64_BIN_OP(-)
PRICE_INT64_BIN_OP(*)
PRICE_INT64_BIN_OP(/)
PRICE_INT64_BIN_REF_OP(+=)
PRICE_INT64_BIN_REF_OP(-=)
PRICE_INT64_BIN_REF_OP(*=)
PRICE_INT64_BIN_REF_OP(/=)
inline std::ostream &operator<<(std::ostream &out, price p) noexcept
{
out << p.to_double();
return out;
}
// Undefine all macros because this file is included in the .h
#undef NAN_SENTINEL
#undef IS_NAN
#undef PRICE_PRICE_COMPARISON
#undef PRICE_PRICE_BIN_OP
#undef PRICE_PRICE_BIN_REF_OP
#undef PRICE_INT64_COMPARISON
#undef PRICE_INT64_BIN_OP
#undef PRICE_INT64_BIN_REF_OP
#endif
} // namespace emumba::market_data
@@ -0,0 +1,179 @@
/**
* @file price.h
* @brief Provides a class used to store and calculate the price value.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*
*/
#ifndef EMUMBA_PRICE_H
#define EMUMBA_PRICE_H
#include <cmath>
#include <ostream>
#include <cstdint>
namespace emumba {
namespace market_data {
/**
* @brief Enum class used for side i.e. Buy(Bid) or Sell(Ask).
*
*/
enum class side
{
buy, /**< Value = 0. Buy/Bid. */
sell, /**< Value = 1. Sell/Ask. */
none /**< Value =2. None/Zero*/
};
#define NAN_SENTINEL std::numeric_limits<std::int64_t>::min()
/**
* @brief Handle the price.
*
*/
class price
{
public:
constexpr price()
: _mantissa(NAN_SENTINEL){};
#ifdef PRICE_AS_DOUBLE
price(double price)
: _price(price)
{}
constexpr double to_double() const noexcept;
static price from_double(double) noexcept;
#else
price(double price)
{
if (std::isnan(price))
{
_mantissa = NAN_SENTINEL;
}
double temp; // NOLINT
if (fabs(modf(price, &temp)) != 0.0)
{
const double rounding = 0.5 * _pow10();
price += (price > 0) ? rounding : -rounding;
price *= __pow10();
_mantissa = (std::int64_t)price;
}
else
{
_mantissa = (std::int64_t)(price * __pow10());
}
}
constexpr price(std::int64_t mantissa, std::int8_t exponent = price::exponent()) noexcept;
constexpr price(price const &) = default;
static price from_double(double) noexcept;
constexpr double to_double() const noexcept;
constexpr float to_float() const noexcept;
static constexpr price zero() noexcept;
static constexpr price nan() noexcept;
constexpr price abs() const noexcept;
price square() const noexcept;
price improve(side s, price by) const noexcept
{
if (s == side::buy)
{
return *this + by;
}
else
{
return *this - by;
}
}
price worsen(side s, price by) const noexcept
{
if (s == side::buy)
{
return *this - by;
}
else
{
return *this + by;
}
}
constexpr bool is_nan() const noexcept;
constexpr bool is_better(side s, price p2) const noexcept;
constexpr bool is_worse(side s, price p2) const noexcept;
constexpr bool is_better_or_equal(side s, price p2) const noexcept;
constexpr bool is_worse_or_equal(side s, price p2) const noexcept;
static constexpr bool is_better_or_equal(side s, price p1, price p2) noexcept;
price &operator=(price const &) = default;
constexpr price operator-() const noexcept;
constexpr bool operator==(price) const noexcept;
constexpr bool operator!=(price) const noexcept;
constexpr bool operator<(price) const noexcept;
constexpr bool operator<=(price) const noexcept;
constexpr bool operator>(price) const noexcept;
constexpr bool operator>=(price) const noexcept;
constexpr price operator+(price) const noexcept;
constexpr price operator-(price) const noexcept;
constexpr price operator*(price) const noexcept;
constexpr price operator/(price) const noexcept;
constexpr price &operator+=(price) noexcept;
constexpr price &operator-=(price) noexcept;
constexpr price &operator*=(price) noexcept;
constexpr price &operator/=(price) noexcept;
constexpr bool operator==(std::int64_t) const noexcept;
constexpr bool operator!=(std::int64_t) const noexcept;
constexpr bool operator<(std::int64_t) const noexcept;
constexpr bool operator<=(std::int64_t) const noexcept;
constexpr bool operator>(std::int64_t) const noexcept;
constexpr bool operator>=(std::int64_t) const noexcept;
constexpr price operator+(std::int64_t) const noexcept;
constexpr price operator-(std::int64_t) const noexcept;
constexpr price operator*(std::int64_t) const noexcept;
constexpr price operator/(std::int64_t) const noexcept;
constexpr price &operator+=(std::int64_t) noexcept;
constexpr price &operator-=(std::int64_t) noexcept;
constexpr price &operator*=(std::int64_t) noexcept;
constexpr price &operator/=(std::int64_t) noexcept;
constexpr std::int64_t mantissa() const noexcept;
static constexpr std::int8_t exponent() noexcept;
#endif
private:
#ifdef PRICE_AS_DOUBLE
double _price;
#else
std::int64_t _mantissa;
static double _pow10() noexcept;
static double __pow10() noexcept;
#endif
};
#ifndef PRICE_AS_DOUBLE
std::ostream &operator<<(std::ostream &, price) noexcept;
#endif
} // namespace market_data
} // namespace emumba
#include "price.cc"
#endif
@@ -0,0 +1,82 @@
#include "price_level.h"
using namespace emumba::deribit_connector::data_types;
price_level::price_level()
: _price(emumba::market_data::price())
, _amount(0)
{}
price_level::price_level(emumba::market_data::price price, double amount)
: _price(price)
, _amount(amount)
{}
emumba::market_data::price price_level::get_price() const
{
return _price;
}
double price_level::get_amount() const
{
return _amount;
}
void price_level::set_amount(const double &amount)
{
_amount = amount;
}
price_level::iterator::iterator(void *ptr, int side)
{
_book_ptr = (std::map<double, data_types::price_level> *)ptr;
_book_level_itr = _book_ptr->begin();
_current_ptr = &_book_level_itr->second;
_side = side;
}
price_level::iterator::self_type price_level::iterator::operator++()
{
_book_level_itr++;
_current_ptr = &_book_level_itr->second;
return *this;
}
price_level::iterator::self_type price_level::iterator::operator++(int)
{
_book_level_itr++;
_current_ptr = &_book_level_itr->second;
return *this;
}
price_level price_level::iterator::operator*()
{
return *_current_ptr;
}
bool price_level::iterator::operator==(const price_level::iterator::self_type &rhs)
{
return _current_ptr == rhs._current_ptr;
}
bool price_level::iterator::operator!=(const price_level::iterator::self_type &rhs)
{
return _current_ptr != rhs._current_ptr;
}
int price_level::iterator::side()
{
return _side;
}
emumba::market_data::price price_level::iterator::price()
{
return _current_ptr->get_price();
}
double price_level::iterator::quantity()
{
return _current_ptr->get_amount();
}
uint32_t price_level::iterator::order_count()
{
return 0;
}
@@ -0,0 +1,138 @@
/**
* @file price_level.h
* @brief Provides a structure used to handle price levels of an order book.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*/
#ifndef PRICE_LEVEL_H
#define PRICE_LEVEL_H
#include "price.h"
#include <cstdint>
#include <map>
namespace emumba {
namespace deribit_connector {
namespace data_types {
/**
* @brief Represents price level of an order book.
*
*/
class price_level
{
public:
class iterator
{
public:
typedef iterator self_type;
iterator(void *ptr, int side);
/**
* @brief Increments the iterator position
*
* @return self_type
*/
self_type operator++();
/**
* @brief Increments the iterator position
*
* @return self_type
*/
self_type operator++(int);
/**
* @brief Retrieve price level at current position of iterator
*
* @return price_level
*/
price_level operator*();
/**
* @brief Equal operator for iterator
*
* @return bool
*/
bool operator==(const self_type &rhs);
/**
* @brief Not equal operator for iterator
*
* @return bool
*/
bool operator!=(const self_type &rhs);
/**
* @brief Returns side of the book; 0:bid, 1:ask
*
* @return int
*/
int side();
/**
* @brief Returns price object of the book at the current iterator position
*
* @return double
*/
emumba::market_data::price price();
/**
* @brief Returns quantity of the book at the current iterator position
*
* @return double
*/
double quantity();
/**
* @brief Returns order count of the book at the current iterator position, Zero if not available
*
* @return uint32_t
*/
uint32_t order_count();
private:
std::map<double, data_types::price_level> * _book_ptr;
price_level * _current_ptr;
std::map<double, emumba::deribit_connector::data_types::price_level>::iterator _book_level_itr;
int _side;
};
price_level();
price_level(emumba::market_data::price price, double amount);
/**
* @brief Returns the price object of a price level.
*
* @return emumba::market_data::price
*/
emumba::market_data::price get_price() const;
/**
* @brief Returns the amount of a price level.
*
* @return double
*/
double get_amount() const;
/**
* @brief Sets the amount of a price level.
*
* @param amount
*/
void set_amount(const double &amount);
private:
emumba::market_data::price _price;
double _amount;
};
} // namespace data_types
} // namespace deribit_connector
} // namespace emumba
#endif /* PRICE_LEVEL_H */
@@ -0,0 +1,76 @@
#include "rpc_message.h"
#include "spdlogger.h"
emumba::deribit_connector::data_types::rpc_message::rpc_message(const std::string &rpc_msg)
{
_message.Parse<rapidjson::ParseFlag::kParseFullPrecisionFlag>(rpc_msg.c_str());
}
const int emumba::deribit_connector::data_types::rpc_message::get_id() const
{
return (_message.HasMember("id") ? _message["id"].GetInt() : 0);
}
const rapidjson::Value &emumba::deribit_connector::data_types::rpc_message::get_method() const
{
return (_message.HasMember("method") ? _message["method"] : _default_value);
}
const rapidjson::Value &emumba::deribit_connector::data_types::rpc_message::get_result() const
{
return (_message.HasMember("result") ? _message["result"] : _default_value);
}
const rapidjson::Value &emumba::deribit_connector::data_types::rpc_message::get_params() const
{
return (_message.HasMember("params") ? _message["params"] : _default_value);
}
const emumba::deribit_connector::data_types::error emumba::deribit_connector::data_types::rpc_message::get_error()
{
if (is_error())
{
if (_message["error"].IsObject())
{
return emumba::deribit_connector::data_types::error(
_message["error"]["code"].GetInt(), _message["error"]["message"].GetString(),
_message["error"].HasMember("data") ? _convert_document.to_string(_message["error"]["data"])
: _convert_document.to_string(_default_value));
}
else
{
return emumba::deribit_connector::data_types::error(_message["error"].GetInt(), _message["message"].GetString(),
"");
}
}
else
{
return emumba::deribit_connector::data_types::error(0, "Success");
}
}
const bool emumba::deribit_connector::data_types::rpc_message::is_response() const
{
return _message.HasMember("result");
}
const bool emumba::deribit_connector::data_types::rpc_message::is_notification() const
{
return _message.HasMember("method") && (strcmp(_message["method"].GetString(), "subscription") == 0);
}
const bool emumba::deribit_connector::data_types::rpc_message::is_batch() const
{
return false;
}
const bool emumba::deribit_connector::data_types::rpc_message::is_error() const
{
return _message.HasMember("error");
}
const bool emumba::deribit_connector::data_types::rpc_message::has_id() const
{
return _message.HasMember("id");
}
@@ -0,0 +1,39 @@
#ifndef EMUMBA_RPC_MESSAGE_H
#define EMUMBA_RPC_MESSAGE_H
#include "document_to_string.h"
#include "error.h"
#include "rapidjson/document.h"
#include <string>
namespace emumba {
namespace deribit_connector {
namespace data_types {
class rpc_message
{
public:
rpc_message(const std::string &rpc_msg);
const int get_id() const;
const rapidjson::Value & get_method() const;
const rapidjson::Value & get_result() const;
const rapidjson::Value & get_params() const;
const emumba::deribit_connector::data_types::error get_error();
const bool is_response() const;
const bool is_notification() const;
const bool is_batch() const;
const bool is_error() const;
const bool has_id() const;
private:
rapidjson::Value _default_value;
rapidjson::Document _message;
document_to_string _convert_document;
};
} // namespace data_types
} // namespace deribit_connector
} // namespace emumba
#endif /* EMUMBA_RPC_MESSAGE_H */
@@ -0,0 +1,424 @@
#include "session.h"
#ifdef TRACEPOINTS
#include "NanoLogCpp17.h"
#endif // TRACEPOINTS
#include "deribit_exception.h"
#include "method_identifiers.h"
#include "spdlogger.h"
#include <rapidjson/istreamwrapper.h>
#include <rapidjson/ostreamwrapper.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
using std::string;
emumba::deribit_connector::session::session(
emumba::connector::io_handler &io, uint id)
: _socket(io, id)
#ifndef ENABLE_BOOST_WEBSOCKET
,
_rate_limit_timer(io)
#endif
,
_id(id), _reconnection(false), _jsonrpc_version("2.0") {
_socket.set_connect_callback(std::bind(&session::send_auth_message, this));
_socket.set_receive_callback(std::bind(&session::process_response, this, std::placeholders::_1));
_socket.set_close_callback(std::bind(&session::on_close, this));
#ifndef ENABLE_BOOST_WEBSOCKET
_num_of_requests = 9;
_rate_limit_timer.set_cb(std::bind(&session::on_rate_timer_expiry, this));
_rate_limit_timer.set_interval(1);
_rate_limit_timer.start();
#endif
}
#ifndef ENABLE_BOOST_WEBSOCKET
void emumba::deribit_connector::session::on_rate_timer_expiry()
{
LOG_TRACE("Rate timer exhausted | Sending buffered message on socket id: {}", _id);
if (_state == state::connected)
{
_num_of_requests = 9;
auto msg_itr = _buffered_messages.begin();
while (_num_of_requests != 0 && msg_itr != _buffered_messages.end())
{
_socket.send(msg_itr->c_str());
msg_itr = _buffered_messages.erase(msg_itr);
_num_of_requests--;
}
}
_rate_limit_timer.reset();
}
#endif
void emumba::deribit_connector::session::set_session_params(const std::string & uri_in,
const configuration::trading_session_config &trading_config,
const configuration::credential_config & cred_config)
{
_websocket_uri = uri_in;
_client_name = cred_config.client_name;
_client_id = cred_config.client_id;
_client_secret = cred_config.secret_key;
_interval = trading_config.heartbeat_interval;
_state = state::not_connected;
_reconnect_config = trading_config.reconnect_config;
LOG_TRACE(
"In set_session_params | reconnection config set | retries count: {}, retry interval: {} | credential config set | "
"client id: {} | secret key: {}",
_reconnect_config.retry_count, _reconnect_config.retry_interval, cred_config.client_id, cred_config.secret_key);
create_heartbeat_messages();
}
void emumba::deribit_connector::session::set_on_receive_callback(on_receive_cb_t fn_in)
{
on_receive_cb = fn_in;
}
void emumba::deribit_connector::session::set_on_reconnect_callback(on_reconnect_cb_t fn_in)
{
on_reconnect_cb = fn_in;
}
void emumba::deribit_connector::session::set_on_error_callback(on_error_cb_t fn_in)
{
on_error_cb = fn_in;
}
void emumba::deribit_connector::session::set_on_print_book_callback(on_print_book_cb_t fn_in)
{
on_print_book_cb = fn_in;
}
#ifndef ENABLE_BOOST_WEBSOCKET
const bool &emumba::deribit_connector::session::is_reception_active() const
{
return _reception_active;
}
void emumba::deribit_connector::session::clear_reception_state()
{
_reception_active = false;
}
#endif
void emumba::deribit_connector::session::print_book()
{
if (on_print_book_cb)
{
on_print_book_cb();
}
}
void emumba::deribit_connector::session::create_heartbeat_messages()
{
_str_buff.Clear();
_writer.Reset(_str_buff);
_writer.StartObject();
_writer.Key("jsonrpc");
_writer.String(_jsonrpc_version);
_writer.Key("id");
_writer.Uint64((int)method_id::set_heartbeat);
_writer.Key("method");
_writer.String(set_heartbeat.c_str());
_writer.Key("params");
_writer.StartObject();
_writer.Key("interval");
_writer.Int64(_interval);
_writer.EndObject();
_writer.EndObject();
strcpy(_message_set_heartbeat, _str_buff.GetString());
_str_buff.Clear();
_writer.Reset(_str_buff);
_writer.StartObject();
_writer.Key("jsonrpc");
_writer.String(_jsonrpc_version);
_writer.Key("id");
_writer.Uint64((int)method_id::test);
_writer.Key("method");
_writer.String(test.c_str());
_writer.EndObject();
strcpy(_message_test, _str_buff.GetString());
}
void emumba::deribit_connector::session::start_session()
{
LOG_INFO("Connecting to websocket on socket id: {}", _id);
_number_of_tries = 3;
_socket.connect(_websocket_uri);
_state = state::connecting;
}
void emumba::deribit_connector::session::poll()
{
#ifdef ENABLE_BOOST_WEBSOCKET
_socket.poll();
#endif
}
void emumba::deribit_connector::session::process_response(std::string response)
{
#ifdef TRACEPOINTS
_timer->start();
#endif // TRACEPOINTS
#ifndef ENABLE_BOOST_WEBSOCKET
_reception_active = true;
#endif
data_types::rpc_message rpc_msg_received(response);
if (likely(!rpc_msg_received.is_error()))
{
if (__builtin_expect(_state == state::connected, true))
{
if (__builtin_expect(rpc_msg_received.is_notification(), true))
{
on_receive_cb(rpc_msg_received.get_error(), rpc_msg_received);
}
else if (rpc_msg_received.has_id() == 0) // no id present; indicates that the response is a heartbeat
{
if (strncmp(_convert_document.to_string(rpc_msg_received.get_method()).c_str(), "\"heartbeat\"", 11) == 0)
{
#ifndef ENABLE_BOOST_WEBSOCKET
LOG_TRACE("Heartbeat message received, resetting heartbeat timeout");
#endif
LOG_DEBUG("Received Test Request Message on socket id: {}", _id);
_socket.send(_message_test);
}
}
else if (rpc_msg_received.get_id() == (int)method_id::test)
{
LOG_TRACE("Received response to test message on socket id: {}", _id);
}
else
{
on_receive_cb(rpc_msg_received.get_error(), rpc_msg_received);
}
}
else
{
int id = rpc_msg_received.get_id();
if (id == (int)method_id::auth)
{
const rapidjson::Value &result = rpc_msg_received.get_result();
_access_token = result["access_token"].GetString();
_refresh_token = result["refresh_token"].GetString();
LOG_DEBUG("Received Auth response on socket id: {}, access_token: {}, refresh_token: {}", _id, _access_token,
_refresh_token);
_socket.send(_message_set_heartbeat);
}
else if (id == (int)method_id::set_heartbeat)
{
LOG_DEBUG("Received Set Heartbeat Ack on socket id: {}", _id);
_state = state::connected;
if (_reconnection)
{
if (on_reconnect_cb)
{
on_reconnect_cb();
}
_reconnection = false;
}
LOG_INFO("Session: {} is Ready", _id);
for (auto msg : _buffered_messages)
{
LOG_DEBUG("Sending buffered message: {} on socket id: {}", msg, _id);
_socket.send(msg.c_str());
}
_buffered_messages.clear();
}
else if (id == (int)method_id::test)
{
LOG_TRACE("Received response to test message on socket id: {}", _id);
}
else if (std::strncmp(_convert_document.to_string(rpc_msg_received.get_method()).c_str(), "\"heartbeat\"", 11) == 0)
{
LOG_DEBUG("Received Test Request Message on socket id: {}", _id);
_socket.send(_message_test);
}
else
{
on_receive_cb(rpc_msg_received.get_error(), rpc_msg_received);
}
}
}
else
{
auto ec = rpc_msg_received.get_error();
LOG_ERROR("Error response in jsonrpc on socket id: {}. Error code: {}, Error message: {}, Error details: {}", _id,
ec.value(), ec.message(), ec.details());
if (rpc_msg_received.get_id() == (int)method_id::auth)
{
if (ec.value() == 13004)
{
LOG_ERROR("Authentication Failed | session id: {}", _id);
}
else if (ec.value() == 11050)
{
LOG_ERROR("URI Failed | session id: {}", _id);
}
else
{
LOG_ERROR("Couldn't complete auth process for session id: {}. Check configuration", _id);
}
}
else
{
on_receive_cb(ec, rpc_msg_received);
}
}
}
void emumba::deribit_connector::session::send_auth_message()
{
_str_buff.Clear();
_writer.Reset(_str_buff);
_writer.StartObject();
_writer.Key("jsonrpc");
_writer.String(_jsonrpc_version);
_writer.Key("id");
_writer.Uint64((int)method_id::auth);
_writer.Key("method");
_writer.String(auth.c_str());
_writer.Key("params");
_writer.StartObject();
_writer.Key("grant_type");
_writer.String("client_credentials");
_writer.Key("client_id");
_writer.String(_client_id.c_str());
_writer.Key("client_secret");
_writer.String(_client_secret.c_str());
_writer.EndObject();
_writer.EndObject();
LOG_TRACE(
"Socket Connected: Sending auth message: {}",
string{_str_buff.GetString()});
_socket.send(_str_buff.GetString());
}
void emumba::deribit_connector::session::send_message(const char *msg_buff)
{
if (_state == state::connected)
{
#ifndef ENABLE_BOOST_WEBSOCKET
if (_num_of_requests > 0)
{
#endif
_socket.send(msg_buff);
#ifndef ENABLE_BOOST_WEBSOCKET
_num_of_requests--;
}
else
{
LOG_DEBUG(
"Internal Rate limit reached | Buffering message: {} on socket "
"id: {}",
string{msg_buff}, _id);
_buffered_messages.push_back(std::string(msg_buff));
}
#endif
}
else if (_state == state::connecting)
{
LOG_DEBUG(
"Buffering message: {} on socket id: {}", string{msg_buff}, _id);
_buffered_messages.push_back(std::string(msg_buff));
}
else if (_state == state::not_connected)
{
_buffered_messages.push_back(std::string(msg_buff));
LOG_DEBUG("Disconnection detected. Trying to connect again on socket id: {}", _id);
_state = state::connecting;
_number_of_tries = _reconnect_config.retry_count;
bool is_connected = false;
while (_number_of_tries != 0 && !is_connected)
{
is_connected = _socket.socket_reconnect(_websocket_uri);
if (is_connected == false)
{
sleep(_reconnect_config.retry_interval);
}
--_number_of_tries;
}
if (is_connected)
{
LOG_DEBUG("Reconnection for session id {} successful", _id);
_state = state::connecting;
_reconnection = true;
}
else
{
LOG_ERROR("Cannot connect to server. Closing application");
sleep(1);
exit(1);
}
}
}
void emumba::deribit_connector::session::on_close()
{
emumba::market_data::market_data_error error;
error.err_type = emumba::market_data::market_data_error::error_type::disconnection;
error.err_text = "Disconnection Detected";
if (on_error_cb)
on_error_cb(error);
LOG_DEBUG("Disconnection detected. Trying to connect again on socket id: {}", _id);
_state = state::connecting;
_number_of_tries = _reconnect_config.retry_count;
bool is_connected = false;
while (_number_of_tries != 0 && !is_connected)
{
is_connected = _socket.socket_reconnect(_websocket_uri);
if (is_connected == false)
{
sleep(_reconnect_config.retry_interval);
}
--_number_of_tries;
}
if (is_connected)
{
LOG_DEBUG("Reconnection for session id {} successful", _id);
if (on_reconnect_cb)
{
on_reconnect_cb();
}
}
else
{
LOG_ERROR("Cannot connect to server. Closing application");
sleep(1);
exit(1);
}
}
const emumba::deribit_connector::session::state &emumba::deribit_connector::session::get_state() const
{
return _state;
}
const uint &emumba::deribit_connector::session::get_session_id() const
{
return _id;
}
#ifdef TRACEPOINTS
void emumba::deribit_connector::session::set_timer_reference(emumba::utils::timer *timer)
{
_timer = timer;
}
#endif
@@ -0,0 +1,139 @@
#ifndef SESSION_H
#define SESSION_H
#include "error.h"
#include "rpc_message.h"
#ifdef TRACEPOINTS
#include "timer.h"
#endif
#include "document_to_string.h"
#include "market_data_common.h"
#include "utils.h"
#include "websocket.h"
#include <functional>
#include <cstring>
#include <string>
#include <vector>
#ifndef ENABLE_BOOST_WEBSOCKET
#include "epoll_timer.h"
#include "io_handler.h"
#endif
typedef std::function<void(const emumba::deribit_connector::data_types::error &,
const emumba::deribit_connector::data_types::rpc_message &)>
on_receive_cb_t;
typedef std::function<void()> on_reconnect_cb_t;
typedef std::function<void()> on_print_book_cb_t;
typedef std::function<void(const emumba::market_data::market_data_error &)> on_error_cb_t;
namespace emumba {
namespace deribit_connector {
class session
{
public:
enum state
{
not_connected,
connecting,
connected
};
session(emumba::connector::io_handler &io, uint id);
void set_session_params(const std::string &uri_in, const configuration::trading_session_config &trading_config,
const configuration::credential_config &cred_config);
void set_on_receive_callback(on_receive_cb_t fn_in);
void set_on_reconnect_callback(on_reconnect_cb_t fn_in);
void set_on_print_book_callback(on_print_book_cb_t fn_in);
void set_on_error_callback(on_error_cb_t fn_in);
#ifndef ENABLE_BOOST_WEBSOCKET
const bool &is_reception_active() const;
void clear_reception_state();
void on_rate_timer_expiry();
#endif
void print_book();
void start_session();
void poll();
void send_message(const char *msg_buff);
void on_close();
const state &get_state() const;
const uint & get_session_id() const;
#ifdef TRACEPOINTS
void set_timer_reference(emumba::utils::timer *timer);
#endif
private:
on_receive_cb_t on_receive_cb;
on_reconnect_cb_t on_reconnect_cb;
on_print_book_cb_t on_print_book_cb;
on_error_cb_t on_error_cb;
std::string _websocket_uri;
std::string _client_name;
std::string _client_id;
std::string _client_secret;
int _interval;
std::vector<std::string> _buffered_messages;
emumba::connector::websocket::client _socket;
emumba::deribit_connector::configuration::reconnection_config _reconnect_config;
#ifndef ENABLE_BOOST_WEBSOCKET
bool _reception_active;
uint8_t _num_of_requests;
emumba::connector::epoll_timer _rate_limit_timer;
#endif
char _message_set_heartbeat[100];
char _message_test[100];
state _state;
uint8_t _number_of_tries;
uint _id;
std::string _access_token;
std::string _refresh_token;
document_to_string _convert_document;
bool _reconnection;
rapidjson::StringBuffer _str_buff;
rapidjson::Writer<rapidjson::StringBuffer> _writer;
char _jsonrpc_version[4];
#ifdef TRACEPOINTS
emumba::utils::timer *_timer;
#endif // TRACEPOINTS
private:
void create_heartbeat_messages();
/**
* @brief Parses the received response, constructs the json message to be sent by
* client application and returns it as string.
*
* @par This function gets called when:
* A response is received on the websocket and the application wants to send a new request.
* The function creates the json message to be sent and returns it in std::string format.
*
* @return std::string Message to be sent on the websocket
* @param resp_payload Received message in raw string form.
*/
void process_response(std::string resp_payload);
/**
* @brief Send the authentication message upon successful connection to the exchange websocket
*
* @return std::string
*/
void send_auth_message();
/**
* @brief Called at the end of a websocket transmission. Used in case the creator of this session
* wants to send packets continuously without waiting for reception.
*
* @return std::string
*/
const std::string auth = "public/auth";
const std::string set_heartbeat = "public/set_heartbeat";
const std::string test = "public/test"; // set these value in constructor
};
} // namespace deribit_connector
} // namespace emumba
#endif /* SESSION_H */
@@ -0,0 +1,134 @@
#include "session_manager.h"
#include <algorithm>
#ifdef TRACEPOINTS
#include "NanoLogCpp17.h"
#endif // TRACEPOINTS
#include "method_identifiers.h"
#include "spdlogger.h"
extern std::atomic<int> print_books;
emumba::deribit_connector::session_manager::session_manager(const std::string & uri_in,
const configuration::trading_session_config &trading_config)
: _websocket_uri(uri_in)
, _trading_config(trading_config)
#ifndef ENABLE_BOOST_WEBSOCKET
, _reconnection_timer(_io)
#endif
{
_id = 0;
#ifdef TRACEPOINTS
init_nanolog();
#endif
if (trading_config.heartbeat_interval < 10)
{
LOG_WARNING("Heartbeat interval value less than 10. Value: {}. Using default interval value (10) for heartbeats.",
trading_config.heartbeat_interval);
_trading_config.heartbeat_interval = 10;
}
#ifndef ENABLE_BOOST_WEBSOCKET
_reconnection_timer.set_cb(std::bind(&session_manager::on_timeout, this));
_reconnection_timer.set_interval(_trading_config.reconnect_config.reception_timeout);
_reconnection_timer.start();
#endif
}
void emumba::deribit_connector::session_manager::init_nanolog()
{
#ifdef TRACEPOINTS
NanoLog::setLogFile("./performance-logs");
NanoLog::preallocate();
NanoLog::setLogLevel(NanoLog::NOTICE);
#endif
}
std::shared_ptr<emumba::deribit_connector::session> emumba::deribit_connector::session_manager::create_new_session(
const configuration::credential_config &cred_config, on_receive_cb_t session_receive_cb)
{
std::shared_ptr<session> new_session = std::make_shared<session>(_io, ++_id);
new_session->set_session_params(_websocket_uri, _trading_config, cred_config);
new_session->set_on_receive_callback(session_receive_cb);
new_session->start_session();
active_session_list.push_back(new_session);
return new_session;
}
void emumba::deribit_connector::session_manager::run()
{
while (1)
{
if (__builtin_expect(print_books.load() == 0, true))
{
#ifdef ENABLE_BOOST_WEBSOCKET
for (auto &it : active_session_list)
{
it->poll();
}
#else
_io.poll();
#endif
}
else
{
#ifdef ENABLE_BOOST_WEBSOCKET
for (auto &it : active_session_list)
{
it->poll();
it->print_book();
}
#else
_io.poll();
for (auto &it : active_session_list)
{
it->print_book();
}
#endif
print_books.store(0);
}
}
}
uint emumba::deribit_connector::session_manager::get_core_id()
{
return _trading_config.core;
}
const bool &emumba::deribit_connector::session_manager::get_cancel_on_disconnect() const
{
// TODO: Can be removed as cancel on disconnect is no longer configurable
return _trading_config.cancel_on_disconnect;
}
emumba::connector::io_handler &emumba::deribit_connector::session_manager::get_io()
{
return _io;
}
#ifndef ENABLE_BOOST_WEBSOCKET
void emumba::deribit_connector::session_manager::on_timeout()
{
for (auto &it : active_session_list)
{
if (it->is_reception_active() == false)
{
LOG_ERROR("No reception on session {} | Connection loss detected | Trying to reconnect this session.",
it->get_session_id());
it->on_close();
}
else
{
LOG_TRACE("Session {} is in correct reception state", it->get_session_id());
it->clear_reception_state();
}
}
_reconnection_timer.reset();
}
#endif
@@ -0,0 +1,62 @@
#ifndef SESSION_MANAGER_H
#define SESSION_MANAGER_H
#include "session.h"
#ifndef ENABLE_BOOST_WEBSOCKET
#include "epoll_timer.h"
#include "io_handler.h"
#endif
#include <memory>
#include <string>
#include <vector>
namespace emumba {
namespace deribit_connector {
class session_manager
{
public:
session_manager(const std::string &uri_in, const configuration::trading_session_config &trading_config);
/**
* @brief Creates a new session and returns an integer ID to identify session
*
* @return int id: An ID associated with the session to keep track of multiple
* sessions created by trader_session and market_handler classes.
*/
std::shared_ptr<session> create_new_session(const configuration::credential_config &cred_config,
on_receive_cb_t session_receive_cb);
/**
* @brief Polls all active sessions in an infinite loop. This function subsequently calls
* the underlying websocket's run_one() function of every session instance.
*
*/
void run();
uint get_core_id();
const bool & get_cancel_on_disconnect() const;
emumba::connector::io_handler &get_io();
#ifndef ENABLE_BOOST_WEBSOCKET
void on_timeout();
#endif
private:
uint _id;
std::string _websocket_uri;
configuration::trading_session_config _trading_config;
std::string outbound_message;
#ifndef ENABLE_BOOST_WEBSOCKET
emumba::connector::io_handler _io;
emumba::connector::epoll_timer _reconnection_timer;
#endif
std::vector<std::shared_ptr<session>> active_session_list;
void init_nanolog();
};
} // namespace deribit_connector
} // namespace emumba
#endif /* SESSION_MANAGER_H */
@@ -0,0 +1,83 @@
#include "spdlogger.h"
#include "spdlog/async.h" //support for async logging
#include <iostream>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
void emumba::logger::set_spdlog_filter(const std::string &svl)
{
// set severity:
if (svl == "trace")
spdlog::set_level(spdlog::level::trace);
else if (svl == "debug")
spdlog::set_level(spdlog::level::debug);
else if (svl == "info")
spdlog::set_level(spdlog::level::info);
else if (svl == "warning")
spdlog::set_level(spdlog::level::warn);
else if (svl == "error")
spdlog::set_level(spdlog::level::err);
else
spdlog::set_level(spdlog::level::info);
}
void emumba::logger::init_spdlog(const std::string &svl, bool isConsoleEnabled, bool isFileEnabled)
{
uint32_t queue_size = 8192;
spdlog::init_thread_pool(queue_size, 1);
try
{
if (isFileEnabled && isConsoleEnabled)
{
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("emumba-deribit-connector.log");
std::vector<spdlog::sink_ptr> sinks{console_sink, file_sink};
auto _emumba_logger =
std::make_shared<spdlog::async_logger>("emumba_logger", sinks.begin(), sinks.end(), spdlog::thread_pool(),
spdlog::async_overflow_policy::overrun_oldest);
spdlog::set_default_logger(_emumba_logger);
// set severity:
set_spdlog_filter(svl);
}
else if (isFileEnabled)
{
auto _emumba_logger =
spdlog::basic_logger_mt<spdlog::async_factory>("emumba_logger", "emumba-deribit-connector.log");
spdlog::set_default_logger(_emumba_logger);
// set severity:
set_spdlog_filter(svl);
}
else if (isConsoleEnabled)
{
auto _emumba_logger = spdlog::stdout_color_mt<spdlog::async_factory>("emumba_logger");
spdlog::set_default_logger(_emumba_logger);
// set severity:
set_spdlog_filter(svl);
}
else
{
spdlog::set_level(spdlog::level::off);
}
// spdlog::flush_every(std::chrono::seconds(3));
spdlog::flush_on(spdlog::level::debug);
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
}
}
bool emumba::logger::flush_logs()
{
try
{
spdlog::get("emumba_logger")->flush();
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Exception: " << ex.what() << std::endl;
return false;
}
return true;
}
@@ -0,0 +1,73 @@
/**
* @file spdlogger.h
* @brief Provides MACROS for logging. Also contains functions to configure
* logging.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned
* by Emumba (Pvt) Ltd [Emumba] and is protected by copyright law and
* international copyright treaties. Access to and use of the software is
* governed by the terms of the applicable Emumba Software Services Agreement
* (the Agreement) and Customer end user license agreements granting a
* non-assignable, non-transferable and non-exclusive license to use the
* software for it's own data processing purposes under the terms defined in the
* Agreement. Except as otherwise granted within the terms of the Agreement,
* copying or reproduction of any part of this source code or associated
* reference material to any other location for further reproduction or
* redistribution, and any amendments to this copyright notice, are expressly
* prohibited. Any reproduction or redistribution for sale or hiring of the
* Software not in accordance with the terms of the Agreement is a violation of
* copyright law.
*/
#ifndef EMUMBA_SPDLOGGER_H
#define EMUMBA_SPDLOGGER_H
#include "lib/logging/logging.h"
#include "spdlog/spdlog.h"
#include <string>
// New macro that includes severity, filename and line number
#define LOG_ERROR(...) LOG(OrderMan, bts::Error, __VA_ARGS__)
#define LOG_WARNING(...) LOG(OrderMan, bts::Warn, __VA_ARGS__)
#define LOG_INFO(...) LOG(OrderMan, bts::Info, __VA_ARGS__)
#define LOG_DEBUG(...) LOG(OrderMan, bts::Diag, __VA_ARGS__)
#define LOG_TRACE(...) LOG(OrderMan, bts::Diag, __VA_ARGS__)
namespace emumba {
namespace logger {
// use during runtime to change severity log output.
/**
* @brief Set the severity of logger.
*
* @param svl The serverity level of logger. i.e. trace, debug, info, warning or
* error.
*/
void set_spdlog_filter(const std::string &svl);
/**
* @brief Initializes the logger used by the feedhandler.
*
* @param svl The severity level of logger. i.e. trace, debug, info, warning or
* error.
* @param isConsoleEnabled Flag to determine whether logs should be printed to
* console or not.
* @param isFileEnabled Flag to determine whether logs should be printed to log
* file or not.
*/
void init_spdlog(const std::string &svl, bool isConsoleEnabled, bool isFileEnabled);
/**
* @brief Clears the buffers used by spdlogger.
*
* @return true In case of success.
* @return false In case of failure.
*/
bool flush_logs();
} // namespace logger
} // namespace emumba
#endif /* EMUMBA_SPDLOGGER_H */
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,144 @@
#ifndef SYMBOL_HANDLER_H
#define SYMBOL_HANDLER_H
#include "deribit_listener.h"
#include "deribit_utils.h"
#include "document_to_string.h"
#include "market_data_common.h"
#include "orders_handler.h"
#include "rapidjson/document.h"
#include "rpc_message.h"
#include "session_manager.h"
#include "symbol_instruments.h"
#include "utils.h"
#ifdef TRACEPOINTS
#include "timer.h"
#endif // TRACEPOINTS
namespace emumba {
namespace deribit_connector {
class symbol_handler
{
public:
symbol_handler(const configuration::symbol_config &symbol_configuration, std::shared_ptr<session_manager> manager_ref,
std::shared_ptr<orders_handler> orders_handler, const int &heartbeat_interval,
deribit_listener *trade_listener, const configuration::credential_config &cred_config);
void print_all_books();
std::list<std::string> get_future_expiries(const std::string &underlying_instrument, const int &expiries,
const std::string &expiry_cycle = "week");
std::list<std::string> get_options_expiries(const std::string &underlying_instrument, const int &expiries,
const std::string &expiry_cycle = "week");
const std::string &get_symbol_name() const;
const uint & get_session_id() const;
void reconfigure_instrument_list(const std::list<std::string> instrument_list, const bool &append);
private:
void initialize();
void send_request_get_instruments();
void send_request_get_combos();
void subscribe_to_symbol_channels();
void on_receive(const emumba::deribit_connector::data_types::error &ec, const data_types::rpc_message &received_msg);
void on_reconnect();
void on_error(const emumba::market_data::market_data_error &err);
void timer_cb();
void send_message(const char *send_msg);
void subscribe_to_instrument_state();
void subscribe_to_public_markprice();
void subscribe_to_public_index();
void subscribe_to_private_portfolio();
void unsubscribe_to_public_markprice();
void unsubscribe_to_public_index();
void unsubscribe_to_private_portfolio();
/**
* @brief Processes the subscription messages
*
* @param subscription_message Json structure containing the params object of subscription message
*/
void process_subscription(const data_types::rpc_message &subscription_message);
/**
* @brief Processes the reponse messages
*
* @param response_message Json structure containing the whole response message
*/
void process_response(const emumba::deribit_connector::data_types::error &ec,
const data_types::rpc_message & response_message);
void process_private_book_subscription(const rapidjson::Value &book_message);
void process_public_quote_subscription(const rapidjson::Value &quote_message);
void process_public_trades_subscription(const rapidjson::Value &trades_message);
void process_public_ticker_subscription(const rapidjson::Value &ticker_message);
void process_public_markprice_subscription(const rapidjson::Value &markprice_message);
void process_public_index_subscription(const rapidjson::Value &index_message);
void process_private_user_portfolio_subscription(const rapidjson::Value &user_portfolio_message);
void process_instrument_state_subscription(const rapidjson::Value &instrument_state_message);
void process_get_instruments_response(const emumba::deribit_connector::data_types::error &ec,
const data_types::rpc_message & get_instruments_message);
void process_get_combos_response(const emumba::deribit_connector::data_types::error &ec,
const data_types::rpc_message & get_combos_message);
void process_public_subscribe_response(const emumba::deribit_connector::data_types::error &ec,
const data_types::rpc_message & public_subscribe_message);
void process_private_subscribe_response(const emumba::deribit_connector::data_types::error &ec,
const data_types::rpc_message & private_subscribe_message);
void process_public_unsubscribe_response(const emumba::deribit_connector::data_types::error &ec,
const data_types::rpc_message &public_unsubscribe_message);
void process_private_unsubscribe_response(const emumba::deribit_connector::data_types::error &ec,
const data_types::rpc_message &private_unsubscribe_message);
uint _session_id;
std::string _symbol_name;
std::string _currency_type;
std::string _instrument_types;
instruments_set _interested_instruments;
configuration::symbol_config _symbol_config;
std::unique_ptr<symbol_instruments> _symbol_instruments;
emumba::market_data::market_data_statistics _market_data_statistics;
emumba::market_data::quote _quote;
std::unordered_set<std::string> _interested_instruments_type;
std::string _public_subscribe_method = "public/subscribe";
std::string _public_unsubscribe_method = "public/unsubscribe";
std::string _private_subscribe_method = "private/subscribe";
std::string _private_unsubscribe_method = "private/unsubscribe";
std::shared_ptr<session_manager> _session_manager;
emumba::connector::epoll_timer _epoll_timer;
std::shared_ptr<session> _session;
std::shared_ptr<orders_handler> _orders_handler;
deribit_listener * _trade_listener;
document_to_string _convert_document;
const std::string _jsonrpc;
rapidjson::StringBuffer _str_buff;
rapidjson::Writer<rapidjson::StringBuffer> _writer;
std::vector<instrument_info> _get_instrument_data;
std::map<std::string /*instrument name*/, instrument_info * /*get instrument api info*/> _get_instrument_ref;
std::unordered_map<std::string /*combo name*/, std::vector<legs_info> /*get instrument api info*/> _combos_data;
std::list<emumba::market_data::security> _complete_securities_data;
std::vector<emumba::market_data::security const *> _complete_securities_ref;
std::list<emumba::market_data::trade_record> _trade_records_list;
std::vector<emumba::market_data::trade_record const *> _trade_records_ref;
emumba::market_data::trade_record _trade_rec;
configuration::credential_config _cred_config;
legs_info _legs_info;
instruments_set _instruments_to_add;
instruments_set _instruments_to_remove;
bool _get_inst_req_sent;
bool _is_whitelist_init;
bool _is_timer_start;
#ifdef TRACEPOINTS
emumba::utils::timer _timer;
#endif // TRACEPOINTS
};
} // namespace deribit_connector
} // namespace emumba
#endif /* SYMBOL_HANDLER_H */
@@ -0,0 +1,565 @@
#include "symbol_instruments.h"
#include "deribit_exception.h"
#include "method_identifiers.h"
#include <algorithm>
#include <rapidjson/writer.h>
using std::string;
using namespace emumba::deribit_connector;
symbol_instruments::symbol_instruments(const std::string &symbol_name, const std::shared_ptr<session> session,
const configuration::subscription_config &sub_config,
deribit_listener * trade_listener)
: _symbol_name(symbol_name)
, _subscription_interval("raw")
, _session(session)
, _session_state(_session->get_state()) // TODO: Remove this
, _subscriptions_config(sub_config) // TODO: look into handling of request limit in session class
, _trade_listener(trade_listener)
, _public_subscribe_method("public/subscribe")
, _public_unsubscribe_method("public/unsubscribe")
, _private_subscribe_method("private/subscribe")
, _private_unsubscribe_method("private/unsubscribe")
, _jsonrpc_version("2.0")
, _writer(_str_buff)
, _public_sub_writer(_public_sub_str_buff)
, _private_sub_writer(_private_sub_str_buff)
, _public_subs_in_request_msg(0)
, _private_subs_in_request_msg(0)
, _is_new_public_sub_msg(true)
, _is_new_private_sub_msg(true)
, _public_unsubs_in_request_msg(0)
, _private_unsubs_in_request_msg(0)
, _is_new_public_unsub_msg(true)
, _is_new_private_unsub_msg(true)
{
_str_buff.Reserve(1000);
_public_sub_str_buff.Reserve(1000);
_private_sub_str_buff.Reserve(1000);
if (_subscriptions_config.use_raw_interval)
{
_subscription_interval = "raw";
}
else
{
_subscription_interval = "100ms";
}
_max_whitelist_size = 65535;
_white_list.reserve(_max_whitelist_size);
_all_sub_req_sent = false;
LOG_TRACE("Constructed symbol_instruments object");
}
void symbol_instruments::add_to_whitelist(const std::string &instrument_name, instrument_info *&instr_info)
{
LOG_DEBUG("Initializing Instrument: {}", instrument_name);
_white_list.emplace(std::make_pair(
instrument_name, instrument_data(instrument_name, _subscriptions_config.book_depth, *instr_info, _trade_listener)));
emumba::deribit_connector::instrument_data & inst_data = _white_list.at(instrument_name);
std::shared_ptr<emumba::deribit_connector::instrument_data_interface> interface =
std::make_shared<emumba::deribit_connector::instrument_data_interface>(&inst_data);
inst_data.set_instrument_data_interface(interface);
void *_api_handler = _trade_listener->on_instrument_definition(inst_data.get_name(), interface.get());
inst_data.set_api_handler(_api_handler);
_instruments_to_subscribe.emplace(instrument_name);
}
void symbol_instruments::initialize_instruments(const instruments_set & set_of_instruments,
std::map<std::string, instrument_info *> instruments_info)
{
try
{
if (set_of_instruments.empty() == false)
{
LOG_INFO("Symbol: {}, No of interested instruments: {}, Subscription interval: {}, Book Depth: {}", _symbol_name,
set_of_instruments.size(), _subscription_interval, _subscriptions_config.book_depth);
for (const std::string &instrument_name : set_of_instruments)
{
add_to_whitelist(instrument_name, instruments_info.at(instrument_name));
}
gen_interested_channels_str();
sub_instruments();
}
else
{
LOG_ERROR(
"Whitelist empty. No instrument selected. Possible reason: Invalid instruments specified in "
"\"interested_instruments\" list. Symbol: {}",
_symbol_name);
emumba::market_data::market_data_error mrkt_data_error;
mrkt_data_error.err_type = emumba::market_data::market_data_error::error_type::whitelist_empty;
mrkt_data_error.err_text =
"Whitelist empty. No instrument selected. Possible reason: Invalid instruments specified in "
"\"interested_instruments\" list. Symbol: " +
_symbol_name;
#ifdef ENABLE_API
_trade_listener->on_error(mrkt_data_error);
#endif
throw emumba::deribit_connector::deribit_exception(
emumba::deribit_connector::deribit_exception::error_code::no_instruments);
}
}
catch (emumba::deribit_connector::deribit_exception &deribit_exception)
{
deribit_exception.print();
}
}
void symbol_instruments::add_new_instruments(const instruments_set & set_of_instruments,
std::map<std::string, instrument_info *> instruments_info)
{
_instruments_to_subscribe.clear();
if (set_of_instruments.empty() == false)
{
LOG_DEBUG("New instruments added | Symbol: {}, No of new instruments: {}", _symbol_name, set_of_instruments.size());
for (const std::string &instrument_name : set_of_instruments)
{
if (_white_list.find(instrument_name) == _white_list.end()) // Sanity check
{
add_to_whitelist(instrument_name, instruments_info.at(instrument_name));
LOG_INFO("New Instrument added | Symbol: {}, Instrument: {}", _symbol_name, instrument_name);
}
}
sub_instruments();
}
else
{
LOG_DEBUG("No new instrument added. Symbol: {}", _symbol_name);
}
}
void symbol_instruments::gen_interested_channels_str()
{
_interested_channels_str = "";
if (_subscriptions_config.public_book)
{
_interested_channels_str = _interested_channels_str + " Book,";
}
if (_subscriptions_config.public_quote)
{
_interested_channels_str = _interested_channels_str + " Quote,";
}
if (_subscriptions_config.public_trade)
{
_interested_channels_str = _interested_channels_str + " Trade,";
}
if (_subscriptions_config.public_ticker)
{
_interested_channels_str = _interested_channels_str + " Ticker,";
}
if (_interested_channels_str.empty())
{
_interested_channels_str = " Nil";
}
else
{
_interested_channels_str.pop_back();
}
LOG_INFO("Subscribing channels | Symbol: {} | Configured channels:{}", _symbol_name, _interested_channels_str);
}
void symbol_instruments::send_message(const char *msg_buff)
{
if (_session_state == session::state::connected)
{
// Symbol instruments class sends instrument level subs/unsub messages. In bootup state, this class will send the
// subscription requests once the response for get instruments whitelist is received by the symbol handler which can
// only happen once the session state is connected. In normal state if the session state is not connected, then symbol
// instruments does not need to send sub/unsub messages because once the session state goes back to connected the
// symbol instruments class will get reinitialized.
_session->send_message(msg_buff);
}
else
{
LOG_DEBUG("Session not in connected state. Sub/Unsub message not sent | Symbol: {}", _symbol_name);
}
}
void symbol_instruments::subscribe_to_private_book(const std::string &instrument_name)
{
auto request_string = "book." + instrument_name + "." + _subscription_interval;
_str_buff.Clear();
_writer.Reset(_str_buff);
_writer.StartObject();
_writer.Key("jsonrpc");
_writer.String(_jsonrpc_version);
_writer.Key("method");
_writer.String(_private_subscribe_method.c_str());
_writer.Key("id");
_writer.Uint64((int)method_id::private_subscribe);
_writer.Key("params");
_writer.StartObject();
_writer.Key("channels");
_writer.StartArray();
_writer.String(request_string.c_str());
_writer.EndArray();
_writer.EndObject();
_writer.EndObject();
send_message(_str_buff.GetString());
}
void symbol_instruments::unsubscribe_to_private_book(const std::string &instrument_name)
{
auto request_string = "book." + instrument_name + "." + _subscription_interval;
_str_buff.Clear();
_writer.Reset(_str_buff);
_writer.StartObject();
_writer.Key("jsonrpc");
_writer.String(_jsonrpc_version);
_writer.Key("method");
_writer.String(_private_unsubscribe_method.c_str());
_writer.Key("id");
_writer.Uint64((int)method_id::private_unsubscribe);
_writer.Key("params");
_writer.StartObject();
_writer.Key("channels");
_writer.StartArray();
_writer.String(request_string.c_str());
_writer.EndArray();
_writer.EndObject();
_writer.EndObject();
send_message(_str_buff.GetString());
}
void symbol_instruments::resubscribe_to_private_book(const std::string &instrument_name)
{
LOG_INFO("Resubscribing Order book for Instrument: {}", instrument_name);
_instruments_to_resubscribe.insert(instrument_name);
unsubscribe_to_private_book(instrument_name);
}
bool symbol_instruments::is_instrument_being_resubscribed(const std::string &instrument_name)
{
return _instruments_to_resubscribe.find(instrument_name) != _instruments_to_resubscribe.end();
}
void symbol_instruments::check_if_channel_is_being_resubscribed(const std::string &channel_name)
{
for (auto &each_instrument_name : _instruments_to_resubscribe)
{
if (channel_name.find(each_instrument_name) != std::string::npos)
{
subscribe_to_private_book(each_instrument_name);
_instruments_to_resubscribe.erase(each_instrument_name);
break;
}
}
}
void symbol_instruments::sub_instruments()
{
for (auto &instrument_name : _instruments_to_subscribe)
{
LOG_DEBUG("Generating subscription message for instrument: {}", instrument_name);
gen_subscribe_to_public_channels_msg(instrument_name);
gen_subscribe_to_private_channels_msg(instrument_name);
}
if (_public_subs_in_request_msg != 0)
{
send_subscribe_to_public_channels_msg();
}
if (_private_subs_in_request_msg != 0)
{
send_subscribe_to_private_channels_msg();
}
if (__builtin_expect(_all_sub_req_sent == false, false))
{
LOG_INFO("All Subscription requests sent | Symbol: {}", _symbol_name);
_all_sub_req_sent = true;
}
}
void symbol_instruments::gen_subscribe_to_public_channels_msg(const std::string &instrument_name)
{
std::string channel_name;
if (_is_new_public_sub_msg == true)
{
_is_new_public_sub_msg = false;
_public_sub_str_buff.Clear();
_public_sub_writer.Reset(_public_sub_str_buff);
_public_sub_writer.StartObject();
_public_sub_writer.Key("jsonrpc");
_public_sub_writer.String(_jsonrpc_version);
_public_sub_writer.Key("method");
_public_sub_writer.String(_public_subscribe_method.c_str());
_public_sub_writer.Key("id");
_public_sub_writer.Uint64((int)method_id::public_subscribe);
_public_sub_writer.Key("params");
_public_sub_writer.StartObject();
_public_sub_writer.Key("channels");
_public_sub_writer.StartArray();
}
if (_subscriptions_config.public_quote == true)
{
channel_name = "quote." + instrument_name;
_public_sub_writer.String(channel_name.c_str());
++_public_subs_in_request_msg;
}
if (_subscriptions_config.public_trade == true)
{
channel_name = "trades." + instrument_name + "." + _subscription_interval;
_public_sub_writer.String(channel_name.c_str());
++_public_subs_in_request_msg;
}
if (_subscriptions_config.public_ticker == true)
{
channel_name = "ticker." + instrument_name + "." + _subscription_interval;
_public_sub_writer.String(channel_name.c_str());
++_public_subs_in_request_msg;
}
if (_public_subs_in_request_msg >= 500)
{
send_subscribe_to_public_channels_msg();
}
}
void symbol_instruments::send_subscribe_to_public_channels_msg()
{
_public_sub_writer.EndArray();
_public_sub_writer.EndObject();
_public_sub_writer.EndObject();
LOG_TRACE(
"Symbol: {}, Total public channels: {}, Public subscription message: {}",
_symbol_name, _public_subs_in_request_msg,
string{_public_sub_str_buff.GetString()});
send_message(_public_sub_str_buff.GetString());
_is_new_public_sub_msg = true;
_public_subs_in_request_msg = 0;
}
void symbol_instruments::gen_subscribe_to_private_channels_msg(const std::string &instrument_name)
{
std::string channel_name;
if (_is_new_private_sub_msg == true)
{
_is_new_private_sub_msg = false;
_private_sub_str_buff.Clear();
_private_sub_writer.Reset(_private_sub_str_buff);
_private_sub_writer.StartObject();
_private_sub_writer.Key("jsonrpc");
_private_sub_writer.String(_jsonrpc_version);
_private_sub_writer.Key("method");
_private_sub_writer.String(_private_subscribe_method.c_str());
_private_sub_writer.Key("id");
_private_sub_writer.Uint64((int)method_id::private_subscribe);
_private_sub_writer.Key("params");
_private_sub_writer.StartObject();
_private_sub_writer.Key("channels");
_private_sub_writer.StartArray();
}
if (_subscriptions_config.public_book == true)
{
channel_name = "book." + instrument_name + "." + _subscription_interval;
_private_sub_writer.String(channel_name.c_str());
++_private_subs_in_request_msg;
}
if (_private_subs_in_request_msg >= 500)
{
send_subscribe_to_private_channels_msg();
}
}
void symbol_instruments::send_subscribe_to_private_channels_msg()
{
_private_sub_writer.EndArray();
_private_sub_writer.EndObject();
_private_sub_writer.EndObject();
LOG_TRACE(
"Symbol: {}, Total private channels: {}, Private subscription message: "
"{}",
_symbol_name, _private_subs_in_request_msg,
string{_private_sub_str_buff.GetString()});
send_message(_private_sub_str_buff.GetString());
_is_new_private_sub_msg = true;
_private_subs_in_request_msg = 0;
}
void symbol_instruments::unsub_instruments(const instruments_set &set_of_instruments)
{
for (auto &instrument_name : set_of_instruments)
{
gen_unsubscribe_to_public_channels_msg(instrument_name);
gen_unsubscribe_to_private_channels_msg(instrument_name);
}
if (_public_unsubs_in_request_msg != 0)
{
send_unsubscribe_to_public_channels_msg();
}
if (_private_unsubs_in_request_msg != 0)
{
send_unsubscribe_to_private_channels_msg();
}
}
void symbol_instruments::gen_unsubscribe_to_public_channels_msg(const std::string &instrument_name)
{
std::string channel_name;
if (_is_new_public_unsub_msg == true)
{
_is_new_public_unsub_msg = false;
_public_unsub_str_buff.Clear();
_public_unsub_writer.Reset(_public_unsub_str_buff);
_public_unsub_writer.StartObject();
_public_unsub_writer.Key("jsonrpc");
_public_unsub_writer.String(_jsonrpc_version);
_public_unsub_writer.Key("method");
_public_unsub_writer.String(_public_unsubscribe_method.c_str());
_public_unsub_writer.Key("id");
_public_unsub_writer.Uint64((int)method_id::public_unsubscribe);
_public_unsub_writer.Key("params");
_public_unsub_writer.StartObject();
_public_unsub_writer.Key("channels");
_public_unsub_writer.StartArray();
}
if (_subscriptions_config.public_quote == true)
{
channel_name = "quote." + instrument_name;
_public_unsub_writer.String(channel_name.c_str());
++_public_unsubs_in_request_msg;
}
if (_subscriptions_config.public_trade == true)
{
channel_name = "trades." + instrument_name + "." + _subscription_interval;
_public_unsub_writer.String(channel_name.c_str());
++_public_unsubs_in_request_msg;
}
if (_subscriptions_config.public_ticker == true)
{
channel_name = "ticker." + instrument_name + "." + _subscription_interval;
_public_unsub_writer.String(channel_name.c_str());
++_public_unsubs_in_request_msg;
}
if (_public_subs_in_request_msg >= 500)
{
send_unsubscribe_to_public_channels_msg();
}
}
void symbol_instruments::send_unsubscribe_to_public_channels_msg()
{
_public_unsub_writer.EndArray();
_public_unsub_writer.EndObject();
_public_unsub_writer.EndObject();
LOG_TRACE(
"Symbol: {}, Total public channels: {}, Public unsubscription message: "
"{}",
_symbol_name, _public_unsubs_in_request_msg,
string{_public_unsub_str_buff.GetString()});
send_message(_public_unsub_str_buff.GetString());
_is_new_public_unsub_msg = true;
_public_unsubs_in_request_msg = 0;
}
void symbol_instruments::gen_unsubscribe_to_private_channels_msg(const std::string &instrument_name)
{
std::string channel_name;
if (_is_new_private_unsub_msg == true)
{
_is_new_private_unsub_msg = false;
_private_unsub_str_buff.Clear();
_private_unsub_writer.Reset(_private_unsub_str_buff);
_private_unsub_writer.StartObject();
_private_unsub_writer.Key("jsonrpc");
_private_unsub_writer.String(_jsonrpc_version);
_private_unsub_writer.Key("method");
_private_unsub_writer.String(_private_unsubscribe_method.c_str());
_private_unsub_writer.Key("id");
_private_unsub_writer.Uint64((int)method_id::private_unsubscribe);
_private_unsub_writer.Key("params");
_private_unsub_writer.StartObject();
_private_unsub_writer.Key("channels");
_private_unsub_writer.StartArray();
}
if (_subscriptions_config.public_book == true)
{
channel_name = "book." + instrument_name + "." + _subscription_interval;
_private_unsub_writer.String(channel_name.c_str());
++_private_unsubs_in_request_msg;
}
if (_private_subs_in_request_msg >= 500)
{
send_subscribe_to_private_channels_msg();
}
}
void symbol_instruments::send_unsubscribe_to_private_channels_msg()
{
_private_unsub_writer.EndArray();
_private_unsub_writer.EndObject();
_private_unsub_writer.EndObject();
LOG_TRACE(
"Symbol: {}, Total private channels: {}, Private unsubscription "
"message: {}",
_symbol_name, _private_unsubs_in_request_msg,
string{_private_unsub_str_buff.GetString()});
send_message(_private_unsub_str_buff.GetString());
_is_new_private_unsub_msg = true;
_private_unsubs_in_request_msg = 0;
}
void symbol_instruments::reset_symbol_instruments()
{
LOG_TRACE("Reconnection occured. Resetting symbol instrument | Symbol: {}", _symbol_name);
_white_list.clear();
_instruments_to_resubscribe.clear();
_instruments_to_subscribe.clear();
_all_sub_req_sent = false;
}
std::ostream &symbol_instruments::print_all_books(std::ostream &out)
{
std::vector<std::string> ordered_white_list;
for (auto each_instrument_data : _white_list)
{
ordered_white_list.emplace_back(each_instrument_data.first);
}
std::sort(ordered_white_list.begin(), ordered_white_list.end());
for (auto each_instrument : ordered_white_list)
{
out << "-----------------------------------------------------------------" << std::endl;
out << "Instrument: " << each_instrument << std::endl;
out << _white_list.at(each_instrument);
}
return out;
}
@@ -0,0 +1,121 @@
#ifndef SYMBOL_INSTRUMENTS_H
#define SYMBOL_INSTRUMENTS_H
#include "deribit_listener.h"
#include "document_to_string.h"
#include "flat_hash_map/bytell_hash_map.hpp"
#include "instrument_data.h"
#include "rapidjson/document.h"
#include "session.h"
#include <future>
#include <list>
#include <unordered_map>
#include <unordered_set>
#include <vector>
typedef std::unordered_set<std::string> instruments_set;
namespace emumba {
namespace deribit_connector {
class symbol_instruments
{
public:
symbol_instruments(const std::string &symbol_name, const std::shared_ptr<session> session,
const configuration::subscription_config &sub_config, deribit_listener *trade_listener);
void initialize_instruments(const instruments_set & set_of_instruments,
std::map<std::string, instrument_info *> instruments_info); // Used during Bootup
void add_new_instruments(
const instruments_set & set_of_instruments,
std::map<std::string, instrument_info *> instruments_info); // Used when new instruments added during run time
void unsub_instruments(const instruments_set &set_of_instruments);
void resubscribe_to_private_book(const std::string &instrument_name);
bool is_instrument_being_resubscribed(const std::string &instrument_name);
void check_if_channel_is_being_resubscribed(const std::string &channel_name);
void reset_symbol_instruments();
std::ostream & print_all_books(std::ostream &out);
friend std::ostream &operator<<(std::ostream &out, symbol_instruments &symbol_instruments)
{
return symbol_instruments.print_all_books(out);
}
ALWAYS_INLINE instrument_data &get_instrument_data(const std::string &instrument_name)
{
return _white_list.at(instrument_name);
}
ALWAYS_INLINE bool is_instrument_in_whitelist(const std::string &instrument_name)
{
return _white_list.find(instrument_name) != _white_list.end();
}
private:
void add_to_whitelist(const std::string &instrument_name, instrument_info *&instr_info);
void sub_instruments();
void send_message(const char *msg_buff);
void gen_interested_channels_str();
void gen_subscribe_to_public_channels_msg(const std::string &instrument_name);
void gen_subscribe_to_private_channels_msg(const std::string &instrument_name);
void send_subscribe_to_public_channels_msg();
void send_subscribe_to_private_channels_msg();
void gen_unsubscribe_to_public_channels_msg(const std::string &instrument_name);
void gen_unsubscribe_to_private_channels_msg(const std::string &instrument_name);
void send_unsubscribe_to_public_channels_msg();
void send_unsubscribe_to_private_channels_msg();
void subscribe_to_private_book(const std::string &instrument_name);
void unsubscribe_to_private_book(const std::string &instrument_name);
ska::flat_hash_map<std::string /*Instrument Name*/, instrument_data /*Instrument data*/,
ska::power_of_two_std_hash<std::string>>
_white_list;
std::unordered_set<std::string> _instruments_to_resubscribe;
std::string _symbol_name;
std::string _interested_channels_str;
std::string _subscription_interval;
std::shared_ptr<session> _session;
const session::state & _session_state;
configuration::subscription_config _subscriptions_config;
uint _max_whitelist_size;
// uint _no_of_requests_sent;
deribit_listener * _trade_listener;
document_to_string _convert_document;
std::vector<std::future<void>> _async_f_list;
bool _all_sub_req_sent;
const std::string _public_subscribe_method;
const std::string _public_unsubscribe_method;
const std::string _private_subscribe_method;
const std::string _private_unsubscribe_method;
char _jsonrpc_version[4];
rapidjson::StringBuffer _str_buff;
rapidjson::Writer<rapidjson::StringBuffer> _writer;
// TODO: Can use a struct for the following variables
// subscribe message variables
instruments_set _instruments_to_subscribe;
rapidjson::StringBuffer _public_sub_str_buff;
rapidjson::Writer<rapidjson::StringBuffer> _public_sub_writer;
rapidjson::StringBuffer _private_sub_str_buff;
rapidjson::Writer<rapidjson::StringBuffer> _private_sub_writer;
uint _public_subs_in_request_msg;
uint _private_subs_in_request_msg;
bool _is_new_public_sub_msg;
bool _is_new_private_sub_msg;
// unsubscribe message variables
rapidjson::StringBuffer _public_unsub_str_buff;
rapidjson::Writer<rapidjson::StringBuffer> _public_unsub_writer;
rapidjson::StringBuffer _private_unsub_str_buff;
rapidjson::Writer<rapidjson::StringBuffer> _private_unsub_writer;
uint _public_unsubs_in_request_msg;
uint _private_unsubs_in_request_msg;
bool _is_new_public_unsub_msg;
bool _is_new_private_unsub_msg;
};
} // namespace deribit_connector
} // namespace emumba
#endif /* SYMBOL_INSTRUMENTS_H */
@@ -0,0 +1,42 @@
#ifndef TIMER_H
#define TIMER_H
#include <sys/time.h>
#include <time.h>
namespace emumba {
namespace utils {
class timer
{
private:
struct timespec start_time, end_time;
public:
timer()
{}
inline void start()
{
clock_gettime(CLOCK_MONOTONIC, &start_time);
}
inline void stop()
{
clock_gettime(CLOCK_MONOTONIC, &end_time);
}
inline double get_elapsed_ms()
{
return get_elapsed_ns() / 1000000.0;
}
inline double get_elapsed_us()
{
return get_elapsed_ns() / 1000.0;
}
double get_elapsed_ns()
{
return ((1000000000.0 * static_cast<double>(end_time.tv_sec - start_time.tv_sec)) +
static_cast<double>(end_time.tv_nsec - start_time.tv_nsec));
}
};
} // namespace utils
} // namespace emumba
#endif
@@ -0,0 +1,703 @@
#include "trader_session.h"
#include "deribit_utils.h"
#include "method_identifiers.h"
#include "rpc_message.h"
#include "spdlogger.h"
#include "utils.h"
emumba::deribit_connector::trader_session::trader_session(
std::shared_ptr<session_manager> manager_ref, std::shared_ptr<orders_handler> orders_handler,
const emumba::deribit_connector::configuration::credential_config &credential_config,
std::vector<std::string> &configured_symbols, deribit_listener *trad_listener)
: _sess_manager(manager_ref)
, _orders_handler(orders_handler)
, _trading_listener(trad_listener)
, _subaccount_index(credential_config.index)
, _credential_config(credential_config)
, _configured_symbols(configured_symbols)
, _private_subscribe_method("private/subscribe")
, _jsonrpc_version("2.0")
{
open_new_connection();
subscribe_to_user_channels();
get_cancel_on_disconnect();
}
void emumba::deribit_connector::trader_session::open_new_connection()
{
_session = _sess_manager->create_new_session(
_credential_config, std::bind(&trader_session::on_receive, this, std::placeholders::_1, std::placeholders::_2));
_session->set_on_reconnect_callback(std::bind(&trader_session::on_reconnect, this));
_session->set_on_error_callback(std::bind(&trader_session::on_error, this, std::placeholders::_1));
#ifdef TRACEPOINTS
_session->set_timer_reference(&_timer);
#endif // TRACEPOINTS
_session_id = _session->get_session_id();
}
void emumba::deribit_connector::trader_session::on_reconnect()
{
subscribe_to_user_channels();
get_cancel_on_disconnect();
_order_queues.clear();
}
void emumba::deribit_connector::trader_session::on_error(const emumba::market_data::market_data_error &err)
{
// This function is called incase disconnection occurs
if (err.err_type == emumba::market_data::market_data_error::error_type::disconnection)
{
emumba::market_data::disconnected_session disconnected_sess;
disconnected_sess.session_id = _session_id;
disconnected_sess.sess_type = emumba::market_data::session_type::order_entry;
disconnected_sess.name = "";
#ifdef ENABLE_API
_trading_listener->on_disconnection(disconnected_sess);
#endif
// call on order updates
_orders_handler->on_trader_sess_disconnect(_session_id, _subaccount_index);
}
else
{
#ifdef ENABLE_API
_trading_listener->on_error(err);
#endif
}
}
void emumba::deribit_connector::trader_session::get_cancel_on_disconnect()
{
_str_buff.Clear();
_writer.Reset(_str_buff);
_writer.StartObject();
_writer.Key("jsonrpc");
_writer.String("2.0");
_writer.Key("method");
_writer.String("private/get_cancel_on_disconnect");
_writer.Key("id");
_writer.Uint64((int)method_id::get_cancel_on_disconnect);
_writer.Key("params");
_writer.StartObject();
_writer.Key("scope");
_writer.String("connection");
_writer.EndObject();
_writer.EndObject();
LOG_DEBUG(
"Get cancel on disconnect request: {}",
std::string{_str_buff.GetString()});
send_message(_str_buff.GetString());
}
void emumba::deribit_connector::trader_session::send_message(const char *msg_buff)
{
_session->send_message(msg_buff);
}
void emumba::deribit_connector::trader_session::subscribe_to_user_channels()
{
subscribe_to_user_orders();
subscribe_to_user_trades();
}
void emumba::deribit_connector::trader_session::subscribe_to_user_orders()
{
_str_buff.Clear();
_writer.Reset(_str_buff);
_writer.StartObject();
_writer.Key("jsonrpc");
_writer.String(_jsonrpc_version);
_writer.Key("method");
_writer.String(_private_subscribe_method.c_str());
_writer.Key("id");
_writer.Uint64((int)method_id::private_subscribe);
_writer.Key("params");
_writer.StartObject();
_writer.Key("channels");
_writer.StartArray();
for (const auto &each_symbol : _configured_symbols)
{
auto request_string = "user.orders.any." + each_symbol + ".raw";
_writer.String(request_string.c_str());
}
_writer.EndArray();
_writer.EndObject();
_writer.EndObject();
send_message(_str_buff.GetString());
}
void emumba::deribit_connector::trader_session::subscribe_to_user_trades()
{
_str_buff.Clear();
_writer.Reset(_str_buff);
_writer.StartObject();
_writer.Key("jsonrpc");
_writer.String(_jsonrpc_version);
_writer.Key("method");
_writer.String(_private_subscribe_method.c_str());
_writer.Key("id");
_writer.Uint64((int)method_id::private_subscribe);
_writer.Key("params");
_writer.StartObject();
_writer.Key("channels");
_writer.StartArray();
for (const auto &each_symbol : _configured_symbols)
{
auto request_string = "user.trades.any." + each_symbol + ".raw";
_writer.String(request_string.c_str());
}
_writer.EndArray();
_writer.EndObject();
_writer.EndObject();
send_message(_str_buff.GetString());
}
void emumba::deribit_connector::trader_session::on_receive(const emumba::deribit_connector::data_types::error &ec,
const data_types::rpc_message & received_msg)
{
if (__builtin_expect(received_msg.is_notification(), true))
{
process_subscription(received_msg);
}
else if (received_msg.is_response() || received_msg.is_error())
{
process_response(ec, received_msg);
}
else
{
LOG_WARNING("Message received other than notification and response");
}
}
void emumba::deribit_connector::trader_session::process_response(const emumba::deribit_connector::data_types::error &ec,
const data_types::rpc_message &response_message)
{
const rapidjson::Value &result = response_message.get_result();
UNUSED(ec);
UNUSED(result);
LOG_TRACE("Trader session | on_receive | {}", _convert_document.to_string(result));
switch (response_message.get_id())
{
case (int)method_id::get_account_summary: {
#ifdef ENABLE_API
_trading_listener->on_account_summary(ec, _convert_document.to_string(result));
#endif
break;
}
case (int)method_id::new_order: {
_orders_handler->process_new_order(ec, result, _order_queues, _subaccount_index);
break;
}
case (int)method_id::edit_order: {
_orders_handler->process_edit_order(ec, result, _order_queues, _subaccount_index);
break;
}
case (int)method_id::cancel_order: {
_orders_handler->process_cancel_order(ec, result, _order_queues, _subaccount_index);
break;
}
case (int)method_id::cancel_all_by_currency: {
#ifdef ENABLE_API
_order_cancellation_info.ec = ec;
ec.value() == 0 ? _order_cancellation_info.no_of_orders_cancelled = result.GetUint()
: _order_cancellation_info.no_of_orders_cancelled = 0;
_order_cancellation_info.type = data_types::cancellation_type::by_currency;
_order_cancellation_info.subaccount_index = _subaccount_index;
_trading_listener->on_mass_cancellation(_order_cancellation_info);
#endif
break;
}
case (int)method_id::cancel_all_by_instrument: {
#ifdef ENABLE_API
_order_cancellation_info.ec = ec;
ec.value() == 0 ? _order_cancellation_info.no_of_orders_cancelled = result.GetUint()
: _order_cancellation_info.no_of_orders_cancelled = 0;
_order_cancellation_info.type = data_types::cancellation_type::by_instrument;
_order_cancellation_info.subaccount_index = _subaccount_index;
_trading_listener->on_mass_cancellation(_order_cancellation_info);
#endif
break;
}
case (int)method_id::cancel_all_positions: {
#ifdef ENABLE_API
_trading_listener->on_cancel_all_positions(ec, _convert_document.to_string(result));
#endif
break;
}
case (int)method_id::get_position: {
#ifdef ENABLE_API
_trading_listener->on_get_position(ec, _convert_document.to_string(result));
#endif
break;
}
case (int)method_id::get_open_orders_by_instrument: {
#ifdef ENABLE_API
_trading_listener->on_get_open_orders(ec, _convert_document.to_string(result));
#endif
break;
}
case (int)method_id::get_mmp_config: {
#ifdef ENABLE_API
_trading_listener->on_get_mmp_config(ec, _convert_document.to_string(result));
#endif
break;
}
case (int)method_id::set_mmp_config: {
#ifdef ENABLE_API
_trading_listener->on_set_mmp_config(ec, _convert_document.to_string(result));
#endif
break;
}
case (int)method_id::reset_mmp_config: {
#ifdef ENABLE_API
_trading_listener->on_reset_mmp(ec, _convert_document.to_string(result));
#endif
break;
}
case (int)method_id::get_cancel_on_disconnect: {
process_get_cancel_on_disconnect(ec, result);
break;
}
case (int)method_id::enable_cancel_on_disconnect: {
process_enable_cancel_on_disconnect(ec, result);
break;
}
case (int)method_id::disable_cancel_on_disconnect: {
process_disable_cancel_on_disconnect(ec, result);
break;
}
case (int)method_id::get_positions: {
process_get_positions(ec, result);
break;
}
case (int)method_id::get_open_orders_by_currency: {
process_get_open_orders(ec, result);
break;
}
case (int)method_id::get_user_trades_by_currency_and_time: {
process_get_user_trades_by_currency_and_time(ec, result);
break;
}
case (int)method_id::rfq_request: {
process_rfq_response(ec, result);
break;
}
case (int)method_id::create_combo: {
process_create_combo(ec, result);
break;
}
case (int)method_id::private_subscribe: {
LOG_TRACE("Response | Private | subscribe | Message: {}", _convert_document.to_string(result));
break;
}
default: {
LOG_WARNING("Unknown response received for message id: {}", response_message.get_id());
}
}
}
void emumba::deribit_connector::trader_session::process_subscription(
const emumba::deribit_connector::data_types::rpc_message &subscription_message)
{
const rapidjson::Value &subscription_message_params = subscription_message.get_params();
std::string subscription_channel = subscription_message_params["channel"].GetString();
LOG_TRACE("Subscription channel: {}, Subscription Message Received: {}", subscription_channel,
_convert_document.to_string(subscription_message_params));
if (strncmp(subscription_channel.c_str(), "user.orders.any", 15) == 0)
{
_orders_handler->process_private_user_orders_subscription(subscription_message_params, _subaccount_index);
}
else if (strncmp(subscription_channel.c_str(), "user.trades.any", 15) == 0)
{
_orders_handler->process_private_user_trades_subscription(subscription_message_params, _subaccount_index);
}
}
void emumba::deribit_connector::trader_session::new_order_queue_push(const emumba::order_entry::order_key &order_key)
{
_order_queues.new_order_queue.push(order_key);
LOG_DEBUG("New Order Queue | Push | Queue size: {}", _order_queues.new_order_queue.size());
}
void emumba::deribit_connector::trader_session::edit_queue_push(const emumba::order_entry::order_key &order_key)
{
_order_queues.edit_queue.push(order_key);
LOG_DEBUG("Edit Order Queue | Push | Queue size: {}", _order_queues.edit_queue.size());
}
void emumba::deribit_connector::trader_session::cancel_queue_push(const emumba::order_entry::order_key &order_key)
{
_order_queues.cancel_queue.push(order_key);
LOG_DEBUG("Cancel Order Queue | Push | Queue size: {}", _order_queues.cancel_queue.size());
}
const uint &emumba::deribit_connector::trader_session::get_session_id() const
{
return _session_id;
}
void emumba::deribit_connector::trader_session::process_get_cancel_on_disconnect(
const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result)
{
if (!ec.value())
{
bool is_enabled = result["enabled"].GetBool();
LOG_DEBUG("Cancel on disconnect | Exchange value: {}", is_enabled);
if (is_enabled == false)
{
enable_cancel_on_disconnect();
}
}
else
{
LOG_ERROR(
"Error in response ec.value: {}, ec.message: {}", ec.value(),
ec.message());
}
}
void emumba::deribit_connector::trader_session::enable_cancel_on_disconnect()
{
_str_buff.Clear();
_writer.Reset(_str_buff);
_writer.StartObject();
_writer.Key("jsonrpc");
_writer.String("2.0");
_writer.Key("method");
_writer.String("private/enable_cancel_on_disconnect");
_writer.Key("id");
_writer.Uint64((int)method_id::enable_cancel_on_disconnect);
_writer.Key("params");
_writer.StartObject();
_writer.Key("scope");
_writer.String("connection");
_writer.EndObject();
_writer.EndObject();
LOG_DEBUG(
"Enable cancel on disconnect request: {}",
std::string{_str_buff.GetString()});
send_message(_str_buff.GetString());
}
void emumba::deribit_connector::trader_session::disable_cancel_on_disconnect()
{
// TODO: This function and its response process function can be removed.
_str_buff.Clear();
_writer.Reset(_str_buff);
_writer.StartObject();
_writer.Key("jsonrpc");
_writer.String("2.0");
_writer.Key("method");
_writer.String("private/disable_cancel_on_disconnect");
_writer.Key("id");
_writer.Uint64((int)method_id::disable_cancel_on_disconnect);
_writer.Key("params");
_writer.StartObject();
_writer.Key("scope");
_writer.String("connection");
_writer.EndObject();
_writer.EndObject();
LOG_DEBUG(
"Disable cancel on disconnect request: {}",
std::string{_str_buff.GetString()});
send_message(_str_buff.GetString());
}
void emumba::deribit_connector::trader_session::process_enable_cancel_on_disconnect(
const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result)
{
if (!ec.value())
{
if (strcmp(result.GetString(), "ok") == 0)
{
LOG_DEBUG("Enable cancel on disconnect successful");
}
else
{
LOG_ERROR(
"Enable cancel on disconnect failed. Result: {}",
std::string{result.GetString()});
}
}
else
{
LOG_ERROR(
"Error in response ec.value: {}, ec.message: {}", ec.value(),
ec.message());
}
}
void emumba::deribit_connector::trader_session::process_disable_cancel_on_disconnect(
const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result)
{
// TODO: This function can be removed
if (!ec.value())
{
if (strcmp(result.GetString(), "ok") == 0)
{
LOG_DEBUG("Disable cancel on disconnect successful");
}
else
{
LOG_ERROR(
"Disable cancel on disconnect failed. Result: {}",
std::string{result.GetString()});
}
}
else
{
LOG_ERROR(
"Error in response ec.value: {}, ec.message: {}", ec.value(),
ec.message());
}
}
void emumba::deribit_connector::trader_session::process_get_positions(
UNUSED_PARAM const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result)
{
for (rapidjson::Value::ConstValueIterator each_position_itr = result.Begin(); each_position_itr != result.End();
++each_position_itr)
{
_position_rec.subaccount_index = _subaccount_index;
auto each_position = each_position_itr->GetObject();
_position_rec.sec_id.value() = each_position["instrument_name"].GetString();
_position_rec.qty = emumba::market_data::quantity(each_position["size"].GetDouble());
_position_rec.average_price = each_position["average_price"].GetDouble();
const char *side = each_position["direction"].GetString();
if ((strcmp(side, "buy") == 0))
{
_position_rec.position_side = emumba::market_data::side::buy;
}
else if ((strcmp(side, "sell") == 0))
{
_position_rec.position_side = emumba::market_data::side::sell;
}
else
{
_position_rec.position_side = emumba::market_data::side::none;
}
_position_records_list.push_back(_position_rec);
_position_records_ref.push_back(&_position_records_list.back());
}
LOG_DEBUG("No of Positions: {}", _position_records_ref.size());
#ifdef ENABLE_API
_trading_listener->on_get_all_positions(ec, _position_records_ref);
#endif
_position_records_ref.clear();
_position_records_list.clear();
}
void emumba::deribit_connector::trader_session::process_get_open_orders(
UNUSED_PARAM const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result)
{
for (rapidjson::Value::ConstValueIterator each_open_order_itr = result.Begin(); each_open_order_itr != result.End();
++each_open_order_itr)
{
_open_order_rec.subaccount_index = _subaccount_index;
auto each_open_order = each_open_order_itr->GetObject();
_open_order_rec.sec_id.value() = each_open_order["instrument_name"].GetString();
_open_order_rec.exchange_order_id = each_open_order["order_id"].GetString();
std::string label =
each_open_order["label"]
.GetString(); // Check what if some open order does not have any label. Is this empty or doesnt exist?
std::string _label_uuid;
std::uint64_t _label_order_id;
_orders_handler->parse_label_value(label, _label_uuid, _label_order_id);
_open_order_rec.internal_order_id = _label_order_id;
const char *order_state = each_open_order["order_state"].GetString();
if (strcmp(order_state, "open") == 0)
{
_open_order_rec.order_status = emumba::order_entry::status::working;
}
else if (strcmp(order_state, "filled") == 0)
{
_open_order_rec.order_status = emumba::order_entry::status::completed;
}
else if (strcmp(order_state, "cancelled") == 0)
{
_open_order_rec.order_status = emumba::order_entry::status::canceled;
}
else if (strcmp(order_state, "rejected") == 0)
{
_open_order_rec.order_status = emumba::order_entry::status::rejected;
}
else if (strcmp(order_state, "untriggered") == 0)
{
_open_order_rec.order_status = emumba::order_entry::status::untriggered;
}
else
{
LOG_WARNING(
"Unknown order state received: {}", std::string{order_state});
}
const char *time_in_force = each_open_order["time_in_force"].GetString();
if (strcmp(time_in_force, "good_til_cancelled") == 0)
{
_open_order_rec.time_in_frc = emumba::order_entry::time_in_force::gtc;
}
else if (strcmp(time_in_force, "good_til_day") == 0)
{
_open_order_rec.time_in_frc = emumba::order_entry::time_in_force::day;
}
else if (strcmp(time_in_force, "fill_or_kill") == 0)
{
_open_order_rec.time_in_frc = emumba::order_entry::time_in_force::fill_or_kill;
}
else if (strcmp(time_in_force, "immediate_or_cancel") == 0)
{
_open_order_rec.time_in_frc = emumba::order_entry::time_in_force::ioc;
}
else
{
LOG_WARNING(
"Unknown time in force received: {}", std::string{time_in_force});
}
const char *side = each_open_order["direction"].GetString();
if ((strcmp(side, "buy") == 0))
{
_open_order_rec.order_side = emumba::market_data::side::buy;
}
else if ((strcmp(side, "sell") == 0))
{
_open_order_rec.order_side = emumba::market_data::side::sell;
}
else
{
_open_order_rec.order_side = emumba::market_data::side::none;
}
_open_order_rec.qty = emumba::market_data::quantity(each_open_order["amount"].GetDouble());
_open_order_rec.filled_qty = emumba::market_data::quantity(each_open_order["filled_amount"].GetDouble());
_open_order_rec.px = each_open_order["price"].GetDouble();
_open_order_rec.average_price = each_open_order["average_price"].GetDouble();
_open_order_rec.last_update_timestamp.set_timestamp(each_open_order["last_update_timestamp"].GetUint64());
_open_order_rec.creation_timestamp.set_timestamp(each_open_order["creation_timestamp"].GetUint64());
_open_order_rec.api = each_open_order["api"].GetBool();
if (each_open_order.HasMember("web"))
{
_open_order_rec.web = each_open_order["web"].GetBool();
}
else
{
_open_order_rec.web = false;
}
_open_order_records_list.push_back(_open_order_rec);
_open_order_records_ref.push_back(&_open_order_records_list.back());
}
LOG_DEBUG("No of Open Orders: {}", _open_order_records_ref.size());
#ifdef ENABLE_API
_trading_listener->on_get_all_open_orders(ec, _open_order_records_ref);
#endif
_open_order_records_ref.clear();
_open_order_records_list.clear();
}
void emumba::deribit_connector::trader_session::process_get_user_trades_by_currency_and_time(
const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result)
{
LOG_DEBUG("Trader session | process_get_user_trades_by_currency_and_time | Subaccount id: {}", _subaccount_index);
_user_trades_response.error = ec;
_user_trades_response.subaccount_index = _subaccount_index;
if (ec.value() == 0)
{
_user_trades_response.has_more = result["has_more"].GetBool();
const rapidjson::Value &user_trades = result["trades"];
for (rapidjson::Value::ConstValueIterator each_user_trade_itr = user_trades.Begin();
each_user_trade_itr != user_trades.End(); ++each_user_trade_itr)
{
auto each_user_trade = each_user_trade_itr->GetObject();
orders_handler::fill_trade_record(_trade_record, each_user_trade);
_trade_records_list.push_back(_trade_record);
_user_trades_response.trades.push_back(&_trade_records_list.back());
}
}
else
{
LOG_DEBUG("Error received in the process_get_user_trades_by_currency_and_time response | Subaccount id: {}",
_subaccount_index);
}
#ifdef ENABLE_API
_trading_listener->on_get_user_trades(_user_trades_response);
#endif
_user_trades_response.trades.clear();
_trade_records_list.clear();
}
void emumba::deribit_connector::trader_session::process_rfq_response(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value &result)
{
_rfq_response.status = false;
_rfq_response.error = ec;
_rfq_response.subaccount_index = _subaccount_index;
if (ec.value() == 0)
{
std::string status = result.GetString();
if (status == "ok")
{
_rfq_response.status = true;
}
}
_trading_listener->on_request_for_quote(_rfq_response);
}
void emumba::deribit_connector::trader_session::process_create_combo(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value &result)
{
_combo_detail.error = ec;
_combo_detail.subaccount_index = _subaccount_index;
if (ec.value() == 0)
{
_combo_detail.legs.clear();
_combo_detail.sec_id.value() = result["id"].GetString();
const rapidjson::Value &leg_trades = result["legs"];
for (rapidjson::Value::ConstValueIterator trade_itr = leg_trades.Begin(); trade_itr != leg_trades.End(); ++trade_itr)
{
auto trade = trade_itr->GetObject();
_leg_detail.instrument_name.value() = trade["instrument_name"].GetString();
_leg_detail.amount = trade["amount"].GetInt();
_combo_detail.legs.push_back(_leg_detail);
}
}
_trading_listener->on_create_combo(_combo_detail);
}
@@ -0,0 +1,106 @@
#ifndef TRADER_SESSION_H
#define TRADER_SESSION_H
#include "common.h"
#include "deribit_listener.h"
#include "deribit_utils.h"
#include "document_to_string.h"
#include "error.h"
#include "order_entry_common.h"
#include "orders_handler.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include "session.h"
#include "session_manager.h"
#include "utils.h"
#ifdef TRACEPOINTS
#include "timer.h"
#endif // TRACEPOINTS
#include <list>
#include <string>
#include <vector>
namespace emumba {
namespace deribit_connector {
class trader_session
{
public:
trader_session(std::shared_ptr<session_manager> manager_ref, std::shared_ptr<orders_handler> orders_handler,
const emumba::deribit_connector::configuration::credential_config &credential_config,
std::vector<std::string> &configured_symbols, deribit_listener *trad_listener);
void send_message(const char *msg_buff);
void new_order_queue_push(const emumba::order_entry::order_key &order_key);
void edit_queue_push(const emumba::order_entry::order_key &order_key);
void cancel_queue_push(const emumba::order_entry::order_key &order_key);
const uint &get_session_id() const;
private:
void on_reconnect();
void on_error(const emumba::market_data::market_data_error &err);
void subscribe_to_user_channels();
void subscribe_to_user_orders();
void subscribe_to_user_trades();
void on_receive(const emumba::deribit_connector::data_types::error &ec, const data_types::rpc_message &received_msg);
void process_subscription(const data_types::rpc_message &subscription_message);
void process_response(const emumba::deribit_connector::data_types::error &ec,
const data_types::rpc_message & response_message);
void process_get_cancel_on_disconnect(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value & result);
void open_new_connection();
void get_cancel_on_disconnect();
void enable_cancel_on_disconnect();
void disable_cancel_on_disconnect();
void process_enable_cancel_on_disconnect(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value & result);
void process_disable_cancel_on_disconnect(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value & result);
void process_get_positions(const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result);
void process_get_open_orders(const emumba::deribit_connector::data_types::error &ec, const rapidjson::Value &result);
void process_get_user_trades_by_currency_and_time(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value & result);
void process_rfq_response(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value & result);
void process_create_combo(const emumba::deribit_connector::data_types::error &ec,
const rapidjson::Value & result);
uint _session_id;
std::shared_ptr<session_manager> _sess_manager;
std::shared_ptr<session> _session;
std::shared_ptr<orders_handler> _orders_handler;
deribit_listener * _trading_listener;
document_to_string _convert_document;
order_queues _order_queues;
std::list<emumba::order_entry::position> _position_records_list;
std::vector<emumba::order_entry::position const *> _position_records_ref;
emumba::order_entry::position _position_rec;
std::list<emumba::order_entry::open_order> _open_order_records_list;
std::vector<emumba::order_entry::open_order const *> _open_order_records_ref;
emumba::order_entry::open_order _open_order_rec;
rapidjson::StringBuffer _str_buff;
rapidjson::Writer<rapidjson::StringBuffer> _writer;
uint _subaccount_index;
configuration::credential_config _credential_config;
std::vector<std::string> _configured_symbols;
const std::string _private_subscribe_method;
char _jsonrpc_version[4];
emumba::deribit_connector::data_types::cancellation_update _order_cancellation_info;
emumba::order_entry::user_trades_response _user_trades_response;
emumba::market_data::trade_record _trade_record;
std::list<emumba::market_data::trade_record> _trade_records_list;
emumba::order_entry::rfq_response _rfq_response;
emumba::order_entry::leg_detail _leg_detail;
emumba::order_entry::combo_detail _combo_detail;
#ifdef TRACEPOINTS
emumba::utils::timer _timer;
#endif // TRACEPOINTS
};
} // namespace deribit_connector
} // namespace emumba
#endif /* TRADER_H */
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,224 @@
/**
* @file trading_session_interface.h
* @brief Provides a class with the basic interface for calling different private methods linked to a trading connection.
*
* @copyright Copyright (c) 2021
* Copyright Emumba (Pvt) Ltd [Emumba]. All rights reserved. This software owned by Emumba (Pvt) Ltd [Emumba] and is
* protected by copyright law and international copyright treaties. Access to and use of the software is governed by the
* terms of the applicable Emumba Software Services Agreement (the Agreement) and Customer end user license agreements
* granting a non-assignable, non-transferable and non-exclusive license to use the software for it's own data processing
* purposes under the terms defined in the Agreement. Except as otherwise granted within the terms of the Agreement, copying
* or reproduction of any part of this source code or associated reference material to any other location for further
* reproduction or redistribution, and any amendments to this copyright notice, are expressly prohibited. Any reproduction or
* redistribution for sale or hiring of the Software not in accordance with the terms of the Agreement is a violation of
* copyright law
*/
#ifndef TRADING_SESSION_INTERFACE_H
#define TRADING_SESSION_INTERFACE_H
#include "common.h"
#include <memory>
#include <string>
namespace emumba {
namespace deribit_connector {
class trader_session;
class orders_handler;
/**
* @brief Provides the basic interface for calling different private methods linked to a trading connection.
*
*/
class trading_session_interface
{
public:
trading_session_interface(std::shared_ptr<trader_session> trader_session_impl,
std::shared_ptr<orders_handler> orders_handler);
/**
* @brief Place a new Buy or Sell order.
*
* @param order Object containing necessary information required to place a new order. User will provide the
* information required to place an order using this object.
* @return emumba::market_data::order_key A unique key generated by the connector against each order. The user will
* provide this Key when an order needs to be updated or cancelled. Order Key will have a value of -1 in case the order
* is not sent successfully.
*/
emumba::order_entry::order_key launch_new_order(const emumba::order_entry::new_order &order);
/**
* @brief Linked to method "private/edit" to edit an order.
*
* @param update Object containing necessary information required to edit a specific order. This object will also
* contain the "emumba::market_data::order_key" provided to the user when a new order was placed.
* @return true The edit order message is sent successfully.
* @return false The edit order message is unsuccessfull. This can happen if the "emumba::market_data::order_key"
* provided in the "update" structure can not be mapped by the connector against an order.
*/
bool update_order(const emumba::order_entry::order_update &update);
/**
* @brief Linked to method "private/cancel" to cancel an order.
*
* @param key The "emumba::market_data::order_key" provided to the user when a new order was placed.
* @return true The cancel order message is sent successfully.
* @return false The cancel order message is unsuccessfull. This can happen if the "emumba::market_data::order_key"
* provided can not be mapped by the connector against an order.
*/
bool cancel_order(const emumba::order_entry::order_key &key);
/**
* @brief Cancel all open orders for the specified currency.
*
* @param currency Currency for which open orders need to be canceled. example btc, eth etc.
*/
void cancel_orders_by_currency(const std::string &currency);
/**
* @brief Linked to method "private/cancel_all_by_instrument" to cancel all orders against an instrument.
*
* @param instrument_name Instrument name.
* @return void
*/
void cancel_orders_by_instrument(const std::string &instrument_name);
/**
* @brief Get the all positions for the specified currency.
*
* @param currency Currency for which positions are required. example btc, eth etc.
*/
void get_all_positions(const std::string &currency);
/**
* @brief Get the all open orders for the specified currency.
*
* @param currency Currency for which open orders are required. example btc, eth etc.
*/
void get_all_open_orders(const std::string &currency);
/**
* @brief Get the user trades.
*
* @param usr_trades_request Object containing necessary information used to get the required user trades.
*/
void get_user_trades(const emumba::order_entry::user_trades_request &usr_trades_request);
/**
* @brief Linked to method "private/close_position" to place a request to cancell all positions.
*
* @param instrument_name Instrument name.
* @return void
*/
void send_cancel_all_positions(const std::string &instrument_name);
/**
* @brief Linked to method "private/get_position" to retrieve user position.
*
* @param instrument_name Instrument name.
* @return void
*/
void send_get_position(const std::string &instrument_name);
/**
* @brief Linked to method "private/get_open_orders_by_instrument" to retrieve list of user's open orders within given
* instrument.
*
* @param instrument_name Instrument name.
* @return void
*/
void send_get_open_orders(const std::string &instrument_name);
/**
* @brief Linked to method "private/set_mmp_config" to set config for Market Maker Protection.
*
* @param currency The currency symbol.
* @param interval MMP Interval in seconds, if set to 0 MMP is disabled.
* @param frozen_time MMP frozen time in seconds, if set to 0 manual reset is required.
* @param quantity_limit Quantity limit.
* @param delta_limit Delta limit.
* @return void
*/
void send_set_mmp_config(const std::string &currency, const int &interval, const int &frozen_time,
const double &quantity_limit, const double &delta_limit);
/**
* @brief Linked to method "private/get_mmp_config" to get config for Market Maker Protection.
*
* @param currency The currency symbol.
* @return void
*/
void send_get_mmp_config(const std::string &currency);
/**
* @brief Linked to method "private/reset_mmp" to reset Market Maker Protection.
*
* @param currency The currency symbol.
* @return void
*/
void send_reset_mmp(const std::string &currency);
/**
* @brief Place a new limit buy order.
*
* @param order Object containing necessary information required to place a new limit buy order.
* @return emumba::order_entry::order_key A unique key generated by the connector against each order. The user will
* provide this Key when an order needs to be updated or cancelled. Order Key will have a value of -1 in case the order
* is not sent successfully.
*/
emumba::order_entry::order_key launch_limit_buy_order(emumba::order_entry::new_limit_order &order);
/**
* @brief Place a new limit sell order.
*
* @param order Object containing necessary information required to place a new limit sell order.
* @return emumba::order_entry::order_key A unique key generated by the connector against each order. The user will
* provide this Key when an order needs to be updated or cancelled. Order Key will have a value of -1 in case the order
* is not sent successfully.
*/
emumba::order_entry::order_key launch_limit_sell_order(emumba::order_entry::new_limit_order &order);
/**
* @brief Place a new market buy order.
*
* @param order Object containing necessary information required to place a new market buy order.
* @return emumba::order_entry::order_key A unique key generated by the connector against each order. The user will
* provide this Key when an order needs to be updated or cancelled. Order Key will have a value of -1 in case the order
* is not sent successfully.
*/
emumba::order_entry::order_key launch_market_buy_order(emumba::order_entry::new_market_order &order);
/**
* @brief Place a new market sell order.
*
* @param order Object containing necessary information required to place a new market sell order.
* @return emumba::order_entry::order_key A unique key generated by the connector against each order. The user will
* provide this Key when an order needs to be updated or cancelled. Order Key will have a value of -1 in case the order
* is not sent successfully.
*/
emumba::order_entry::order_key launch_market_sell_order(emumba::order_entry::new_market_order &order);
/**
* @brief Get the session id. This session id is unique for each session created.
*
* @return const uint& session id
*/
const uint &get_session_id() const;
void send_rfq(const emumba::order_entry::rfq_request &rfq_request);
bool create_combo(const std::vector<emumba::order_entry::combo_trade const *> &combo_trades);
emumba::order_entry::order_key order_key;
#ifdef E2E_PERF
void e2e_perf_send_buy_order_market(emumba::order_entry::new_market_order &order, const double &change_id);
#endif
private:
std::shared_ptr<trader_session> _trader_session;
std::shared_ptr<orders_handler> _orders_handler;
};
} // namespace deribit_connector
} // namespace emumba
#endif
@@ -0,0 +1,52 @@
#include "utils.h"
std::string emumba::deribit_connector::uuid::generate_uuid_v4()
{
std::stringstream string_stream;
int i;
string_stream << std::hex;
for (i = 0; i < 8; i++)
{
string_stream << dis(gen);
}
string_stream << "_";
for (i = 0; i < 4; i++)
{
string_stream << dis(gen);
}
string_stream << "_4";
for (i = 0; i < 3; i++)
{
string_stream << dis(gen);
}
string_stream << "_";
string_stream << dis2(gen);
for (i = 0; i < 3; i++)
{
string_stream << dis(gen);
}
string_stream << "_";
for (i = 0; i < 12; i++)
{
string_stream << dis(gen);
};
return string_stream.str();
}
void emumba::deribit_connector::order_queues::clear()
{
while (!new_order_queue.empty())
{
new_order_queue.pop();
}
while (!edit_queue.empty())
{
edit_queue.pop();
}
while (!cancel_queue.empty())
{
cancel_queue.pop();
}
}
@@ -0,0 +1,77 @@
#ifndef UTILS_H
#define UTILS_H
#include "order_entry_common.h"
#include <cstdint>
#include <limits>
#include <queue>
#include <random>
#include <sstream>
#include <string>
#define NAN_PRICE std::numeric_limits<std::int64_t>::min()
#define NAN_DOUBLE std::numeric_limits<double>::quiet_NaN()
#define DOUBLE_LIMIT 0.00000001
namespace emumba {
namespace deribit_connector {
namespace uuid {
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_int_distribution<> dis(0, 15);
static std::uniform_int_distribution<> dis2(8, 11);
std::string generate_uuid_v4();
} // namespace uuid
/**
* @brief Determines the state of the application while it requests public/private subscriptions from the exchange.
*
*/
enum class subscription_state
{
subscribing,
subscribed
};
struct instrument_info
{
std::string instrument_name = "";
double tick_size = 0;
double taker_commission = 0;
double block_trade_commission = 0;
std::string settlement_period = "";
std::string quote_currency = "";
double min_trade_amount = 0;
double maker_commission = 0;
int leverage = 0;
std::string kind = "";
bool is_active = false;
uint64_t expiration_timestamp = 0;
uint64_t creation_timestamp = 0;
int contract_size = 0;
std::string base_currency = "";
double strike = 0;
std::string option_type = "";
};
struct legs_info
{
std::string instrument_name;
int quantity_ratio;
};
struct order_queues
{
std::queue<emumba::order_entry::order_key> new_order_queue;
std::queue<emumba::order_entry::order_key> edit_queue;
std::queue<emumba::order_entry::order_key> cancel_queue;
void clear();
};
} // namespace deribit_connector
} // namespace emumba
#endif /* UTILS_H */
@@ -0,0 +1,32 @@
#ifndef WRITER_H
#define WRITER_H
#include "deribit_emumba/deribit_utils.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
namespace emumba {
namespace deribit_connector {
class writer
{
public:
rapidjson::StringBuffer str_buff;
rapidjson::Writer<rapidjson::StringBuffer> message;
ALWAYS_INLINE std::string to_string() {
return std::string{str_buff.GetString()};
}
ALWAYS_INLINE __attribute__((always_inline)) void reset()
{
str_buff.Clear();
message.Reset(str_buff);
message.StartObject();
message.Key("jsonrpc");
message.String("2.0");
message.Key("method");
}
} writer;
} // namespace deribit_connector
} // namespace emumba
#endif
+5 -4
View File
@@ -24,10 +24,11 @@ target_include_directories(
sparkd PUBLIC
${CMAKE_SOURCE_DIR}/src/server/sparkd/cmds/spartan
${CMAKE_SOURCE_DIR}/src
${CMAKE_BINARY_DIR}/src/server/sparkd/cmds/spartan ${emumba-deribit-connector-library_INCLUDE_DIRS})
${CMAKE_BINARY_DIR}/src/server/sparkd/cmds/spartan
)
find_package(PkgConfig REQUIRED)
pkg_check_modules(emumba-deribit-connector-library REQUIRED emumba-deribit-connector)
# find_package(PkgConfig REQUIRED)
# pkg_check_modules(emumba-deribit-connector-library REQUIRED emumba-deribit-connector)
# This dir contains rapidjsonConfig.cmake
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules)
@@ -78,7 +79,7 @@ target_link_libraries(sparkd PUBLIC
libspark
binance_execution
databento::databento
${emumba-deribit-connector-library_LIBRARIES}
deribit_emumba
ssl
cryptopp
tbb
+9 -1
View File
@@ -1,5 +1,6 @@
#include "Run.h"
#include "binance_execution/execution_gateway_provider.h"
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/api/public/ServiceContext.h"
#include "lib/config/config.h"
#include "lib/config/protos/ports.pb.h"
@@ -21,6 +22,7 @@
#include "lib/orderman/orders/algo/algo_orders/ProRataAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/ScratchAlgo.h"
#include "lib/os/os.h"
#include "lib/persistence/IDGenerator.h"
#include "lib/persistence/PersistenceServiceServer.h"
#include "lib/plugin/SparkServices.h"
#include "lib/plugin/plugin.h"
@@ -31,8 +33,10 @@
#include "md/cmds/Run.h"
#include "server/sparkd/algo/AlgoDefinitionServiceServer.h"
#include "server/sparkd/analytics/AnalyticsServiceServer.h"
#include "server/sparkd/cmds/md/cmds/Cleanup.h"
#include "server/sparkd/http/WebServer.h"
#include "server/sparkd/md/MarketDataAuxiliaryServiceServer.h"
#include "server/sparkd/md/MarketDataGateway.h"
#include "server/sparkd/md/platforms/mdp3_onixs/TradeSummarySignalDispatcher.h"
#include "server/sparkd/md/recording/MDRecorder.h"
#include "server/sparkd/position/MarginServiceServer.h"
@@ -101,7 +105,11 @@ static void execute() {
bts::md::MBOProcessor mbo_processor;
bts::md::TradeSummarySignalDispatcher tss_dispatcher;
md::cmds::Run::run();
md::cmds::Cleanup::execute();
bts::md::MarketDataGateway md_gateway{
config::get<api::md::MarketDataConfig>(),
persistence::IDGenerator::instance_ptr()};
md_gateway.start();
api::synthetic::SyntheticSecurityService synthetic_service(sc);
bts::md::MDClient mdclient(
@@ -1,12 +1,12 @@
#include "DeribitDemo.h"
#include "DeribitDemoConfiguration.h"
#include "deribit_emumba/deribit_exception.h"
#include "deribit_emumba/deribit_handler.h"
#include "deribit_emumba/deribit_listener.h"
#include "deribit_emumba/trading_session_interface.h"
#include "lib/logging/logging.h"
#include "server/sparkd/cmds/crypto/DeribitDemoListener.h"
#include <csignal>
#include <emumba-deribit-connector/deribit_exception.h>
#include <emumba-deribit-connector/deribit_handler.h>
#include <emumba-deribit-connector/deribit_listener.h>
#include <emumba-deribit-connector/trading_session_interface.h>
#include <list>
namespace bts::cmds {
@@ -2,7 +2,8 @@
#include "RapidJsonReader.h"
#include "lib/logging/logging.h"
#include "rapidjson/istreamwrapper.h"
#include <emumba-deribit-connector/deribit_utils.h>
#include "deribit_emumba/deribit_utils.h"
#include <fstream>
#include <list>
#include <optional>
@@ -1,8 +1,8 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include "deribit_emumba/deribit_utils.h"
#include <emumba-deribit-connector/deribit_utils.h>
#include <iostream>
#include <list>
#include <map>
@@ -1,19 +1,25 @@
#include "LiveLadderDemo.h"
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/config/config.h"
#include "lib/persistence/IDGenerator.h"
#include "server/sparkd/md/MarketDataGateway.h"
#include "server/sparkd/md/MarketDataRegistration.h"
#include "server/sparkd/md/listeners/LiveLadderPrinter.h"
#include <thread>
#include <vector>
namespace bts::cmds {
LiveLadderDemo::LiveLadderDemo(const api::md::MarketDataConfig &) {
using bts::api::md::MarketDataConfig;
using bts::persistence::IDGenerator;
LiveLadderDemo::LiveLadderDemo(const MarketDataConfig &config) {
char ssl_cert_file[] = "SSL_CERT_FILE=/etc/ssl/cert/ca-certificates.crt";
putenv(ssl_cert_file);
md::MarketDataGateway gw;
md::MarketDataGateway gw{config, IDGenerator::instance_ptr()};
md::listeners::LiveLadderPrinter ladder_printer;
gw.run();
gw.start();
const std::size_t n_loops = 16;
for (std::size_t n = 0; n < n_loops; n++) {
+11 -3
View File
@@ -1,5 +1,6 @@
#include "Run.h"
#include "Cleanup.h"
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/domain/SecurityCriteria.h"
#include "lib/logging/logging.h"
@@ -7,6 +8,7 @@
#include "lib/md/UserSymbol.h"
#include "lib/md/relay/RelayRecorder.h"
#include "lib/os/os.h"
#include "lib/persistence/IDGenerator.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/thread_manager.h"
#include "server/sparkd/md/MarketDataGateway.h"
@@ -14,6 +16,7 @@
#include "server/sparkd/md/platforms/ice_onixs/ICEMain.h"
#include "server/sparkd/md/platforms/mdp3_onixs/MDP3Main.h"
#include <string>
#include <thread>
namespace bts::cmds::md::cmds {
@@ -91,9 +94,14 @@ static void run_onixs(
static void run_md_gwy() {
LOG(md, Info, "Run::run_md_gwy t_id:{} t_name:{}", os::get_thread_id(),
os::get_thread_name());
bts::md::MarketDataGateway gwy = {};
// this keeps going in a loop
gwy.run();
bts::md::MarketDataGateway gwy{
config::get<api::md::MarketDataConfig>(),
persistence::IDGenerator::instance_ptr()};
gwy.start();
while (true) {
using namespace std::chrono_literals;
std::this_thread::sleep_for(2s);
}
}
void Run::run() {
@@ -1,4 +1,7 @@
#include "EMExecutionGateway.h"
#include "deribit_emumba/market_data_common.h"
#include "deribit_emumba/order_entry_common.h"
#include "deribit_emumba/price.h"
#include "lib/api/service_helpers/AdminServiceUtils.h"
#include "lib/api/services/AdminService.pb.h"
#include "lib/api/services/OrderService.pb.h"
@@ -41,9 +44,6 @@
#include <boost/proto/transform/impl.hpp>
#include <cmath>
#include <cstdint>
#include <emumba-deribit-connector/market_data_common.h>
#include <emumba-deribit-connector/order_entry_common.h>
#include <emumba-deribit-connector/price.h>
#include <optional>
#include <string>
#include <tbb/concurrent_queue.h>
@@ -0,0 +1,7 @@
#include "IMarketDataProvider.h"
namespace bts::md {
IMarketDataProvider::~IMarketDataProvider() = default;
} // namespace bts::md
@@ -0,0 +1,25 @@
#pragma once
#include "lib/api/domain/Venue.pb.h"
#include "server/sparkd/md/IMarketDataSource.h"
#include <vector>
namespace bts::md {
/**
* @brief This is a single function interface, whose sole purpose is to provide a list of market
* data sources (per venue). A good example implementation is the `CCApiMarketDataProvider`, which
* reads `MarketDataConfig` and returns an `IMarketDataSource` for each venue configuration (e.g.,
* `bts::api::md::BinanceMarketDataConfig`, `bts::api::md::CoinbaseMarketDataConfig`, etc.).
*/
class IMarketDataProvider {
public:
virtual ~IMarketDataProvider();
virtual std::vector<
std::pair<bts::api::domain::Venue, std::shared_ptr<IMarketDataSource>>>
get_sources() = 0;
};
} // namespace bts::md
@@ -0,0 +1,9 @@
#include "IMarketDataSource.h"
namespace bts::md {
IMarketDataSource::~IMarketDataSource() = default;
void IMarketDataSource::stop() {}
} // namespace bts::md
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <memory>
namespace bts::md {
class IMarketDataListener;
/**
* @brief This interface represents a source of market data. Call `IMarketDataSource::start` to
* connect an `IMarketDataListener` to the source and begin receiving market data.
*/
class IMarketDataSource {
public:
virtual ~IMarketDataSource();
virtual void start(std::shared_ptr<IMarketDataListener> listener) = 0;
virtual void stop();
};
} // namespace bts::md
+58 -382
View File
@@ -1,69 +1,40 @@
#include "MarketDataGateway.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/api/md/EmumbaDeribitMarketDataConfig.pb.h"
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/api/md/MarketDataConfigUtils.h"
#include "lib/api/md/MarketDataReplayConfig.pb.h"
#include "lib/collections/Contains.h"
#include "lib/config/protos/system_config.pb.h"
#include "lib/core/FMT.h"
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain/SecurityID.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/ProtoPrinter.h"
#include "lib/logging/logging.h"
#include "lib/md/GeneralMarketDataListener.h"
#include "lib/md/GeneralSecurityIndex.h"
#include "lib/md/IMarketDataListener.h"
#include "lib/md/MDProducer.h"
#include "lib/os/os.h"
#include "lib/persistence/IDGenerator.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/bts_fatal.h"
#include "lib/utils/thread_manager.h"
#include "server/sparkd/md/MarketDataListenerID.h"
#include "server/sparkd/md/MarketDataRegistration.h"
#include "server/sparkd/md/MarketDataRegistrationError.h"
#include "server/sparkd/md/MarketDataRegistrationFactory.h"
#include "server/sparkd/md/MarketDataSubscription.h"
#include "server/sparkd/md/binance/BinanceSubscription.h"
#include "server/sparkd/md/binance_coin_futures/binance_coin_futures_subscription.h"
#include "server/sparkd/md/binance_us/BinanceUsSubscription.h"
#include "server/sparkd/md/binance_usds_futures/BinanceUsdsFuturesSubscription.h"
#include "server/sparkd/md/cboe/CboeMarketDataHandler.h"
#include "server/sparkd/md/coinbase/CoinbaseSubscription.h"
#include "server/sparkd/md/deribit/DeribitSubscription.h"
#include "server/sparkd/md/emumba/EmumbaDeribitData.h"
#include "server/sparkd/md/ftx/FTXSubscription.h"
#include "server/sparkd/md/glbx/GlbxMarketDataHandler.h"
#include "server/sparkd/md/okx/OkxSubscription.h"
#include "server/sparkd/md/platforms/mdp3_onixs/MDP3Main.h"
#include "server/sparkd/md/recording/MDReplayer.h"
#include "server/sparkd/md/taifex/TaifexDataGateway.h"
#include "server/sparkd/md/IMarketDataProvider.h"
#include "server/sparkd/md/OnixsMarketDataProvider.h"
#include "server/sparkd/md/ReplayMarketDataProvider.h"
#include "server/sparkd/md/ccapi/CCApiMarketDataProvider.h"
#include "server/sparkd/md/databento/DatabentoMarketDataProvider.h"
#include "server/sparkd/md/emumba/EmumbaMarketDataProvider.h"
#include "server/sparkd/md/taifex/TaifexMarketDataProvider.h"
#include <bits/stdint-uintn.h>
#include <boost/filesystem.hpp>
#include <ccapi_cpp/ccapi_event_dispatcher.h>
#include <emumba-deribit-connector/deribit_utils.h>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace bts::md {
using bts::api::md::MarketDataConfig;
using bts::config::SystemConfig;
//using bts::domain::Security;
using bts::domain::SecurityID;
using bts::io::ProtoPrinter;
using bts::persistence::IDGenerator;
using std::shared_ptr;
using std::string;
using std::unique_ptr;
using std::unordered_map;
using std::vector;
using Path = boost::filesystem::path;
static bool validate(const MarketDataConfig &cfg, vector<string> &errs) {
auto orig_n_errs = errs.size();
if (!cfg.deribit().empty() && !cfg.emumba_deribit().empty()) {
@@ -73,363 +44,68 @@ static bool validate(const MarketDataConfig &cfg, vector<string> &errs) {
return errs.size() == orig_n_errs;
}
struct MarketDataListenerData {
MarketDataListenerData() = default;
MarketDataListenerData(const MarketDataListenerData &) noexcept = delete;
MarketDataListenerData(MarketDataListenerData &&) noexcept = default;
MarketDataListenerData &operator=(const MarketDataListenerData &) noexcept =
delete;
MarketDataListenerData &operator=(MarketDataListenerData &&) noexcept =
default;
~MarketDataListenerData() = default;
MarketDataListenerID id;
MarketDataSubscription subscription;
};
struct MDReplayData {
bts::api::md::MarketDataReplayConfig replay_conf;
bts::api::Venue venue;
bts::md::GeneralSecurityIndex sec_index;
bts::md::MDProducer md_producer;
bts::md::recording::MDReplayer md_replayer;
MDReplayData(const bts::api::md::MarketDataReplayConfig &replay_conf)
: replay_conf(replay_conf), venue(replay_conf.venue()), sec_index(),
md_replayer(replay_conf.venue(), sec_index, md_producer, replay_conf.replay_speed()) {}
};
struct VenueListenerData {
bts::md::GeneralSecurityIndex sec_index;
bts::md::MDProducer md_producer;
std::shared_ptr<bts::md::IMarketDataListener> listener;
utils::DispatcherTimer timer;
VenueListenerData(bts::api::Venue venue)
: sec_index(),
listener(std::make_shared<bts::md::GeneralMarketDataListener>(
sec_index, md_producer, venue)),
timer(utils::Dispatcher::background(), 200, [&]() {
md_producer.heartbeat();
}) {}
};
struct MarketDataGateway::PImpl {
api::md::MarketDataConfig md_config =
config::get<api::md::MarketDataConfig>();
ccapi::EventDispatcher dispatcher;
MarketDataRegistrationFactory registration_factory;
std::shared_ptr<IDGenerator> sec_id_store = IDGenerator::instance_ptr();
unordered_map<MarketDataListenerID, MarketDataListenerData> listeners;
std::vector<std::unique_ptr<MDReplayData>> replays = {};
// we create these and pass them to the cc md sources
std::vector<std::shared_ptr<VenueListenerData>> venue_listeners = {};
struct MdSink {
GeneralSecurityIndex sec_index;
MDProducer md_producer;
shared_ptr<IMarketDataListener> listener;
utils::DispatcherTimer timer;
MdSink(api::Venue venue, IDGenerator *sec_id_store)
: sec_index(), listener(std::make_shared<GeneralMarketDataListener>(
sec_index, md_producer, venue, sec_id_store)),
timer(utils::Dispatcher::background(), 200, [&]() {
md_producer.heartbeat();
}) {}
};
MarketDataConfig config;
shared_ptr<IDGenerator> sec_id_store;
vector<unique_ptr<IMarketDataProvider>> providers;
vector<unique_ptr<MdSink>> sinks;
vector<unique_ptr<IMarketDataSource>> sources;
PImpl(MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
: config(std::move(config)), sec_id_store(std::move(sec_id_store)) {
providers.emplace_back(std::make_unique<CCApiMarketDataProvider>(
this->config, this->sec_id_store));
providers.emplace_back(std::make_unique<EmumbaMarketDataProvider>(
this->config, this->sec_id_store));
providers.emplace_back(
std::make_unique<OnixsMarketDataProvider>(this->config));
providers.emplace_back(std::make_unique<DatabentoMarketDataProvider>(
this->config, this->sec_id_store));
providers.emplace_back(std::make_unique<TaifexMarketDataProvider>(
this->config, this->sec_id_store));
providers.emplace_back(std::make_unique<ReplayMarketDataProvider>(
this->config, this->sec_id_store));
}
};
MarketDataGateway::MarketDataGateway()
: me_(std::make_unique<MarketDataGateway::PImpl>()) {}
MarketDataGateway::MarketDataGateway(
MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
: me_(std::make_unique<MarketDataGateway::PImpl>(
std::move(config), std::move(sec_id_store))) {}
MarketDataGateway::~MarketDataGateway() = default;
// starts the market data sources and then keeps the function going
void MarketDataGateway::run() {
void MarketDataGateway::start() {
vector<string> cfg_errs;
if (!validate(me_->md_config, cfg_errs)) {
if (!validate(me_->config, cfg_errs)) {
BTS_FATAL_MSGF("Invalid MarketDataConfig:", FMT_JOIN(cfg_errs, "\n"));
return;
}
LOG(md, Info, "MarketDataGateway::run starting t_id:{} t_name:{}",
os::get_thread_id(), os::get_thread_name());
_start_cc(me_->md_config);
if (!me_->md_config.emumba_deribit().empty()) {
bts_assert(me_->md_config.emumba_deribit().size() == 1);
_start_emumba_deribit(me_->md_config.emumba_deribit().at(0));
}
start_glbx(me_->md_config);
start_taifex(me_->md_config);
start_cboe(me_->md_config);
_start_replays(me_->md_config);
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
LOG(md, Info, "MarketDataGateway::run ending t_id:{} t_name:{}",
os::get_thread_id(), os::get_thread_name());
}
// CCAPI //////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
MarketDataRegistration MarketDataGateway::_start_cc(
const MarketDataConfig &cfg) noexcept {
LOG(md, Info, "MarketDataGateway::_start_cc starting t_id:{} t_name:{}",
os::get_thread_id(), os::get_thread_name());
auto reg = me_->registration_factory.create();
bts_assert(reg.listener_id.has_value());
bts_assert(!CONTAINS_KEY(me_->listeners, *reg.listener_id));
LOG(md, Info, "MarketDataGateway subscribing to data:\n{}",
ProtoPrinter::to_string(cfg));
vector<string> cfg_errs;
if (!validate(cfg, cfg_errs)) {
return MarketDataRegistration{
{MarketDataRegistrationError::Type::CONFIG_ERROR,
fmt::to_string(fmt::join(cfg_errs, "\n"))}};
}
MarketDataListenerData &listener_data = me_->listeners[*reg.listener_id];
listener_data.id = *reg.listener_id;
// FTX
for (const auto &ftx : cfg.ftx()) {
auto p = me_->venue_listeners.emplace_back(
std::make_shared<VenueListenerData>(bts::api::VENUE_FTX));
listener_data.subscription.add(ftx::FTXSubscription{
&me_->dispatcher, ftx, me_->sec_id_store.get(), p->listener.get()});
}
// Binance
for (const auto &binance : cfg.binance()) {
auto p = me_->venue_listeners.emplace_back(
std::make_shared<VenueListenerData>(bts::api::VENUE_BINANCE));
listener_data.subscription.add(binance::BinanceSubscription{
&me_->dispatcher, binance, me_->sec_id_store.get(),
p->listener.get()});
}
// Binance Coin Futures
for (const auto &binance_cfs : cfg.binance_coin_futures()) {
auto p =
me_->venue_listeners.emplace_back(std::make_shared<VenueListenerData>(
bts::api::VENUE_BINANCE_COIN_FUTURES));
listener_data.subscription.add(
binance_coin_futures::BinanceCoinFuturesSubscription{
&me_->dispatcher, binance_cfs, me_->sec_id_store.get(),
p->listener.get()});
}
// Binance US
for (const auto &binance_us : cfg.binance_us()) {
auto p = me_->venue_listeners.emplace_back(
std::make_shared<VenueListenerData>(bts::api::VENUE_BINANCE_US));
listener_data.subscription.add(binance_us::BinanceUsSubscription{
&me_->dispatcher, binance_us, me_->sec_id_store.get(),
p->listener.get()});
}
// Coinbase
for (const auto &coinbase : cfg.coinbase()) {
auto p = me_->venue_listeners.emplace_back(
std::make_shared<VenueListenerData>(bts::api::VENUE_COINBASE));
listener_data.subscription.add(coinbase::CoinbaseSubscription{
&me_->dispatcher, coinbase, me_->sec_id_store.get(),
p->listener.get()});
}
// Binance USDS Futures
for (const auto &binance_ufs : cfg.binance_usds_futures()) {
auto p =
me_->venue_listeners.emplace_back(std::make_shared<VenueListenerData>(
bts::api::VENUE_BINANCE_USDS_FUTURES));
listener_data.subscription.add(
binance_usds_futures::BinanceUsdsFuturesSubscription{
&me_->dispatcher, binance_ufs, me_->sec_id_store.get(),
p->listener.get()});
}
// Deribit
for (const auto &deribit : cfg.deribit()) {
auto p = me_->venue_listeners.emplace_back(
std::make_shared<VenueListenerData>(bts::api::VENUE_DERIBIT));
listener_data.subscription.add(deribit::DeribitSubscription{
&me_->dispatcher, deribit, me_->sec_id_store.get(),
p->listener.get()});
}
// Okx
for (const auto &okx : cfg.okx()) {
auto p = me_->venue_listeners.emplace_back(
std::make_shared<VenueListenerData>(bts::api::VENUE_OKX));
listener_data.subscription.add(okx::OkxSubscription{
&me_->dispatcher, okx, me_->sec_id_store.get(), p->listener.get()});
}
LOG(md, Info, "MarketDataGateway::_start_cc ending t_id:{} t_name:{}",
os::get_thread_id(), os::get_thread_name());
return reg;
}
void MarketDataGateway::unsubscribe(MarketDataListenerID id) {
LOG(md, Info, "Unsubscribing listener {}", id);
me_->listeners.erase(id);
}
/// glbx //////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void MarketDataGateway::start_glbx(const bts::api::md::MarketDataConfig &cfg){
for (const auto &glbx : cfg.glbx()) {
if(!glbx.use_databento()){
_start_onix_glbx(glbx);
return;
for (auto &provider : me_->providers) {
for (auto &[venue, source] : provider->get_sources()) {
const auto v = domain::DomainSecurityUtils::convert(venue);
auto &sink = me_->sinks.emplace_back(
std::make_unique<PImpl::MdSink>(v, me_->sec_id_store.get()));
source->start(sink->listener);
}
auto p = me_->venue_listeners.emplace_back(
std::make_shared<VenueListenerData>(bts::api::VENUE_GLBX));
// Capture `p->listener` and `me_->sec_id_store` by value
auto listener = p->listener;
auto sec_id_store = me_->sec_id_store;
std::string tid_name = "glbx";
bts::threading::make_thread(tid_name, [listener, sec_id_store, glbx](){
api::md::glbx::GlbxMarketDatahandler::run(
listener,
sec_id_store,
glbx);
}).detach();
}
}
static void run_onixs() {
LOG(md, Info, "MarketDataGateway::run_onixs t_id:{} t_name",
os::get_thread_id(), os::get_thread_name());
bts::md::MBOWriter *mbo = nullptr;
bts::md::TradeSummarySignalDispatcher *tssd = nullptr;
std::vector<std::shared_ptr<bts::md::IMDPlatform>> platforms;
// get all glbx symbols
auto cme_symbols = SymbolLoader::load(
bts::domain::SecurityCriteria::create({api::domain::VENUE_GLBX}));
if (!cme_symbols.empty()) {
auto *main = new bts::md::MDP3Main();
main->start({cme_symbols.begin(), cme_symbols.end()}, mbo, tssd);
platforms.emplace_back(main);
}
// deribit
// auto *d_platform = new bts::md::EmMDListener()
// Timers
auto &disp = utils::Dispatcher::current();
utils::DispatcherTimer heartbeat_timer(disp, 250, [&]() {
for (auto &p : platforms)
p->send_heartbeats();
});
heartbeat_timer.start();
disp.run();
}
void MarketDataGateway::_start_onix_glbx(
bts::api::md::GlbxMarketDataConfig cfg) {
LOG(md, Info,
"MarketDataGateway::_start_onix_glbx starting t_id:{} t_name:{}. "
"cfg:\n{}",
os::get_thread_id(), os::get_thread_name(), cfg.DebugString());
bts::threading::make_thread("Onix CME", [&]() {
bts::md::run_onixs();
}).detach();
}
// deribit ////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void MarketDataGateway::_start_emumba_deribit(
bts::api::md::EmumbaDeribitMarketDataConfig cfg) {
LOG(md, Info,
"MarketDataGateway::_start_emumba_deribit starting t_id:{} t_name:{}. "
"cfg:\n{}",
os::get_thread_id(), os::get_thread_name(), cfg.DebugString());
auto start_deribit_thr = [cfg = std::move(cfg)]() {
const auto config_path = Path{config::get<SystemConfig>().config_path()} /
cfg.json_conf_file_name();
const auto conf =
bts::cmds::DeribitDemoConfiguration::read_file(config_path.string());
bts_assert_msgf(
conf.has_value(), "Failed to parse deribit config '{}'",
config_path.string());
// log
for (const auto &s : conf.value().symbol_config_list) {
LOG(md, Info, "sub.symbol: {}", s.symbol_name);
LOG(md, Info, "sub.futs: {} sub.options: {}", s.interested_in_future,
s.interested_in_option);
for (const auto &i : s.interested_instruments) {
LOG(md, Info, "sub.instrument: {}", i);
}
}
EmumbaDeribitData ddata_obj;
EmumbaDeribitData *ddata = &ddata_obj;
// end log
bts_assert(conf.has_value());
LOG(md, Info, "Deribit conf: {}", conf.value());
ddata->real_handler =
std::make_shared<emumba::deribit_connector::deribit_handler>(
conf.value().symbol_config_list, conf.value().trading_config,
conf.value().credential_map, conf.value().logging_configuration,
conf.value().deribit_url, &ddata->listener);
// since the trading interface is housed inside the EmMDListener, we need to
// create and send emumba's trading_session_interface
for (const auto &p : conf.value().credential_map) {
const auto &sess = p.second;
auto ts_ptr = std::make_shared<
emumba::deribit_connector::trading_session_interface>(
ddata->real_handler->create_new_trading_session(sess));
ddata->trading_session_vec.push_back({sess, ts_ptr});
}
bts_assert(!ddata->trading_session_vec.empty());
ddata->listener.set_handler(
ddata->real_handler.get(), ddata->trading_session_vec);
ddata->real_handler->run();
};
threading::make_thread("Emumba", start_deribit_thr).detach();
LOG(md, Info, "MarketDataGateway::_start_deribit ending t_id:{} t_name:{}",
os::get_thread_id(), os::get_thread_name());
}
// replays ////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void MarketDataGateway::_start_replays(
const bts::api::md::MarketDataConfig &cfg) {
LOG(md, Info, "MarketDataGateway::_start_replays starting t_id:{} t_name:{}",
os::get_thread_id(), os::get_thread_name());
for (const auto &r :
MarketDataConfigUtils::get_effective_replay_config(cfg)) {
me_->replays.emplace_back(std::make_unique<MDReplayData>(r));
}
for (const auto &replayer : me_->replays) {
std::string tid_name = "MDReplayer_" + Venue_Name(replayer->venue);
bts::threading::make_thread(tid_name, [&]() {
replayer->md_replayer.run();
}).detach();
}
LOG(md, Info, "MarketDataGateway::_start_replays ending t_id:{} t_name:{}",
os::get_thread_id(), os::get_thread_name());
}
void MarketDataGateway::start_taifex(const bts::api::md::MarketDataConfig &cfg) {
// taifex
for (const auto &taifex : cfg.taifex()) {
auto p = me_->venue_listeners.emplace_back(
std::make_shared<VenueListenerData>(bts::api::VENUE_TAIFEX));
// Capture `p->listener` and `me_->sec_id_store` by value
auto listener = p->listener;
auto sec_id_store = me_->sec_id_store;
std::string tid_name = "taifex";
bts::threading::make_thread(tid_name, [listener, sec_id_store, taifex]() {
bts::md::taifex::TaifexDataGateway::run(
taifex, listener, sec_id_store);
}).detach();
}
}
void MarketDataGateway::start_cboe(const bts::api::md::MarketDataConfig &cfg) {
for (const api::md::CboeMarketDataConfig &cboe : cfg.cboe()) {
auto p = me_->venue_listeners.emplace_back(
std::make_shared<VenueListenerData>(bts::api::VENUE_CBOE));
std::string tid_name = "CBOE";
// Capture `p->listener` and `me_->sec_id_store` by value
auto listener = p->listener;
auto sec_id_store = me_->sec_id_store;
bts::threading::make_thread(tid_name, [listener, sec_id_store, cboe]() {
api::md::cboe::CboeMarketDatahandler::run(
listener,
sec_id_store,
cboe);
}).detach();
}
}
} // namespace bts::md
+13 -24
View File
@@ -1,44 +1,33 @@
#pragma once
#include "lib/api/md/GlbxMarketDataConfig.pb.h"
#include "server/sparkd/md/MarketDataListenerID.h"
#include <memory>
namespace bts::api::md {
class EmumbaDeribitMarketDataConfig;
class MarketDataConfig;
} // namespace bts::api::md
}
namespace bts::persistence {
class IDGenerator;
}
namespace bts::md {
class IMarketDataListener;
class MarketDataRegistration;
/**
* @brief The `MarketDataGateway` is responsible for starting all configured market data sources.
* It connects each source to a special `IMarketDataListener` that writes the data to shared memory.
*/
class MarketDataGateway {
public:
MarketDataGateway();
explicit MarketDataGateway(
api::md::MarketDataConfig config,
std::shared_ptr<persistence::IDGenerator> sec_id_store);
~MarketDataGateway();
/**
* @brief Connects a market data listener to a market data source
* @return MarketDataRegistration
*/
void run();
void unsubscribe(MarketDataListenerID id);
void start();
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
MarketDataRegistration _start_cc(const api::md::MarketDataConfig &) noexcept;
static void _start_emumba_deribit(
bts::api::md::EmumbaDeribitMarketDataConfig cfg);
static void _start_onix_glbx(
bts::api::md::GlbxMarketDataConfig cfg);
void _start_replays(const bts::api::md::MarketDataConfig &cfg);
void start_glbx(const bts::api::md::MarketDataConfig &cfg);
void start_cboe(const bts::api::md::MarketDataConfig &cfg);
void start_taifex(const bts::api::md::MarketDataConfig &cfg);
};
} // namespace bts::md
@@ -1,68 +0,0 @@
#include "MarketDataSubscription.h"
#include "server/sparkd/md/binance/BinanceSubscription.h"
#include "server/sparkd/md/binance_coin_futures/binance_coin_futures_subscription.h"
#include "server/sparkd/md/binance_us/BinanceUsSubscription.h"
#include "server/sparkd/md/binance_usds_futures/BinanceUsdsFuturesSubscription.h"
#include "server/sparkd/md/coinbase/CoinbaseSubscription.h"
#include "server/sparkd/md/deribit/DeribitSubscription.h"
#include "server/sparkd/md/ftx/FTXSubscription.h"
#include "server/sparkd/md/okx/OkxSubscription.h"
#include <vector>
namespace bts::md {
using std::vector;
struct MarketDataSubscription::PImpl {
vector<ftx::FTXSubscription> ftx_subs;
vector<binance::BinanceSubscription> binance_subs;
vector<binance_coin_futures::BinanceCoinFuturesSubscription>
binance_coin_futures_subs;
vector<binance_us::BinanceUsSubscription> binance_us_subs;
vector<coinbase::CoinbaseSubscription> coinbase_subs;
vector<binance_usds_futures::BinanceUsdsFuturesSubscription>
binance_usds_futures_subs;
vector<deribit::DeribitSubscription> deribit_subs;
vector<okx::OkxSubscription> okx_subs;
// TODO: add more exchanges
};
MarketDataSubscription::MarketDataSubscription()
: me_(std::make_unique<PImpl>()) {}
MarketDataSubscription::~MarketDataSubscription() = default;
void MarketDataSubscription::add(binance::BinanceSubscription sub) {
me_->binance_subs.push_back(std::move(sub));
}
void MarketDataSubscription::add(
binance_coin_futures::BinanceCoinFuturesSubscription sub) {
me_->binance_coin_futures_subs.push_back(std::move(sub));
}
void MarketDataSubscription::add(binance_us::BinanceUsSubscription sub) {
me_->binance_us_subs.push_back(std::move(sub));
}
void MarketDataSubscription::add(coinbase::CoinbaseSubscription sub) {
me_->coinbase_subs.push_back(std::move(sub));
}
void MarketDataSubscription::add(
binance_usds_futures::BinanceUsdsFuturesSubscription sub) {
me_->binance_usds_futures_subs.push_back(std::move(sub));
}
void MarketDataSubscription::add(deribit::DeribitSubscription sub) {
me_->deribit_subs.push_back(std::move(sub));
}
void MarketDataSubscription::add(ftx::FTXSubscription sub) {
me_->ftx_subs.push_back(std::move(sub));
}
void MarketDataSubscription::add(okx::OkxSubscription sub) {
me_->okx_subs.push_back(std::move(sub));
}
} // namespace bts::md
@@ -1,64 +0,0 @@
#pragma once
#include <memory>
namespace bts::md::binance {
class BinanceSubscription;
}
namespace bts::md::binance_coin_futures {
class BinanceCoinFuturesSubscription;
}
namespace bts::md::binance_us {
class BinanceUsSubscription;
}
namespace bts::md::coinbase {
class CoinbaseSubscription;
}
namespace bts::md::binance_usds_futures {
class BinanceUsdsFuturesSubscription;
}
namespace bts::md::deribit {
class DeribitSubscription;
}
namespace bts::md::ftx {
class FTXSubscription;
}
namespace bts::md::okx {
class OkxSubscription;
}
namespace bts::md {
class MarketDataSubscription {
public:
MarketDataSubscription();
MarketDataSubscription(const MarketDataSubscription &) noexcept = delete;
MarketDataSubscription(MarketDataSubscription &&) noexcept = default;
MarketDataSubscription &operator=(const MarketDataSubscription &) noexcept =
delete;
MarketDataSubscription &operator=(MarketDataSubscription &&) noexcept =
default;
~MarketDataSubscription();
void add(binance::BinanceSubscription);
void add(binance_coin_futures::BinanceCoinFuturesSubscription);
void add(binance_us::BinanceUsSubscription);
void add(coinbase::CoinbaseSubscription);
void add(binance_usds_futures::BinanceUsdsFuturesSubscription);
void add(deribit::DeribitSubscription);
void add(ftx::FTXSubscription);
void add(okx::OkxSubscription);
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace bts::md
@@ -0,0 +1,43 @@
#include "OnixsMarketDataProvider.h"
#include "lib/api/domain/Venue.pb.h"
#include "server/sparkd/md/glbx/OnixsGlbxSubscription.h"
#include <memory>
#include <vector>
namespace bts::md {
using bts::api::domain::Venue;
using bts::api::md::MarketDataConfig;
using bts::md::OnixsMarketDataProvider;
using std::pair;
using std::shared_ptr;
using std::string;
using std::vector;
struct OnixsMarketDataProvider::PImpl {
MarketDataConfig config;
vector<shared_ptr<OnixsGlbxSubscription>> sources;
};
OnixsMarketDataProvider::OnixsMarketDataProvider(MarketDataConfig config)
: me_(std::make_unique<PImpl>()) {
me_->config = std::move(config);
}
OnixsMarketDataProvider::~OnixsMarketDataProvider() = default;
vector<pair<Venue, shared_ptr<IMarketDataSource>>>
OnixsMarketDataProvider::get_sources() {
vector<pair<Venue, shared_ptr<IMarketDataSource>>> rv;
for (const auto &glbx : me_->config.glbx()) {
if (glbx.use_databento()) {
continue;
}
me_->sources.push_back(std::make_shared<OnixsGlbxSubscription>(glbx));
rv.push_back({Venue::VENUE_GLBX, me_->sources.back()});
}
return rv;
}
} // namespace bts::md
@@ -0,0 +1,26 @@
#pragma once
#include "lib/api/md/MarketDataConfig.pb.h"
#include "server/sparkd/md/IMarketDataProvider.h"
#include "server/sparkd/md/IMarketDataSource.h"
#include <memory>
namespace bts::md {
class OnixsMarketDataProvider : public IMarketDataProvider {
public:
OnixsMarketDataProvider();
explicit OnixsMarketDataProvider(api::md::MarketDataConfig config);
~OnixsMarketDataProvider();
std::vector<
std::pair<bts::api::domain::Venue, std::shared_ptr<IMarketDataSource>>>
get_sources() override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace bts::md
@@ -0,0 +1,52 @@
#include "ReplayMarketDataProvider.h"
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/api/md/MarketDataConfigUtils.h"
#include "lib/config/config.h"
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/persistence/IDGenerator.h"
#include "server/sparkd/md/IMarketDataSource.h"
#include "server/sparkd/md/recording/MDReplayer.h"
namespace bts::md {
using bts::api::md::MarketDataConfig;
using bts::domain::DomainSecurityUtils;
using bts::md::recording::MDReplayer;
using bts::persistence::IDGenerator;
using std::pair;
using std::shared_ptr;
using std::string;
using std::vector;
struct ReplayMarketDataProvider::PImpl {
MarketDataConfig config;
shared_ptr<IDGenerator> sec_id_store;
vector<pair<api::domain::Venue, shared_ptr<IMarketDataSource>>> sources;
PImpl(MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
: config(std::move(config)), sec_id_store(std::move(sec_id_store)) {}
};
ReplayMarketDataProvider::ReplayMarketDataProvider()
: ReplayMarketDataProvider(
config::get<MarketDataConfig>(), IDGenerator::instance_ptr()) {}
ReplayMarketDataProvider::ReplayMarketDataProvider(
MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
: me_(std::make_unique<PImpl>(std::move(config), std::move(sec_id_store))) {
}
ReplayMarketDataProvider::~ReplayMarketDataProvider() = default;
vector<pair<api::domain::Venue, shared_ptr<IMarketDataSource>>>
ReplayMarketDataProvider::get_sources() {
const auto replay_cfgs =
MarketDataConfigUtils::get_effective_replay_config(me_->config);
for (const auto &r : replay_cfgs) {
const auto v = DomainSecurityUtils::convert(r.venue());
me_->sources.push_back({v, std::make_shared<MDReplayer>(r.venue())});
}
return me_->sources;
}
} // namespace bts::md
@@ -0,0 +1,28 @@
#pragma once
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/persistence/IDGenerator.h"
#include "server/sparkd/md/IMarketDataProvider.h"
#include <memory>
namespace bts::md {
class ReplayMarketDataProvider : public IMarketDataProvider {
public:
ReplayMarketDataProvider();
explicit ReplayMarketDataProvider(
api::md::MarketDataConfig config,
std::shared_ptr<persistence::IDGenerator> sec_id_store);
~ReplayMarketDataProvider();
std::vector<
std::pair<bts::api::domain::Venue, std::shared_ptr<IMarketDataSource>>>
get_sources() override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace bts::md
@@ -0,0 +1,46 @@
#include "ReplayMarketDataSubscription.h"
#include "lib/api/md/MarketDataReplayConfig.pb.h"
#include "lib/logging/logging.h"
#include "lib/md/GeneralMarketDataListener.h"
#include "lib/md/IMarketDataListener.h"
#include "lib/md/MDProducer.h"
#include "lib/utils/thread_manager.h"
#include "server/sparkd/md/recording/MDReplayer.h"
#include <ccapi_cpp/ccapi_event_dispatcher.h>
namespace bts::md {
using bts::api::md::MarketDataReplayConfig;
using bts::md::recording::MDReplayer;
using std::string;
struct ReplayMarketDataSource::PImpl {
MarketDataReplayConfig config;
PImpl(const MarketDataReplayConfig &config) : config(config) {}
};
ReplayMarketDataSource::ReplayMarketDataSource(MarketDataReplayConfig config)
: me_(std::make_unique<PImpl>(config)) {
me_->config = std::move(config);
}
ReplayMarketDataSource::~ReplayMarketDataSource() = default;
void ReplayMarketDataSource::start(
std::shared_ptr<IMarketDataListener> listener) {
LOG(md, Info, "MarketDataGateway::_start_replays starting t_id:{} t_name:{}",
os::get_thread_id(), os::get_thread_name());
auto md_replayer = std::make_shared<MDReplayer>(me_->config.venue());
std::string tid_name = "MDReplayer_" + Venue_Name(me_->config.venue());
bts::threading::make_thread(tid_name, [&, listener, md_replayer]() {
md_replayer->start(listener);
}).detach();
LOG(md, Info, "MarketDataGateway::_start_replays ending t_id:{} t_name:{}",
os::get_thread_id(), os::get_thread_name());
}
void ReplayMarketDataSource::stop() {}
} // namespace bts::md
@@ -0,0 +1,25 @@
#pragma once
#include "lib/api/md/MarketDataReplayConfig.pb.h"
#include "lib/md/IMarketDataListener.h"
#include "server/sparkd/md/IMarketDataSource.h"
#include <memory>
namespace bts::md {
class ReplayMarketDataSource : public IMarketDataSource {
public:
ReplayMarketDataSource();
explicit ReplayMarketDataSource(api::md::MarketDataReplayConfig config);
~ReplayMarketDataSource();
void start(std::shared_ptr<IMarketDataListener> listener) override;
void stop() override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace bts::md
@@ -171,7 +171,7 @@ struct BinanceSubscription::PImpl {
// The session automatically starts/stops in its ctr/dtr
CCApiSession session;
IMarketDataListener *listener;
std::shared_ptr<IMarketDataListener> listener;
BinanceSecurityRequestor sec_requestor;
// These aliases are useful for quickly switching the handler types (e.g.,
@@ -194,11 +194,10 @@ struct BinanceSubscription::PImpl {
explicit PImpl(
ccapi::EventDispatcher *dispatcher,
BinanceMarketDataConfig cfg,
IDGenerator *sec_id_store,
IMarketDataListener *listener)
IDGenerator *sec_id_store)
: sec_id_store(sec_id_store), cfg(std::move(cfg)),
criteria(SecurityCriteria::from_proto(this->cfg.security_criteria())),
session(dispatcher, mk_session_cfg(this->cfg), {}), listener(listener),
session(dispatcher, mk_session_cfg(this->cfg), {}),
sec_requestor(
&session,
this->criteria,
@@ -209,6 +208,11 @@ struct BinanceSubscription::PImpl {
LOG(md, Info, "Initializing BinanceSubscription:\n{}",
ProtoPrinter::to_string(this->cfg));
sec_requestor.callback().bind<&PImpl::on_securities>(*this);
}
void start(std::shared_ptr<IMarketDataListener> listener) {
bts_assert(listener != nullptr);
this->listener = std::move(listener);
sec_requestor.request();
}
@@ -216,7 +220,7 @@ struct BinanceSubscription::PImpl {
SecurityType sec_type, const string &sec_desc) {
SecurityID sec_id{sec_id_store->get_or_add(VENUE, sec_type, sec_desc)};
auto [it, is_new] =
depth_handlers.try_emplace(sec_desc, sec_id, listener);
depth_handlers.try_emplace(sec_desc, sec_id, listener.get());
bts_assert(is_new);
return it->second;
}
@@ -225,7 +229,7 @@ struct BinanceSubscription::PImpl {
SecurityType sec_type, const string &sec_desc) {
SecurityID sec_id{sec_id_store->get_or_add(VENUE, sec_type, sec_desc)};
auto [it, is_new] =
ticker_handlers.try_emplace(sec_desc, sec_id, listener);
ticker_handlers.try_emplace(sec_desc, sec_id, listener.get());
bts_assert_msgf(
is_new, "Ticker handler already created for '{}'", sec_desc);
return it->second;
@@ -242,7 +246,7 @@ struct BinanceSubscription::PImpl {
}
auto [it, is_new] = stats_handlers.try_emplace(
hnd_id,
std::make_unique<StatsHandler>(sec_desc_id_tuples, listener));
std::make_unique<StatsHandler>(sec_desc_id_tuples, listener.get()));
bts_assert_msgf(
is_new, "StatsHandler already created with id '{}'", hnd_id);
return *it->second;
@@ -333,10 +337,8 @@ struct BinanceSubscription::PImpl {
BinanceSubscription::BinanceSubscription(
ccapi::EventDispatcher *dispatcher,
BinanceMarketDataConfig cfg,
IDGenerator *sec_id_store,
IMarketDataListener *listener)
: me_(std::make_unique<PImpl>(
dispatcher, std::move(cfg), sec_id_store, listener)) {}
IDGenerator *sec_id_store)
: me_(std::make_unique<PImpl>(dispatcher, std::move(cfg), sec_id_store)) {}
BinanceSubscription::BinanceSubscription(BinanceSubscription &&) noexcept =
default;
@@ -345,4 +347,8 @@ BinanceSubscription &BinanceSubscription::operator=(
BinanceSubscription::~BinanceSubscription() = default;
void BinanceSubscription::start(std::shared_ptr<IMarketDataListener> listener) {
me_->start(listener);
}
} // namespace bts::md::binance
@@ -1,5 +1,7 @@
#pragma once
#include "server/sparkd/md/IMarketDataSource.h"
#include <memory>
namespace ccapi {
@@ -25,13 +27,12 @@ namespace bts::md::binance {
*
* @a https://binance-docs.github.io/apidocs/spot/en/#exchange-information
*/
class BinanceSubscription {
class BinanceSubscription : public IMarketDataSource {
public:
BinanceSubscription(
ccapi::EventDispatcher *dispatcher,
api::md::BinanceMarketDataConfig cfg,
persistence::IDGenerator *sec_id_store,
IMarketDataListener *listener);
persistence::IDGenerator *sec_id_store);
BinanceSubscription(const BinanceSubscription &) noexcept = delete;
BinanceSubscription(BinanceSubscription &&) noexcept;
@@ -39,10 +40,9 @@ class BinanceSubscription {
delete;
BinanceSubscription &operator=(BinanceSubscription &&) noexcept;
~BinanceSubscription();
~BinanceSubscription() override;
void add_listener(IMarketDataListener *listener);
void remove_listener(IMarketDataListener *listener);
void start(std::shared_ptr<IMarketDataListener> listener) override;
private:
struct PImpl;
@@ -47,6 +47,7 @@ using bts::io::ProtoPrinter;
using bts::io::QueryParams;
using bts::persistence::IDGenerator;
using std::pair;
using std::shared_ptr;
using std::string;
using std::tuple;
using std::uint32_t;
@@ -174,7 +175,7 @@ struct BinanceUsSubscription::PImpl {
// The session automatically starts/stops in its ctr/dtr
CCApiSession session;
IMarketDataListener *listener;
shared_ptr<IMarketDataListener> listener;
BinanceUsSecurityRequestor sec_requestor;
// These aliases are useful for quickly switching the handler types (e.g.,
@@ -191,17 +192,16 @@ struct BinanceUsSubscription::PImpl {
// different map, 'stats_handlers' (unique ids are generated w/ a counter,
// 'stats_handlers_id_counter').
unordered_map<string, StatsHandler *> sec_stats_handler_map;
unordered_map<uint32_t, std::unique_ptr<StatsHandler>> stats_handlers;
unordered_map<uint32_t, unique_ptr<StatsHandler>> stats_handlers;
uint32_t stats_handlers_id_counter = 0;
explicit PImpl(
ccapi::EventDispatcher *dispatcher,
BinanceUsMarketDataConfig cfg,
IDGenerator *sec_id_store,
IMarketDataListener *listener)
IDGenerator *sec_id_store)
: sec_id_store(sec_id_store), cfg(std::move(cfg)),
criteria(SecurityCriteria::from_proto(this->cfg.security_criteria())),
session(dispatcher, mk_session_cfg(this->cfg), {}), listener(listener),
session(dispatcher, mk_session_cfg(this->cfg), {}),
sec_requestor(
&session,
this->criteria,
@@ -212,6 +212,11 @@ struct BinanceUsSubscription::PImpl {
LOG(md, Info, "Initializing BinanceUsSubscription:\n{}",
ProtoPrinter::to_string(this->cfg));
sec_requestor.callback().bind<&PImpl::on_securities>(*this);
}
void start(std::shared_ptr<IMarketDataListener> listener) {
bts_assert(listener != nullptr);
this->listener = std::move(listener);
sec_requestor.request();
}
@@ -219,7 +224,7 @@ struct BinanceUsSubscription::PImpl {
SecurityType sec_type, const string &sec_desc) {
SecurityID sec_id{sec_id_store->get_or_add(VENUE, sec_type, sec_desc)};
auto [it, is_new] =
depth_handlers.try_emplace(sec_desc, sec_id, listener);
depth_handlers.try_emplace(sec_desc, sec_id, listener.get());
bts_assert(is_new);
return it->second;
}
@@ -228,7 +233,7 @@ struct BinanceUsSubscription::PImpl {
SecurityType sec_type, const string &sec_desc) {
SecurityID sec_id{sec_id_store->get_or_add(VENUE, sec_type, sec_desc)};
auto [it, is_new] =
ticker_handlers.try_emplace(sec_desc, sec_id, listener);
ticker_handlers.try_emplace(sec_desc, sec_id, listener.get());
bts_assert_msgf(
is_new, "Ticker handler already created for '{}'", sec_desc);
return it->second;
@@ -245,7 +250,7 @@ struct BinanceUsSubscription::PImpl {
}
auto [it, is_new] = stats_handlers.try_emplace(
hnd_id,
std::make_unique<StatsHandler>(sec_desc_id_tuples, listener));
std::make_unique<StatsHandler>(sec_desc_id_tuples, listener.get()));
bts_assert_msgf(
is_new, "StatsHandler already created with id '{}'", hnd_id);
return *it->second;
@@ -335,10 +340,8 @@ struct BinanceUsSubscription::PImpl {
BinanceUsSubscription::BinanceUsSubscription(
ccapi::EventDispatcher *dispatcher,
BinanceUsMarketDataConfig cfg,
IDGenerator *sec_id_store,
IMarketDataListener *listener)
: me_(std::make_unique<PImpl>(
dispatcher, std::move(cfg), sec_id_store, listener)) {}
IDGenerator *sec_id_store)
: me_(std::make_unique<PImpl>(dispatcher, std::move(cfg), sec_id_store)) {}
BinanceUsSubscription::BinanceUsSubscription(
BinanceUsSubscription &&) noexcept = default;
@@ -347,4 +350,8 @@ BinanceUsSubscription &BinanceUsSubscription::operator=(
BinanceUsSubscription::~BinanceUsSubscription() = default;
void BinanceUsSubscription::start(shared_ptr<IMarketDataListener> listener) {
me_->start(std::move(listener));
}
} // namespace bts::md::binance_us
@@ -1,5 +1,7 @@
#pragma once
#include "server/sparkd/md/IMarketDataSource.h"
#include <memory>
namespace ccapi {
@@ -25,13 +27,12 @@ namespace bts::md::binance_us {
*
* @a https://docs.binance.us/#get-exchange-information
*/
class BinanceUsSubscription {
class BinanceUsSubscription : public IMarketDataSource {
public:
BinanceUsSubscription(
ccapi::EventDispatcher *dispatcher,
api::md::BinanceUsMarketDataConfig cfg,
persistence::IDGenerator *sec_id_store,
IMarketDataListener *listener);
persistence::IDGenerator *sec_id_store);
BinanceUsSubscription(const BinanceUsSubscription &) noexcept = delete;
BinanceUsSubscription(BinanceUsSubscription &&) noexcept;
@@ -41,8 +42,7 @@ class BinanceUsSubscription {
~BinanceUsSubscription();
void add_listener(IMarketDataListener *listener);
void remove_listener(IMarketDataListener *listener);
void start(std::shared_ptr<IMarketDataListener> listener) override;
private:
struct PImpl;
@@ -41,6 +41,7 @@ using bts::io::ProtoPrinter;
using bts::io::QueryParams;
using bts::persistence::IDGenerator;
using std::pair;
using std::shared_ptr;
using std::string;
using std::uint32_t;
using std::unique_ptr;
@@ -167,7 +168,7 @@ struct BinanceUsdsFuturesSubscription::PImpl {
SecurityCriteria criteria;
// The session automatically starts/stops in its ctr/dtr
CCApiSession session;
IMarketDataListener *listener;
std::shared_ptr<IMarketDataListener> listener;
BinanceUsdsFuturesSecurityRequestor sec_requestor;
// These aliases are useful for quickly switching the handler types (e.g.,
@@ -183,15 +184,19 @@ struct BinanceUsdsFuturesSubscription::PImpl {
explicit PImpl(
ccapi::EventDispatcher *dispatcher,
BinanceUsdsFuturesMarketDataConfig cfg,
IDGenerator *sec_id_store,
IMarketDataListener *listener)
IDGenerator *sec_id_store)
: sec_id_store(sec_id_store), cfg(std::move(cfg)),
criteria(SecurityCriteria::from_proto(this->cfg.security_criteria())),
session(dispatcher, mk_session_cfg(this->cfg), {}), listener(listener),
session(dispatcher, mk_session_cfg(this->cfg), {}),
sec_requestor(&session, this->cfg, sec_id_store) {
LOG(md, Info, "Initializing BinanceUsdsFuturesSubscription:\n{}",
ProtoPrinter::to_string(this->cfg));
sec_requestor.callback().bind<&PImpl::on_securities>(*this);
}
void start(shared_ptr<IMarketDataListener> listener) {
bts_assert(listener != nullptr);
this->listener = std::move(listener);
sec_requestor.request();
}
@@ -203,7 +208,7 @@ struct BinanceUsdsFuturesSubscription::PImpl {
SecurityType sec_type, const string &sec_desc) {
const auto sec_id = get_sec_id(sec_type, sec_desc);
auto [it, is_new] =
depth_handlers.try_emplace(sec_desc, sec_id, listener);
depth_handlers.try_emplace(sec_desc, sec_id, listener.get());
bts_assert_msgf(
is_new, "Depth handler already created for '{}'", sec_desc);
return it->second;
@@ -213,7 +218,7 @@ struct BinanceUsdsFuturesSubscription::PImpl {
SecurityType sec_type, const string &sec_desc) {
const auto sec_id = get_sec_id(sec_type, sec_desc);
auto [it, is_new] =
ticker_handlers.try_emplace(sec_desc, sec_id, listener);
ticker_handlers.try_emplace(sec_desc, sec_id, listener.get());
bts_assert_msgf(
is_new, "Ticker handler already created for '{}'", sec_desc);
return it->second;
@@ -223,7 +228,7 @@ struct BinanceUsdsFuturesSubscription::PImpl {
SecurityType sec_type, const string &sec_desc) {
const auto sec_id = get_sec_id(sec_type, sec_desc);
auto [it, is_new] =
stats_handlers.try_emplace(sec_desc, sec_id, listener);
stats_handlers.try_emplace(sec_desc, sec_id, listener.get());
bts_assert_msgf(
is_new, "Stats handler already created for '{}'", sec_desc);
return it->second;
@@ -297,10 +302,8 @@ struct BinanceUsdsFuturesSubscription::PImpl {
BinanceUsdsFuturesSubscription::BinanceUsdsFuturesSubscription(
ccapi::EventDispatcher *dispatcher,
BinanceUsdsFuturesMarketDataConfig cfg,
IDGenerator *sec_id_store,
IMarketDataListener *listener)
: me_(std::make_unique<PImpl>(
dispatcher, std::move(cfg), sec_id_store, listener)) {}
IDGenerator *sec_id_store)
: me_(std::make_unique<PImpl>(dispatcher, std::move(cfg), sec_id_store)) {}
BinanceUsdsFuturesSubscription::BinanceUsdsFuturesSubscription(
BinanceUsdsFuturesSubscription &&) noexcept = default;
@@ -309,4 +312,9 @@ BinanceUsdsFuturesSubscription &BinanceUsdsFuturesSubscription::operator=(
BinanceUsdsFuturesSubscription::~BinanceUsdsFuturesSubscription() = default;
void BinanceUsdsFuturesSubscription::start(
shared_ptr<IMarketDataListener> listener) {
me_->start(std::move(listener));
}
} // namespace bts::md::binance_usds_futures
@@ -1,5 +1,7 @@
#pragma once
#include "server/sparkd/md/IMarketDataSource.h"
#include <memory>
namespace ccapi {
@@ -25,13 +27,12 @@ namespace bts::md::binance_usds_futures {
*
* @a https://binance-docs.github.io/apidocs/futures/en/#exchange-information
*/
class BinanceUsdsFuturesSubscription {
class BinanceUsdsFuturesSubscription : public IMarketDataSource {
public:
BinanceUsdsFuturesSubscription(
ccapi::EventDispatcher *dispatcher,
api::md::BinanceUsdsFuturesMarketDataConfig cfg,
persistence::IDGenerator *sec_id_store,
IMarketDataListener *listener);
persistence::IDGenerator *sec_id_store);
BinanceUsdsFuturesSubscription(
const BinanceUsdsFuturesSubscription &) noexcept = delete;
@@ -43,8 +44,7 @@ class BinanceUsdsFuturesSubscription {
~BinanceUsdsFuturesSubscription();
void add_listener(IMarketDataListener *listener);
void remove_listener(IMarketDataListener *listener);
void start(std::shared_ptr<IMarketDataListener> listener) override;
private:
struct PImpl;
@@ -0,0 +1,139 @@
#include "CCApiMarketDataProvider.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/config/config.h"
#include "lib/io/ProtoPrinter.h"
#include "lib/logging/logging.h"
#include "lib/md/GeneralMarketDataListener.h"
#include "lib/md/GeneralSecurityIndex.h"
#include "lib/md/MDProducer.h"
#include "lib/persistence/IDGenerator.h"
#include "server/sparkd/md/binance/BinanceSubscription.h"
#include "server/sparkd/md/binance_us/BinanceUsSubscription.h"
#include "server/sparkd/md/binance_usds_futures/BinanceUsdsFuturesSubscription.h"
#include "server/sparkd/md/coinbase/CoinbaseSubscription.h"
#include "server/sparkd/md/deribit/DeribitSubscription.h"
#include "server/sparkd/md/ftx/FTXSubscription.h"
#include "server/sparkd/md/okx/OkxSubscription.h"
#include <ccapi_cpp/ccapi_event_dispatcher.h>
namespace bts::md {
using bts::api::md::MarketDataConfig;
using bts::persistence::IDGenerator;
using std::pair;
using std::shared_ptr;
using std::string;
using std::unique_ptr;
using std::unordered_map;
using std::vector;
static bool validate(
const MarketDataConfig & /*cfg*/, vector<string> & /*errs*/) {
return true;
}
struct CCApiMarketDataProvider::PImpl {
struct VenueSink {
GeneralSecurityIndex sec_index;
MDProducer md_producer;
GeneralMarketDataListener listener;
VenueSink(api::Venue venue, shared_ptr<IDGenerator> sec_id_store)
: sec_index(),
listener(sec_index, md_producer, venue, sec_id_store.get()) {}
};
MarketDataConfig config;
ccapi::EventDispatcher dispatcher;
shared_ptr<IDGenerator> sec_id_store;
unordered_map<api::Venue, unique_ptr<VenueSink>> venue_sinks;
vector<std::pair<api::domain::Venue, std::shared_ptr<IMarketDataSource>>>
sources;
PImpl(MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
: config(std::move(config)), sec_id_store(std::move(sec_id_store)) {}
VenueSink *get_venue_sink(api::Venue venue) {
auto it = venue_sinks.find(venue);
if (it != venue_sinks.end()) {
return it->second.get();
}
return venue_sinks
.try_emplace(venue, std::make_unique<VenueSink>(venue, sec_id_store))
.first->second.get();
}
};
CCApiMarketDataProvider::CCApiMarketDataProvider(
MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
: me_(std::make_unique<PImpl>(std::move(config), std::move(sec_id_store))) {
}
CCApiMarketDataProvider::~CCApiMarketDataProvider() = default;
vector<pair<api::domain::Venue, shared_ptr<IMarketDataSource>>>
CCApiMarketDataProvider::get_sources() {
auto &cfg = me_->config;
vector<string> cfg_errs;
if (!validate(cfg, cfg_errs)) {
LOG(md, Error,
"CCApiMarketDataProvider::get_sources failed. Invalid config: {}",
fmt::to_string(fmt::join(cfg_errs, "\n")));
}
// Binance
for (const auto &binance : cfg.binance()) {
me_->sources.emplace_back(
api::domain::VENUE_BINANCE,
std::make_shared<binance::BinanceSubscription>(
&me_->dispatcher, binance, me_->sec_id_store.get()));
}
// Binance US
for (const auto &binance_us : cfg.binance_us()) {
me_->sources.emplace_back(
api::domain::VENUE_BINANCE_US,
std::make_shared<binance_us::BinanceUsSubscription>(
&me_->dispatcher, binance_us, me_->sec_id_store.get()));
}
// Binance USDS Futures
for (const auto &binance_ufs : cfg.binance_usds_futures()) {
me_->sources.emplace_back(
api::domain::VENUE_BINANCE_USDS_FUTURES,
std::make_shared<
binance_usds_futures::BinanceUsdsFuturesSubscription>(
&me_->dispatcher, binance_ufs, me_->sec_id_store.get()));
}
// Coinbase
for (const auto &coinbase : cfg.coinbase()) {
me_->sources.emplace_back(
api::domain::VENUE_COINBASE,
std::make_shared<coinbase::CoinbaseSubscription>(
&me_->dispatcher, coinbase, me_->sec_id_store.get()));
}
// Deribit
for (const auto &deribit : cfg.deribit()) {
me_->sources.emplace_back(
api::domain::VENUE_DERIBIT,
std::make_shared<deribit::DeribitSubscription>(
&me_->dispatcher, deribit, me_->sec_id_store.get()));
}
// FTX
for (const auto &ftx : cfg.ftx()) {
me_->sources.emplace_back(
api::domain::VENUE_FTX,
std::make_shared<ftx::FTXSubscription>(
&me_->dispatcher, ftx, me_->sec_id_store.get()));
}
// Okx
for (const auto &okx : cfg.okx()) {
me_->sources.emplace_back(
api::domain::VENUE_OKX,
std::make_shared<okx::OkxSubscription>(
&me_->dispatcher, okx, me_->sec_id_store.get()));
}
return me_->sources;
}
} // namespace bts::md
@@ -0,0 +1,27 @@
#pragma once
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/persistence/IDGenerator.h"
#include "server/sparkd/md/IMarketDataProvider.h"
#include <memory>
namespace bts::md {
class CCApiMarketDataProvider : public IMarketDataProvider {
public:
explicit CCApiMarketDataProvider(
api::md::MarketDataConfig config,
std::shared_ptr<persistence::IDGenerator> sec_id_store);
~CCApiMarketDataProvider();
std::vector<
std::pair<api::domain::Venue, std::shared_ptr<IMarketDataSource>>>
get_sources() override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace bts::md
@@ -5,8 +5,10 @@
#include "lib/domain/SecurityCriteria.h"
#include "lib/io/ProtoPrinter.h"
#include "lib/io/QueryParams.h"
#include "lib/md/IMarketDataListener.h"
#include "lib/md/common.h"
#include "lib/persistence/IDGenerator.h"
#include "lib/utils/bts_assert.h"
#include "server/sparkd/md/ccapi/CCApiMarketDepthAdaptor.h"
#include "server/sparkd/md/ccapi/CCApiSession.h"
#include "server/sparkd/md/ccapi/CCApiTradeAdaptor.h"
@@ -36,6 +38,7 @@ using bts::io::ProtoPrinter;
using bts::io::QueryParams;
using bts::persistence::IDGenerator;
using std::pair;
using std::shared_ptr;
using std::string;
using std::unique_ptr;
using std::unordered_map;
@@ -151,7 +154,7 @@ struct CoinbaseSubscription::PImpl {
// The session automatically starts/stops in its ctr/dtr
CCApiSession session;
IMarketDataListener *listener;
shared_ptr<IMarketDataListener> listener;
CoinbaseSecurityRequestor sec_requestor;
// These aliases are useful for quickly switching the handler types (e.g.,
@@ -172,11 +175,10 @@ struct CoinbaseSubscription::PImpl {
explicit PImpl(
ccapi::EventDispatcher *dispatcher,
CoinbaseMarketDataConfig cfg,
IDGenerator *sec_id_store,
IMarketDataListener *listener)
IDGenerator *sec_id_store)
: sec_id_store(sec_id_store), cfg(std::move(cfg)),
criteria(SecurityCriteria::from_proto(this->cfg.security_criteria())),
session(dispatcher, mk_session_cfg(this->cfg), {}), listener(listener),
session(dispatcher, mk_session_cfg(this->cfg), {}),
sec_requestor(
&session,
this->criteria,
@@ -187,6 +189,11 @@ struct CoinbaseSubscription::PImpl {
LOG(md, Info, "Initializing CoinbaseSubscription:\n{}",
ProtoPrinter::to_string(this->cfg));
sec_requestor.callback().bind<&PImpl::on_securities>(*this);
}
void start(shared_ptr<IMarketDataListener> listener) {
bts_assert(listener != nullptr);
this->listener = std::move(listener);
sec_requestor.request();
}
@@ -195,7 +202,7 @@ struct CoinbaseSubscription::PImpl {
auto sec_id =
SecurityID{sec_id_store->get_or_add(VENUE, sec_type, sec_desc)};
auto [it, is_new] =
stats_handlers.try_emplace(sec_desc, sec_id, listener);
stats_handlers.try_emplace(sec_desc, sec_id, listener.get());
bts_assert_msgf(
is_new, "StatsHandler already created with id '{}'", sec_id);
return it->second;
@@ -204,7 +211,7 @@ struct CoinbaseSubscription::PImpl {
SecurityType sec_type, const string &sec_desc) {
SecurityID sec_id{sec_id_store->get_or_add(VENUE, sec_type, sec_desc)};
auto [it, is_new] =
depth_handlers.try_emplace(sec_desc, sec_id, listener);
depth_handlers.try_emplace(sec_desc, sec_id, listener.get());
bts_assert(is_new);
return it->second;
}
@@ -213,7 +220,7 @@ struct CoinbaseSubscription::PImpl {
SecurityType sec_type, const string &sec_desc) {
SecurityID sec_id{sec_id_store->get_or_add(VENUE, sec_type, sec_desc)};
auto [it, is_new] =
ticker_handlers.try_emplace(sec_desc, sec_id, listener);
ticker_handlers.try_emplace(sec_desc, sec_id, listener.get());
bts_assert_msgf(
is_new, "Ticker handler already created for '{}'", sec_desc);
return it->second;
@@ -285,10 +292,8 @@ struct CoinbaseSubscription::PImpl {
CoinbaseSubscription::CoinbaseSubscription(
ccapi::EventDispatcher *dispatcher,
CoinbaseMarketDataConfig cfg,
IDGenerator *sec_id_store,
IMarketDataListener *listener)
: me_(std::make_unique<PImpl>(
dispatcher, std::move(cfg), sec_id_store, listener)) {}
IDGenerator *sec_id_store)
: me_(std::make_unique<PImpl>(dispatcher, std::move(cfg), sec_id_store)) {}
CoinbaseSubscription::CoinbaseSubscription(CoinbaseSubscription &&) noexcept =
default;
@@ -297,4 +302,8 @@ CoinbaseSubscription &CoinbaseSubscription::operator=(
CoinbaseSubscription::~CoinbaseSubscription() = default;
void CoinbaseSubscription::start(shared_ptr<IMarketDataListener> listener) {
me_->start(std::move(listener));
}
} // namespace bts::md::coinbase
@@ -1,5 +1,7 @@
#pragma once
#include "server/sparkd/md/IMarketDataSource.h"
#include <memory>
namespace ccapi {
@@ -20,13 +22,12 @@ class IDGenerator;
namespace bts::md::coinbase {
class CoinbaseSubscription {
class CoinbaseSubscription : public IMarketDataSource {
public:
CoinbaseSubscription(
ccapi::EventDispatcher *dispatcher,
api::md::CoinbaseMarketDataConfig cfg,
persistence::IDGenerator *sec_id_store,
IMarketDataListener *listener);
persistence::IDGenerator *sec_id_store);
CoinbaseSubscription(const CoinbaseSubscription &) noexcept = delete;
CoinbaseSubscription(CoinbaseSubscription &&) noexcept;
@@ -36,8 +37,7 @@ class CoinbaseSubscription {
~CoinbaseSubscription();
void add_listener(IMarketDataListener *listener);
void remove_listener(IMarketDataListener *listener);
void start(std::shared_ptr<IMarketDataListener> listener) override;
private:
struct PImpl;
@@ -0,0 +1,52 @@
#include "DatabentoMarketDataProvider.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/persistence/IDGenerator.h"
#include "server/sparkd/md/IMarketDataSource.h"
#include "server/sparkd/md/databento/DatabentoDataHandler.h"
#include "server/sparkd/md/glbx/DatabentoGlbxSubscription.h"
#include <memory>
#include <vector>
namespace bts::md {
using bts::api::md::MarketDataConfig;
using bts::persistence::IDGenerator;
using std::pair;
using std::shared_ptr;
using std::string;
using std::vector;
struct DatabentoMarketDataProvider::PImpl {
MarketDataConfig config;
shared_ptr<IDGenerator> sec_id_store;
vector<shared_ptr<IMarketDataSource>> glbx_sources;
PImpl(MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
: config(std::move(config)), sec_id_store(std::move(sec_id_store)) {}
};
DatabentoMarketDataProvider::DatabentoMarketDataProvider(
MarketDataConfig config, shared_ptr<IDGenerator> sec_id_store)
: me_(std::make_unique<PImpl>(std::move(config), std::move(sec_id_store))) {
me_->config = std::move(config);
}
DatabentoMarketDataProvider::~DatabentoMarketDataProvider() = default;
vector<pair<api::domain::Venue, shared_ptr<IMarketDataSource>>>
DatabentoMarketDataProvider::get_sources() {
vector<pair<api::domain::Venue, shared_ptr<IMarketDataSource>>> rv;
for (const auto &glbx : me_->config.glbx()) {
if (glbx.use_databento()) {
me_->glbx_sources.push_back(
std::make_shared<DatabentoGlbxSubscription>(
glbx, me_->sec_id_store));
rv.push_back({api::domain::VENUE_GLBX, me_->glbx_sources.back()});
}
}
return rv;
}
} // namespace bts::md
@@ -0,0 +1,33 @@
#pragma once
#include "server/sparkd/md/IMarketDataProvider.h"
#include <memory>
namespace bts::persistence {
class IDGenerator;
}
namespace bts::api::md {
class MarketDataConfig;
}
namespace bts::md {
class DatabentoMarketDataProvider : public IMarketDataProvider {
public:
explicit DatabentoMarketDataProvider(
api::md::MarketDataConfig config,
std::shared_ptr<persistence::IDGenerator> sec_id_store);
~DatabentoMarketDataProvider();
std::vector<
std::pair<api::domain::Venue, std::shared_ptr<IMarketDataSource>>>
get_sources() override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
};
} // namespace bts::md
@@ -52,6 +52,7 @@ using bts::io::websocket::JsonRpcId;
using bts::io::websocket::JsonRpcRequest;
using bts::persistence::IDGenerator;
using nlohmann::json;
using std::shared_ptr;
using std::string;
using std::uint32_t;
using std::unique_ptr;
@@ -134,14 +135,13 @@ struct DeribitSubscription::PImpl {
const bool subscribe_trades = true;
size_t sub_id_counter = 0;
CCApiSession session;
IMarketDataListener *listener;
shared_ptr<IMarketDataListener> listener;
DeribitChannelDemuxer channel_demuxer;
explicit PImpl(
ccapi::EventDispatcher *dispatcher,
DeribitMarketDataConfig cfg,
IDGenerator *sec_id_store,
IMarketDataListener *listener)
IDGenerator *sec_id_store)
: cfg(std::move(cfg)),
criteria(SecurityCriteria::from_proto(this->cfg.security_criteria())),
excluded_secs(
@@ -159,11 +159,15 @@ struct DeribitSubscription::PImpl {
criteria,
&instrument_store,
{this->cfg.currencies().begin(), this->cfg.currencies().end()}),
session(dispatcher, mk_session_cfg(this->cfg), {}),
listener(listener) {
session(dispatcher, mk_session_cfg(this->cfg), {}) {
// DON'T reference the ctr arg 'cfg' (it has been moved to 'this->cfg')
_INFO("ctr. config:\n{}", ProtoPrinter::to_string(this->cfg));
sec_requestor.callback().bind<&PImpl::on_securities>(*this);
}
void start(std::shared_ptr<IMarketDataListener> listener) {
bts_assert(listener != nullptr);
this->listener = std::move(listener);
sec_requestor.request_all();
subscribe_instrument_state();
}
@@ -172,7 +176,7 @@ struct DeribitSubscription::PImpl {
SecurityType sec_type, const string &sec_desc) {
const SecurityID sid{sec_id_store->get_or_add(VENUE, sec_type, sec_desc)};
auto [it, is_new] = security_data_map.try_emplace(
sec_desc, std::make_unique<SecurityData>(sid, listener));
sec_desc, std::make_unique<SecurityData>(sid, listener.get()));
_ASSERT_MSGF(is_new, "add_security_data. Already added '{}'", sec_desc);
_INFO("add_security_data. sec: '{}'", sec_desc);
return *it->second;
@@ -303,10 +307,8 @@ struct DeribitSubscription::PImpl {
DeribitSubscription::DeribitSubscription(
ccapi::EventDispatcher *dispatcher,
DeribitMarketDataConfig cfg,
IDGenerator *sec_id_store,
IMarketDataListener *listener)
: me_(std::make_unique<PImpl>(
dispatcher, std::move(cfg), sec_id_store, listener)) {}
IDGenerator *sec_id_store)
: me_(std::make_unique<PImpl>(dispatcher, std::move(cfg), sec_id_store)) {}
DeribitSubscription::DeribitSubscription(DeribitSubscription &&) noexcept =
default;
@@ -315,4 +317,8 @@ DeribitSubscription &DeribitSubscription::operator=(
DeribitSubscription::~DeribitSubscription() = default;
void DeribitSubscription::start(shared_ptr<IMarketDataListener> listener) {
me_->start(std::move(listener));
}
} // namespace bts::md::deribit
@@ -1,5 +1,7 @@
#pragma once
#include "server/sparkd/md/IMarketDataSource.h"
#include <memory>
namespace ccapi {
@@ -24,13 +26,12 @@ namespace bts::md::deribit {
* @brief Subscribes to market data at Deribit
* @a https://docs.deribit.com/?#market-data
*/
class DeribitSubscription {
class DeribitSubscription : public IMarketDataSource {
public:
DeribitSubscription(
ccapi::EventDispatcher *dispatcher,
api::md::DeribitMarketDataConfig cfg,
persistence::IDGenerator *sec_id_store,
IMarketDataListener *listener);
persistence::IDGenerator *sec_id_store);
DeribitSubscription(const DeribitSubscription &) noexcept = delete;
DeribitSubscription(DeribitSubscription &&) noexcept;
@@ -40,9 +41,8 @@ class DeribitSubscription {
~DeribitSubscription();
void add_listener(IMarketDataListener *listener);
void remove_listener(IMarketDataListener *listener);
void start(std::shared_ptr<IMarketDataListener> listener) override;
private:
struct PImpl;
std::unique_ptr<PImpl> me_;
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff. Show more