Skip to content

Commit

Permalink
feat: add java binding
Browse files Browse the repository at this point in the history
  • Loading branch information
cryptochassis committed Jul 18, 2023
1 parent 8bd2082 commit d32f42b
Show file tree
Hide file tree
Showing 29 changed files with 199 additions and 65 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,4 @@ docs/_build/
# Other
.project
.venv/
target/
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Breaking changes in v6: Greetings, Ladies and Gentlemen, we've introduced some s
mkdir binding/build
cd binding/build
rm -rf * (if rebuild from scratch)
cmake -DBUILD_PYTHON=ON -DINSTALL_PYTHON=ON .. (optional: -DBUILD_VERSION=<anything>)
cmake -DBUILD_PYTHON=ON .. (optional: -DBUILD_VERSION=<anything>)
cmake --build .
cmake --install .
```
Expand Down Expand Up @@ -155,7 +155,7 @@ For a specific exchange and instrument, get recents trades.

**Code 1:**

[C++](example/src/market_data_simple_request/main.cpp) / [Python](binding/python/example/src/market_data_simple_request/main.py)
[C++](example/src/market_data_simple_request/main.cpp) / [Python](binding/python/example/market_data_simple_request/main.py)
```
#include "ccapi_cpp/ccapi_session.h"
namespace ccapi {
Expand Down Expand Up @@ -228,7 +228,7 @@ For a specific exchange and instrument, whenever the best bid's or ask's price o

**Code 2:**

[C++](example/src/market_data_simple_subscription/main.cpp) / [Python](binding/python/example/src/market_data_simple_subscription/main.py)
[C++](example/src/market_data_simple_subscription/main.cpp) / [Python](binding/python/example/market_data_simple_subscription/main.py)
```
#include "ccapi_cpp/ccapi_session.h"
namespace ccapi {
Expand Down Expand Up @@ -402,7 +402,7 @@ For a specific exchange and instrument, submit a simple limit order.

**Code 1:**

[C++](example/src/execution_management_simple_request/main.cpp) / [Python](binding/python/example/src/execution_management_simple_request/main.py)
[C++](example/src/execution_management_simple_request/main.cpp) / [Python](binding/python/example/execution_management_simple_request/main.py)
```
#include "ccapi_cpp/ccapi_session.h"
namespace ccapi {
Expand Down Expand Up @@ -492,7 +492,7 @@ For a specific exchange and instrument, receive order updates.

**Code 2:**

[C++](example/src/execution_management_simple_subscription/main.cpp) / [Python](binding/python/example/src/execution_management_simple_subscription/main.py)
[C++](example/src/execution_management_simple_subscription/main.cpp) / [Python](binding/python/example/execution_management_simple_subscription/main.py)
```
#include "ccapi_cpp/ccapi_session.h"
namespace ccapi {
Expand Down Expand Up @@ -735,7 +735,7 @@ For a specific exchange and instrument, submit a simple limit order.

**Code:**

[C++](example/src/fix_simple/main.cpp) / [Python](binding/python/example/src/fix_simple/main.py)
[C++](example/src/fix_simple/main.cpp) / [Python](binding/python/example/fix_simple/main.py)
```
#include "ccapi_cpp/ccapi_session.h"
namespace ccapi {
Expand Down Expand Up @@ -879,7 +879,7 @@ An example can be found [here](example/src/market_data_advanced_subscription/mai

#### Enable library logging

[C++](example/src/enable_library_logging/main.cpp) / [Python](binding/python/example/src/enable_library_logging/main.py)
[C++](example/src/enable_library_logging/main.cpp) / [Python](binding/python/example/enable_library_logging/main.py)

Extend a subclass, e.g. `MyLogger`, from class `Logger` and override method `logMessage`. Assign a `MyLogger` pointer to `Logger::logger`. Add one of the following macros in the compiler command line: `CCAPI_ENABLE_LOG_TRACE`, `CCAPI_ENABLE_LOG_DEBUG`, `CCAPI_ENABLE_LOG_INFO`, `CCAPI_ENABLE_LOG_WARN`, `CCAPI_ENABLE_LOG_ERROR`, `CCAPI_ENABLE_LOG_FATAL`. Enable logging if you'd like to inspect raw responses/messages from the exchange for troubleshooting purposes.
```
Expand Down
14 changes: 13 additions & 1 deletion binding/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ option(BUILD_PYTHON "Build Python Library" OFF)
option(INSTALL_PYTHON "Install Python Library" OFF)
message(STATUS "Build Python: ${BUILD_PYTHON}")

option(BUILD_JAVA "Build Java Library" OFF)
option(INSTALL_JAVA "Install Java Library" OFF)
message(STATUS "Build Java: ${BUILD_JAVA}")

set(CMAKE_CXX_STANDARD 17)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
Expand Down Expand Up @@ -212,4 +216,12 @@ add_compile_definitions(CCAPI_ENABLE_EXCHANGE_WHITEBIT)
find_package(ZLIB REQUIRED)
link_libraries(ZLIB::ZLIB)

add_subdirectory(python)
set(SWIG_INTERFACE ${CCAPI_PROJECT_DIR}/binding/swig_interface.i)

if(BUILD_PYTHON)
add_subdirectory(python)
endif()

if(BUILD_JAVA)
add_subdirectory(java)
endif()
47 changes: 47 additions & 0 deletions binding/java/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
set(NAME binding_java)
project(${NAME})
set(SWIG_TARGET_NAME ccapi_${NAME})

# Find Java and JNI
find_package(Java COMPONENTS Development REQUIRED)
message(STATUS "Java_VERSION: ${Java_VERSION}")
message(STATUS "Java_JAVA_EXECUTABLE: ${Java_JAVA_EXECUTABLE}")
find_package(JNI REQUIRED)
message(STATUS "JNI_FOUND: ${JNI_FOUND}")

set(JAVA_DOMAIN_NAME "cryptochassis")
set(JAVA_DOMAIN_EXTENSION "com")

set(JAVA_GROUP "${JAVA_DOMAIN_EXTENSION}.${JAVA_DOMAIN_NAME}")
set(JAVA_ARTIFACT "ccapi")

set(JAVA_PACKAGE "${JAVA_GROUP}.${JAVA_ARTIFACT}")

set_property(SOURCE ${SWIG_INTERFACE} PROPERTY CPLUSPLUS ON)
set_property(SOURCE ${SWIG_INTERFACE} PROPERTY COMPILE_OPTIONS "-package;${JAVA_PACKAGE};-doxygen")

swig_add_library(${SWIG_TARGET_NAME}
LANGUAGE java
OUTPUT_DIR ${CMAKE_BINARY_DIR}/java/${SWIG_TARGET_NAME}
SOURCES ${SWIG_INTERFACE} ${SOURCE_LOGGER})

if(NOT CCAPI_LEGACY_USE_WEBSOCKETPP)
add_dependencies(${SWIG_TARGET_NAME} boost rapidjson hffix)
endif()
set_property(TARGET ${SWIG_TARGET_NAME} PROPERTY SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON)
target_include_directories(${SWIG_TARGET_NAME}
PRIVATE
${JNI_INCLUDE_DIRS}
)

set(PACKAGING_DIR packaging)
set(PACKAGING_DIR_FULL ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGING_DIR}/${BUILD_VERSION})
file(MAKE_DIRECTORY ${PACKAGING_DIR_FULL})
set(JAVA_PACKAGING_TARGET_NAME java_${PACKAGING_DIR})
add_custom_target(${JAVA_PACKAGING_TARGET_NAME} ALL
COMMAND ${Java_JAVAC_EXECUTABLE} -d ${PACKAGING_DIR_FULL} ${CMAKE_CURRENT_BINARY_DIR}/${SWIG_TARGET_NAME}/*.java
COMMAND ${Java_JAR_EXECUTABLE} cf ${JAVA_ARTIFACT}-${BUILD_VERSION}.jar ${JAVA_DOMAIN_EXTENSION}/${JAVA_DOMAIN_NAME}/${JAVA_ARTIFACT}/**
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${SWIG_TARGET_NAME}> ${PACKAGING_DIR_FULL}
WORKING_DIRECTORY ${PACKAGING_DIR_FULL}
)
add_dependencies(${JAVA_PACKAGING_TARGET_NAME} ${SWIG_TARGET_NAME})
46 changes: 46 additions & 0 deletions binding/java/example/execution_management_simple_request/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import com.cryptochassis.ccapi.EventHandler;
import com.cryptochassis.ccapi.SessionOptions;
import com.cryptochassis.ccapi.SessionConfigs;
import com.cryptochassis.ccapi.Session;
import com.cryptochassis.ccapi.Request;
import com.cryptochassis.ccapi.Event;
import com.cryptochassis.ccapi.map_string_string;

public class Main {
static class MyEventHandler extends EventHandler {
@Override
public boolean processEvent(Event event, Session session) {
System.out.println(String.format("Received an event:\n%s", event.toStringPretty(2, 2)));
return true;
}
}
public static void main(String[] args) {
if(System.getenv("BINANCE_API_KEY") == null) {
System.err.println("Please set environment variable BINANCE_API_KEY");
}
if(System.getenv("BINANCE_API_SECRET") == null) {
System.err.println("Please set environment variable BINANCE_API_SECRET");
}
System.loadLibrary("ccapi_binding_java");
var eventHandler = new MyEventHandler();
var option = new SessionOptions();
var config = new SessionConfigs();
var session = new Session(option, config, eventHandler);
var request = new Request(Request.Operation.CREATE_ORDER, "binance", "BTCUSDT");
var param = new map_string_string();
param.put("SIDE","BUY");
param.put("QUANTITY","0.0005");
param.put("LIMIT_PRICE","20000");
request.appendParam(param);
session.sendRequest(request);
try
{
Thread.sleep(10000);
}
catch(InterruptedException e)
{
}
session.stop();
System.out.println("Bye");
}
}
48 changes: 48 additions & 0 deletions binding/java/example/market_data_simple_subscription/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import com.cryptochassis.ccapi.EventHandler;
import com.cryptochassis.ccapi.SessionOptions;
import com.cryptochassis.ccapi.SessionConfigs;
import com.cryptochassis.ccapi.Session;
import com.cryptochassis.ccapi.Subscription;
import com.cryptochassis.ccapi.SubscriptionList;
import com.cryptochassis.ccapi.Event;

public class Main {
static class MyEventHandler extends EventHandler {
@Override
public boolean processEvent(Event event, Session session) {
if (event.getType() == Event.Type.SUBSCRIPTION_DATA) {
for (var message : event.getMessageList()) {
System.out.println(String.format("Best bid and ask at %s are:", message.getTimeISO()));
for (var element : message.getElementList()){
var elementNameValueMap = element.getNameValueMap();
for (var entry : elementNameValueMap.entrySet()){
var name = entry.getKey();
var value = entry.getValue();
System.out.println(String.format(" %s = %s", name, value));
}
}
}
}
return true;
}
}
public static void main(String[] args) {
System.loadLibrary("ccapi_binding_java");
var eventHandler = new MyEventHandler();
var option = new SessionOptions();
var config = new SessionConfigs();
var session = new Session(option, config, eventHandler);
var subscriptionList = new SubscriptionList();
subscriptionList.add(new Subscription("coinbase", "BTC-USD", "MARKET_DEPTH"));
session.subscribe(subscriptionList);
try
{
Thread.sleep(10000);
}
catch(InterruptedException e)
{
}
session.stop();
System.out.println("Bye");
}
}
76 changes: 27 additions & 49 deletions binding/python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
if(NOT BUILD_PYTHON)
return()
endif()
set(NAME binding_python)
project(${NAME})
set(SWIG_TARGET_NAME ccapi_${NAME})
Expand All @@ -15,7 +12,7 @@ message(STATUS "Python_EXECUTABLE: ${Python_EXECUTABLE}")
list(APPEND CMAKE_SWIG_FLAGS "-py3")
set(SWIG_INTERFACE ${CCAPI_PROJECT_DIR}/binding/swig_interface.i)
set_property(SOURCE ${SWIG_INTERFACE} PROPERTY CPLUSPLUS ON)
set_property(SOURCE ${SWIG_INTERFACE} PROPERTY COMPILE_OPTIONS "-builtin;-threads")
set_property(SOURCE ${SWIG_INTERFACE} PROPERTY COMPILE_OPTIONS "-builtin;-threads;-doxygen")
swig_add_library(${SWIG_TARGET_NAME}
LANGUAGE python
OUTPUT_DIR ${CMAKE_BINARY_DIR}/python/${SWIG_TARGET_NAME}
Expand All @@ -35,71 +32,52 @@ target_link_libraries(${SWIG_TARGET_NAME} PRIVATE ${Python_LIBRARIES})
# #######################
configure_file(
setup.py.in
${CMAKE_CURRENT_BINARY_DIR}/setup.py.in
${CMAKE_CURRENT_BINARY_DIR}/setup.py
@ONLY)
file(GENERATE
OUTPUT $<CONFIG>/setup.py
INPUT ${CMAKE_CURRENT_BINARY_DIR}/setup.py.in)

# Find if python module MODULE_NAME is available,
# if not install it to the Python user install directory.
function(search_python_module MODULE_NAME)
execute_process(
COMMAND ${Python_EXECUTABLE} -c "import ${MODULE_NAME}; print(${MODULE_NAME}.__version__)"
RESULT_VARIABLE _RESULT
OUTPUT_VARIABLE MODULE_VERSION
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(${_RESULT} STREQUAL "0")
message(STATUS "Found python module: ${MODULE_NAME} (found version \"${MODULE_VERSION}\")")
else()
message(WARNING "Can't find python module \"${MODULE_NAME}\", user install it using pip...")
execute_process(
COMMAND ${Python_EXECUTABLE} -m pip install --upgrade ${MODULE_NAME}
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
endfunction()
# function(search_python_module MODULE_NAME)
# execute_process(
# COMMAND ${Python_EXECUTABLE} -c "import ${MODULE_NAME}; print(${MODULE_NAME}.__version__)"
# RESULT_VARIABLE _RESULT
# OUTPUT_VARIABLE MODULE_VERSION
# ERROR_QUIET
# OUTPUT_STRIP_TRAILING_WHITESPACE
# )
# if(${_RESULT} STREQUAL "0")
# message(STATUS "Found python module: ${MODULE_NAME} (found version \"${MODULE_VERSION}\")")
# else()
# message(WARNING "Can't find python module \"${MODULE_NAME}\", user install it using pip...")
# execute_process(
# COMMAND ${Python_EXECUTABLE} -m pip install --upgrade ${MODULE_NAME}
# OUTPUT_STRIP_TRAILING_WHITESPACE
# )
# endif()
# endfunction()

# Look for required python modules
search_python_module(setuptools)
search_python_module(wheel)
# search_python_module(setuptools)
# search_python_module(wheel)
set(PACKAGING_DIR packaging)
set(PACKAGING_DIR_FULL ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGING_DIR})
set(PACKAGING_DIR_FULL ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGING_DIR}/${BUILD_VERSION})
file(MAKE_DIRECTORY ${PACKAGING_DIR_FULL})
set(PYTHON_PACKAGING_TARGET_NAME python_${PACKAGING_DIR})
add_custom_target(${PYTHON_PACKAGING_TARGET_NAME} ALL
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/setup.py ${PACKAGING_DIR_FULL}
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/setup.py ${PACKAGING_DIR_FULL}
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/${SWIG_TARGET_NAME}/ccapi.py ${PACKAGING_DIR_FULL}
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${SWIG_TARGET_NAME}> ${PACKAGING_DIR_FULL}
COMMAND ${Python_EXECUTABLE} setup.py bdist_wheel
COMMAND ${Python_EXECUTABLE} -m pip install --upgrade .
WORKING_DIRECTORY ${PACKAGING_DIR_FULL}
)
)
add_dependencies(${PYTHON_PACKAGING_TARGET_NAME} ${SWIG_TARGET_NAME})

install(CODE "
execute_process(COMMAND ${Python_EXECUTABLE} -m pip install --upgrade .
WORKING_DIRECTORY ${PACKAGING_DIR_FULL})
")

# Test
if(BUILD_TEST)
set(TEST_DIR ${CMAKE_CURRENT_BINARY_DIR}/test)
set(VENV_DIR ${TEST_DIR}/.venv)
if(WIN32)
set(VENV_Python_EXECUTABLE "${VENV_DIR}\\Scripts\\python.exe")
else()
set(VENV_Python_EXECUTABLE ${VENV_DIR}/bin/python)
endif()
configure_file(test/test.py ${TEST_DIR}/test.py COPYONLY)
add_custom_command(TARGET ${PYTHON_PACKAGING_TARGET_NAME} POST_BUILD
COMMAND ${Python_EXECUTABLE} -m venv ${VENV_DIR}
# Must not call it in a folder containing the setup.py otherwise pip call it
# (i.e. "python setup.py bdist") while we want to consume the wheel package
COMMAND ${VENV_Python_EXECUTABLE} -m pip install --find-links=${PACKAGING_DIR_FULL}/dist ccapi
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
add_test(NAME test
COMMAND ${VENV_Python_EXECUTABLE} test.py
COMMAND ${Python_EXECUTABLE} test.py
WORKING_DIRECTORY ${TEST_DIR})
endif()
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ def __init__(self):
def processEvent(self, event: Event, session: Session) -> bool:
if event.getType() == Event.Type_SUBSCRIPTION_DATA:
for message in event.getMessageList():
print(f'Best bid and ask at {message.getTimeISO()} are:')
correlationId = message.getCorrelationIdList()[0]
print(f'{correlationId}: Best bid and ask at {message.getTimeISO()} are:')
for element in message.getElementList():
elementNameValueMap = element.getNameValueMap()
for name, value in elementNameValueMap.items():
Expand All @@ -18,8 +19,8 @@ def processEvent(self, event: Event, session: Session) -> bool:
config = SessionConfigs()
session = Session(option, config, eventHandler)
subscriptionList = SubscriptionList()
subscriptionList.append(Subscription('coinbase', 'BTC-USD', 'MARKET_DEPTH'))
subscriptionList.append(Subscription('coinbase', 'ETH-USD', 'MARKET_DEPTH'))
subscriptionList.append(Subscription('coinbase', 'BTC-USD', 'MARKET_DEPTH', '', 'BTC'))
subscriptionList.append(Subscription('coinbase', 'ETH-USD', 'MARKET_DEPTH', '', 'ETH'))
session.subscribe(subscriptionList)
time.sleep(10)
session.stop()
Expand Down
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
1 change: 1 addition & 0 deletions binding/python/setup.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ setup(
packages=[''],
py_modules = ['ccapi'],
package_data={'': ['$<TARGET_FILE_NAME:@SWIG_TARGET_NAME@>']},
install_requires=['setuptools', 'wheel'],
)
6 changes: 3 additions & 3 deletions include/ccapi_cpp/ccapi_fix_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
#include "ccapi_cpp/ccapi_subscription.h"
namespace beast = boost::beast;
namespace ccapi {
/**
* This class represents a TCP socket connection for the FIX API.
*/
template <class T>
class FixConnection CCAPI_FINAL {
/**
* This class represents a TCP socket connection for the FIX API.
*/
public:
FixConnection(std::string host, std::string port, Subscription subscription, std::shared_ptr<T> streamPtr)
: host(host), port(port), subscription(subscription), streamPtr(streamPtr) {
Expand Down

0 comments on commit d32f42b

Please sign in to comment.