diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 23fe6406..8a240fd8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -157,6 +157,13 @@ jobs: ) endif() + - name: Install node-gyp + shell: cmake -P {0} + run: | + execute_process( + COMMAND npm install -g node-gyp + ) + - name: Configure shell: cmake -P {0} run: | @@ -190,9 +197,11 @@ jobs: if ("$ENV{GITHUB_EVENT_NAME}" STREQUAL "push") set(ENV{CMAKE_BUILD_TYPE} "Release") set(ENV{DOTNET_BUILD_CONFIGURATION} "Release") + set(ENV{NODE_GYP_BUILD_MODE} "release") else() set(ENV{CMAKE_BUILD_TYPE} "Debug") set(ENV{DOTNET_BUILD_CONFIGURATION} "Debug") + set(ENV{NODE_GYP_BUILD_MODE} "debug") endif() execute_process( @@ -233,11 +242,14 @@ jobs: -DBUILD_PYTHON=ON -DBUILD_JAVA=ON -DBUILD_CSHARP=ON + # -DBUILD_GO=ON + # -DBUILD_JAVASCRIPT=ON -DBUILD_TEST=ON -S binding -B binding/build -D CMAKE_BUILD_TYPE=$ENV{CMAKE_BUILD_TYPE} -D DOTNET_BUILD_CONFIGURATION=$ENV{DOTNET_BUILD_CONFIGURATION} + -D NODE_GYP_BUILD_MODE=$ENV{NODE_GYP_BUILD_MODE} -G Ninja -D CMAKE_MAKE_PROGRAM=ninja # -D CMAKE_C_COMPILER_LAUNCHER=ccache diff --git a/.gitignore b/.gitignore index 3c4ed4ee..113c5037 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,5 @@ docs/_build/ .venv/ target/ obj/ +node_modules/ +package-lock.json diff --git a/README.md b/README.md index 89db58b4..c4b45755 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ # ccapi * A header-only C++ library for streaming market data and executing trades directly from cryptocurrency exchanges (i.e. the connections are between your server and the exchange server without anything in-between). -* Bindings for other languages such as Python, Java, C#, and Go are provided. +* Bindings for other languages such as Python, Java, C#, Go, and Javascript are provided. * Code closely follows Bloomberg's API: https://www.bloomberg.com/professional/support/api-library/. * It is ultra fast thanks to very careful optimizations: move semantics, regex optimization, locality of reference, lock contention minimization, etc. * Supported exchanges: @@ -109,7 +109,7 @@ mkdir binding/build cd binding/build rm -rf * (if rebuild from scratch) -cmake -DBUILD_PYTHON=ON -DBUILD_VERSION=1.0.0 .. (Use -DBUILD_JAVA=ON if the target language is Java, -DBUILD_CSHARP=ON if the target language is C#, -DBUILD_GO=ON if the target language is Go) +cmake -DBUILD_PYTHON=ON -DBUILD_VERSION=1.0.0 .. (Use -DBUILD_JAVA=ON if the target language is Java, -DBUILD_CSHARP=ON if the target language is C#, -DBUILD_GO=ON if the target language is Go, -DBUILD_JAVASCRIPT=ON if the target language is Javascript) cmake --build . ``` * The packaged build artifacts are located in the `binding/build//packaging/` directory. SWIG generated raw files and build artifacts are located in the `binding/build//ccapi_binding_` directory. @@ -122,6 +122,8 @@ cmake --build . * "‘_PyObject_GC_UNTRACK’ was not declared in this scope". If you use Python >= 3.8, please use SWIG >= 4.0. * Java: * "Could NOT find JNI (missing: JAVA_INCLUDE_PATH JAVA_INCLUDE_PATH2 JAVA_AWT_INCLUDE_PATH)". Check that the environment variable `JAVA_HOME` is correct. + * Javascript: + * "Check for node-gyp Program: not found". You can install node-gyp using npm: `npm install -g node-gyp` ## Constants [`include/ccapi_cpp/ccapi_macro.h`](include/ccapi_cpp/ccapi_macro.h) @@ -190,6 +192,16 @@ go build . * Troubleshoot: * Some C/C++ header files not found. Check that you sourced the export_compiler_options.sh file which provides important environment variables needed by the cgo tool. +[Javascript](binding/javascript/example) +* Javascript API is nearly identical to C++ API and covers nearly all the functionalities from C++ API. +* Build and install the Javascript binding as shown [above](#non-c). +* Inside a concrete example directory (e.g. binding/javascript/example/market_data_simple_subscription), run +``` +rm -rf node_modules (if rebuild from scratch) +npm install +node index.js +``` + ## Documentations ### Simple Market Data @@ -200,7 +212,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/market_data_simple_request/main.py) / [Java](binding/java/example/market_data_simple_request/Main.java) / [C#](binding/csharp/example/market_data_simple_request/MainProgram.cs) / [Go](binding/go/example/market_data_simple_request/main.go) +[C++](example/src/market_data_simple_request/main.cpp) / [Python](binding/python/example/market_data_simple_request/main.py) / [Java](binding/java/example/market_data_simple_request/Main.java) / [C#](binding/csharp/example/market_data_simple_request/MainProgram.cs) / [Go](binding/go/example/market_data_simple_request/main.go) / [Javascript](binding/javascript/example/market_data_simple_request/index.js) ``` #include "ccapi_cpp/ccapi_session.h" namespace ccapi { @@ -273,7 +285,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/market_data_simple_subscription/main.py) / [Java](binding/java/example/market_data_simple_subscription/Main.java) / [C#](binding/csharp/example/market_data_simple_subscription/MainProgram.cs) / [Go](binding/go/example/market_data_simple_subscription/main.go) +[C++](example/src/market_data_simple_subscription/main.cpp) / [Python](binding/python/example/market_data_simple_subscription/main.py) / [Java](binding/java/example/market_data_simple_subscription/Main.java) / [C#](binding/csharp/example/market_data_simple_subscription/MainProgram.cs) / [Go](binding/go/example/market_data_simple_subscription/main.go) / [Javascript](binding/javascript/example/market_data_simple_subscription/index.js) ``` #include "ccapi_cpp/ccapi_session.h" namespace ccapi { @@ -447,7 +459,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/execution_management_simple_request/main.py) / [Java](binding/java/example/execution_management_simple_request/Main.java) / [C#](binding/csharp/example/execution_management_simple_request/MainProgram.cs) / [Go](binding/go/example/execution_management_simple_request/main.go) +[C++](example/src/execution_management_simple_request/main.cpp) / [Python](binding/python/example/execution_management_simple_request/main.py) / [Java](binding/java/example/execution_management_simple_request/Main.java) / [C#](binding/csharp/example/execution_management_simple_request/MainProgram.cs) / [Go](binding/go/example/execution_management_simple_request/main.go) / [Javascript](binding/javascript/example/execution_management_simple_request/index.js) ``` #include "ccapi_cpp/ccapi_session.h" namespace ccapi { @@ -537,7 +549,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/execution_management_simple_subscription/main.py) / [Java](binding/java/example/execution_management_simple_subscription/Main.java) / [C#](binding/csharp/example/execution_management_simple_subscription/MainProgram.cs) / [Go](binding/go/example/execution_management_simple_subscription/main.go) +[C++](example/src/execution_management_simple_subscription/main.cpp) / [Python](binding/python/example/execution_management_simple_subscription/main.py) / [Java](binding/java/example/execution_management_simple_subscription/Main.java) / [C#](binding/csharp/example/execution_management_simple_subscription/MainProgram.cs) / [Go](binding/go/example/execution_management_simple_subscription/main.go) / [Javascript](binding/javascript/example/execution_management_simple_subscription/index.js) ``` #include "ccapi_cpp/ccapi_session.h" namespace ccapi { @@ -780,7 +792,7 @@ For a specific exchange and instrument, submit a simple limit order. **Code:** -[C++](example/src/fix_simple/main.cpp) / [Python](binding/python/example/fix_simple/main.py) / [Java](binding/java/example/fix_simple/Main.java) / [C#](binding/csharp/example/fix_simple/MainProgram.cs) / [Go](binding/go/example/fix_simple/main.go) +[C++](example/src/fix_simple/main.cpp) / [Python](binding/python/example/fix_simple/main.py) / [Java](binding/java/example/fix_simple/Main.java) / [C#](binding/csharp/example/fix_simple/MainProgram.cs) / [Go](binding/go/example/fix_simple/main.go) / [Javascript](binding/javascript/example/fix_simple/index.js) ``` #include "ccapi_cpp/ccapi_session.h" namespace ccapi { diff --git a/binding/csharp/example/fix_simple/MainProgram.cs b/binding/csharp/example/fix_simple/MainProgram.cs index b3488785..0f4f5a3e 100644 --- a/binding/csharp/example/fix_simple/MainProgram.cs +++ b/binding/csharp/example/fix_simple/MainProgram.cs @@ -16,7 +16,7 @@ class MyEventHandler : ccapi.EventHandler { param.Add(new ccapi.PairIntString(40, "2")); param.Add(new ccapi.PairIntString(59, "1")); request.AppendParamFix(param); - session.SendRequest(request); + session.SendRequestByFix(request); } } else if (event_.GetType_() == ccapi.Event.Type.FIX) { System.Console.WriteLine(string.Format("Received an event of type FIX:\n{0}", event_.ToStringPretty(2, 2))); diff --git a/binding/go/CMakeLists.txt b/binding/go/CMakeLists.txt index 5454dfac..0d9fed37 100644 --- a/binding/go/CMakeLists.txt +++ b/binding/go/CMakeLists.txt @@ -22,7 +22,7 @@ set(GO_PACKAGE "ccapi") file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${SWIG_TARGET_NAME}) add_custom_target(${SWIG_TARGET_NAME} ALL - COMMAND ${SWIG_EXECUTABLE} -outcurrentdir -c++ -go -package ${GO_PACKAGE} -I${CCAPI_PROJECT_DIR}/include ${SWIG_INTERFACE} + COMMAND ${SWIG_EXECUTABLE} -outcurrentdir -c++ -go -intgosize 64 -package ${GO_PACKAGE} -I${CCAPI_PROJECT_DIR}/include ${SWIG_INTERFACE} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${SWIG_TARGET_NAME} ) add_dependencies(${SWIG_TARGET_NAME} boost rapidjson hffix) diff --git a/binding/go/example/fix_simple/main.go b/binding/go/example/fix_simple/main.go index 14650061..13841b3e 100644 --- a/binding/go/example/fix_simple/main.go +++ b/binding/go/example/fix_simple/main.go @@ -41,7 +41,7 @@ func (*MyEventHandler) ProcessEvent(event ccapi.Event, session ccapi.Session) bo param.Add(aParam) } request.AppendParamFix(param) - session.SendRequest(request) + session.SendRequestByFix(request) } } else if event.GetType() == ccapi.EventType_FIX { fmt.Printf("Received an event of type FIX:\n%s\n", event.ToStringPretty(2, 2)) diff --git a/binding/go/example/market_data_simple_subscription/main.go b/binding/go/example/market_data_simple_subscription/main.go index 94fff678..e091f005 100644 --- a/binding/go/example/market_data_simple_subscription/main.go +++ b/binding/go/example/market_data_simple_subscription/main.go @@ -21,18 +21,18 @@ func (*MyEventHandler) ProcessEvent(event ccapi.Event, session ccapi.Session) bo elementList := message.GetElementList() for j := 0; j < int(elementList.Size()); j++ { element := elementList.Get(j) - nameValueMap := element.GetNameValueMap() - if nameValueMap.Has_key("BID_PRICE") { - fmt.Printf(" %s = %s\n", "BID_PRICE", nameValueMap.Get("BID_PRICE")) + elementNameValueMap := element.GetNameValueMap() + if elementNameValueMap.Has_key("BID_PRICE") { + fmt.Printf(" BID_PRICE = %s\n", elementNameValueMap.Get("BID_PRICE")) } - if nameValueMap.Has_key("BID_SIZE") { - fmt.Printf(" %s = %s\n", "BID_SIZE", nameValueMap.Get("BID_SIZE")) + if elementNameValueMap.Has_key("BID_SIZE") { + fmt.Printf(" BID_SIZE = %s\n", elementNameValueMap.Get("BID_SIZE")) } - if nameValueMap.Has_key("ASK_PRICE") { - fmt.Printf(" %s = %s\n", "ASK_PRICE", nameValueMap.Get("ASK_PRICE")) + if elementNameValueMap.Has_key("ASK_PRICE") { + fmt.Printf(" ASK_PRICE = %s\n", elementNameValueMap.Get("ASK_PRICE")) } - if nameValueMap.Has_key("ASK_SIZE") { - fmt.Printf(" %s = %s\n", "ASK_SIZE", nameValueMap.Get("ASK_SIZE")) + if elementNameValueMap.Has_key("ASK_SIZE") { + fmt.Printf(" ASK_SIZE = %s\n", elementNameValueMap.Get("ASK_SIZE")) } } } diff --git a/binding/go/test/go.mod b/binding/go/test/go.mod deleted file mode 100644 index 0e5ae7c4..00000000 --- a/binding/go/test/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module main - -require ( - cryptochassis.com/ccapi v1.0.0 -) - -replace cryptochassis.com/ccapi => ../../../build/go/packaging/1.0.0 diff --git a/binding/java/example/fix_simple/Main.java b/binding/java/example/fix_simple/Main.java index 68c81077..a8bdbed9 100644 --- a/binding/java/example/fix_simple/Main.java +++ b/binding/java/example/fix_simple/Main.java @@ -29,7 +29,7 @@ public boolean processEvent(Event event, Session session) { param.add(new PairIntString(40, "2")); param.add(new PairIntString(59, "1")); request.appendParamFix(param); - session.sendRequest(request); + session.sendRequestByFix(request); } } else if (event.getType() == Event.Type.FIX) { System.out.println(String.format("Received an event of type FIX:\n%s", event.toStringPretty(2, 2))); diff --git a/binding/javascript/CMakeLists.txt b/binding/javascript/CMakeLists.txt new file mode 100644 index 00000000..4348db9b --- /dev/null +++ b/binding/javascript/CMakeLists.txt @@ -0,0 +1,130 @@ +set(NAME binding_${LANG}) +project(${NAME}) +set(SWIG_TARGET_NAME ccapi_${NAME}) + +# Find javascript cli +find_program(NODE_EXECUTABLE NAMES node REQUIRED) +if(NOT NODE_EXECUTABLE) + message(FATAL_ERROR "Check for node Program: not found") +else() + message(STATUS "Found node Program: ${NODE_EXECUTABLE}") +endif() + +execute_process( + COMMAND ${NODE_EXECUTABLE} -v + OUTPUT_VARIABLE NODE_EXECUTABLE_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE +) +message(STATUS "node version: ${NODE_EXECUTABLE_VERSION}") + +find_program(NODE_GYP_EXECUTABLE NAMES node-gyp REQUIRED) +if(NOT NODE_GYP_EXECUTABLE) + message(FATAL_ERROR "Check for node-gyp Program: not found") +else() + message(STATUS "Found node-gyp Program: ${NODE_EXECUTABLE}") +endif() + +execute_process( + COMMAND ${NODE_GYP_EXECUTABLE} -v + OUTPUT_VARIABLE NODE_GYP_EXECUTABLE_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE +) +message(STATUS "node-gyp version: ${NODE_GYP_EXECUTABLE_VERSION}") + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${SWIG_TARGET_NAME}) +set(BINDING_GYP_CFLAGS_CC_LIST "") +set(BINDING_GYP_LDFLAGS_LIST "") +set(BINDING_GYP_DEFINES_LIST "") +list(APPEND BINDING_GYP_CFLAGS_CC_LIST -std=c++17 -fPIC -Wno-deprecated-declarations) +if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") +list(APPEND BINDING_GYP_CFLAGS_CC_LIST -g) +list(APPEND BINDING_GYP_LDFLAGS_LIST -g) +else() +list(APPEND BINDING_GYP_CFLAGS_CC_LIST -O3 -DNDEBUG) +endif() +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") +list(APPEND BINDING_GYP_CFLAGS_CC_LIST -pthread) +endif() +list(APPEND BINDING_GYP_DEFINES_LIST CCAPI_ENABLE_SERVICE_MARKET_DATA CCAPI_ENABLE_SERVICE_EXECUTION_MANAGEMENT CCAPI_ENABLE_SERVICE_FIX CCAPI_ENABLE_EXCHANGE_COINBASE CCAPI_ENABLE_EXCHANGE_GEMINI CCAPI_ENABLE_EXCHANGE_KRAKEN CCAPI_ENABLE_EXCHANGE_KRAKEN_FUTURES CCAPI_ENABLE_EXCHANGE_BITSTAMP CCAPI_ENABLE_EXCHANGE_BITFINEX CCAPI_ENABLE_EXCHANGE_BITMEX CCAPI_ENABLE_EXCHANGE_BINANCE_US CCAPI_ENABLE_EXCHANGE_BINANCE CCAPI_ENABLE_EXCHANGE_BINANCE_MARGIN CCAPI_ENABLE_EXCHANGE_BINANCE_USDS_FUTURES CCAPI_ENABLE_EXCHANGE_BINANCE_COIN_FUTURES CCAPI_ENABLE_EXCHANGE_HUOBI CCAPI_ENABLE_EXCHANGE_HUOBI_USDT_SWAP CCAPI_ENABLE_EXCHANGE_HUOBI_COIN_SWAP CCAPI_ENABLE_EXCHANGE_OKX CCAPI_ENABLE_EXCHANGE_ERISX CCAPI_ENABLE_EXCHANGE_KUCOIN CCAPI_ENABLE_EXCHANGE_KUCOIN_FUTURES CCAPI_ENABLE_EXCHANGE_DERIBIT CCAPI_ENABLE_EXCHANGE_GATEIO CCAPI_ENABLE_EXCHANGE_GATEIO_PERPETUAL_FUTURES CCAPI_ENABLE_EXCHANGE_CRYPTOCOM CCAPI_ENABLE_EXCHANGE_BYBIT CCAPI_ENABLE_EXCHANGE_BYBIT_DERIVATIVES CCAPI_ENABLE_EXCHANGE_ASCENDEX CCAPI_ENABLE_EXCHANGE_BITGET CCAPI_ENABLE_EXCHANGE_BITGET_FUTURES CCAPI_ENABLE_EXCHANGE_BITMART CCAPI_ENABLE_EXCHANGE_MEXC CCAPI_ENABLE_EXCHANGE_MEXC_FUTURES CCAPI_ENABLE_EXCHANGE_WHITEBIT) +if(CCAPI_ENABLE_LOG_ERROR) +list(APPEND BINDING_GYP_DEFINES_LIST CCAPI_ENABLE_LOG_ERROR) +endif() +if(CCAPI_ENABLE_LOG_WARN) +list(APPEND BINDING_GYP_DEFINES_LIST CCAPI_ENABLE_LOG_WARN) +endif() +if(CCAPI_ENABLE_LOG_INFO) +list(APPEND BINDING_GYP_DEFINES_LIST CCAPI_ENABLE_LOG_INFO) +endif() +if(CCAPI_ENABLE_LOG_DEBUG) +list(APPEND BINDING_GYP_DEFINES_LIST CCAPI_ENABLE_LOG_DEBUG) +endif() +if(CCAPI_ENABLE_LOG_TRACE) +list(APPEND BINDING_GYP_DEFINES_LIST CCAPI_ENABLE_LOG_TRACE) +endif() +list(JOIN BINDING_GYP_CFLAGS_CC_LIST "\",\"" BINDING_GYP_CFLAGS_CC) +message(STATUS "BINDING_GYP_CFLAGS_CC: ${BINDING_GYP_CFLAGS_CC}") +list(JOIN BINDING_GYP_LDFLAGS_LIST "\",\"" BINDING_GYP_LDFLAGS) +message(STATUS "BINDING_GYP_LDFLAGS: ${BINDING_GYP_LDFLAGS}") +list(JOIN BINDING_GYP_DEFINES_LIST "\",\"" BINDING_GYP_DEFINES) +message(STATUS "BINDING_GYP_DEFINES: ${BINDING_GYP_DEFINES}") + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/binding.gyp.in + ${CMAKE_CURRENT_BINARY_DIR}/${SWIG_TARGET_NAME}/binding.gyp +@ONLY) +if(NOT NODE_GYP_BUILD_MODE) + set(NODE_GYP_BUILD_MODE "release" CACHE STRING + "Choose the type of build, options are: debug, release." + FORCE) +endif() +if("${NODE_GYP_BUILD_MODE}" STREQUAL "release") + set(NODE_GYP_BUILD_DIR "build/Release") +else() +set(NODE_GYP_BUILD_DIR "build/Debug") +endif() +message(STATUS "NODE_GYP_BUILD_MODE: ${NODE_GYP_BUILD_MODE}") +add_custom_target(${SWIG_TARGET_NAME} ALL + COMMAND ${SWIG_EXECUTABLE} -outcurrentdir -c++ -javascript -node -I${CCAPI_PROJECT_DIR}/include ${SWIG_INTERFACE} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/ccapi_logger.cpp . + COMMAND ${NODE_GYP_EXECUTABLE} --${NODE_GYP_BUILD_MODE} clean configure build + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${SWIG_TARGET_NAME} +) +add_dependencies(${SWIG_TARGET_NAME} boost rapidjson hffix) + +set(PACKAGING_DIR packaging) +set(PACKAGING_DIR_FULL ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGING_DIR}/${BUILD_VERSION}) +file(MAKE_DIRECTORY ${PACKAGING_DIR_FULL}) +set(JAVASCRIPT_PACKAGING_TARGET_NAME ${LANG}_${PACKAGING_DIR}) +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/package.json.in + ${PACKAGING_DIR_FULL}/package.json +@ONLY) +add_custom_target(${JAVASCRIPT_PACKAGING_TARGET_NAME} ALL + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/${SWIG_TARGET_NAME}/${NODE_GYP_BUILD_DIR}/index.node ${PACKAGING_DIR_FULL} + WORKING_DIRECTORY ${PACKAGING_DIR_FULL} +) +add_dependencies(${JAVASCRIPT_PACKAGING_TARGET_NAME} ${SWIG_TARGET_NAME}) + +# Test +if(BUILD_TEST) + set(TEST_DIR ${CMAKE_CURRENT_BINARY_DIR}/test) + find_program(NPM_EXECUTABLE NAMES npm REQUIRED) + if(NOT NPM_EXECUTABLE) + message(FATAL_ERROR "Check for npm Program: not found") + else() + message(STATUS "Found npm Program: ${NPM_EXECUTABLE}") + endif() + file(COPY test DESTINATION ${TEST_DIR}) + set(JAVASCRIPT_TEST_TARGET_NAME javascript_test_test) + set(JAVASCRIPT_TEST_BUILD_DIRECTORY ${TEST_DIR}/test) + file(MAKE_DIRECTORY ${JAVASCRIPT_TEST_BUILD_DIRECTORY}) + add_custom_target(${JAVASCRIPT_TEST_TARGET_NAME} ALL + COMMAND ${CMAKE_COMMAND} -E rm -rf node_modules + COMMAND ${NPM_EXECUTABLE} install ${PACKAGING_DIR_FULL} + WORKING_DIRECTORY ${JAVASCRIPT_TEST_BUILD_DIRECTORY} + ) + add_dependencies(${JAVASCRIPT_TEST_TARGET_NAME} ${JAVASCRIPT_PACKAGING_TARGET_NAME}) + add_test(NAME javascript_test + COMMAND ${NODE_EXECUTABLE} index.js + WORKING_DIRECTORY ${JAVASCRIPT_TEST_BUILD_DIRECTORY}) +endif() diff --git a/binding/javascript/binding.gyp.in b/binding/javascript/binding.gyp.in new file mode 100644 index 00000000..29589356 --- /dev/null +++ b/binding/javascript/binding.gyp.in @@ -0,0 +1,65 @@ +{ + "targets": [ + { + "target_name": "index", + "sources": [ + "swig_interface_wrap.cxx", + "ccapi_logger.cpp" + ], + "include_dirs": [ + "@CCAPI_PROJECT_DIR@/include", + "@BOOST_INCLUDE_DIR@", + "@RAPIDJSON_INCLUDE_DIR@", + "@HFFIX_INCLUDE_DIR@", + "<(node_root_dir)/deps/openssl/openssl/include" + ], + "cflags_cc!": [ + "-fno-exceptions" + ], + "conditions": [ + [ + "OS=='mac'", + { + "xcode_settings": { + "OTHER_CPLUSPLUSFLAGS" : [ "@BINDING_GYP_CFLAGS_CC@" ], + "GCC_ENABLE_CPP_EXCEPTIONS": "YES" + } + } + ], + [ + "target_arch=='ia32'", + { + "include_dirs": [ + "<(node_root_dir)/deps/openssl/config/piii" + ] + } + ], + [ + "target_arch=='x64'", + { + "include_dirs": [ + "<(node_root_dir)/deps/openssl/config/k8" + ] + } + ], + [ + "target_arch=='arm'", + { + "include_dirs": [ + "<(node_root_dir)/deps/openssl/config/arm" + ] + } + ] + ], + "cflags_cc": [ + "@BINDING_GYP_CFLAGS_CC@" + ], + "ldflags": [ + "@BINDING_GYP_LDFLAGS@" + ], + "defines": [ + "@BINDING_GYP_DEFINES@" + ] + } + ] +} diff --git a/binding/javascript/example/execution_management_simple_request/index.js b/binding/javascript/example/execution_management_simple_request/index.js new file mode 100644 index 00000000..fae65f87 --- /dev/null +++ b/binding/javascript/example/execution_management_simple_request/index.js @@ -0,0 +1,40 @@ +const ccapi = require('ccapi') +if (!process.env.OKX_API_KEY) { + console.error('Please set environment variable OKX_API_KEY') + process.exit(1) +} +if (!process.env.OKX_API_SECRET) { + console.error('Please set environment variable OKX_API_SECRET') + process.exit(1) +} +if (!process.env.OKX_API_PASSPHRASE) { + console.error('Please set environment variable OKX_API_PASSPHRASE') + process.exit(1) +} +const Session = ccapi.Session +const SessionConfigs = ccapi.SessionConfigs +const SessionOptions = ccapi.SessionOptions +const Request = ccapi.Request +const MapStringString = ccapi.MapStringString +const sessionConfigs = new SessionConfigs() +const sessionOptions = new SessionOptions() +const session = new Session(sessionOptions, sessionConfigs) +const request = new Request(Request.Operation_CREATE_ORDER, 'okx', 'BTC-USDT') +const param = new MapStringString() +param.set('SIDE', 'BUY') +param.set('QUANTITY', '0.0005') +param.set('LIMIT_PRICE', '20000') +request.appendParam(param) +session.sendRequest(request) +const intervalId = setInterval(() => { + const eventList = session.getEventQueue().purge() + for (let i = 0; i < eventList.size(); i++) { + const event = eventList.get(i) + console.log(`Received an event:\n${event.toStringPretty(2, 2)}`) + } +}, 1) +setTimeout(() => { + clearInterval(intervalId) +session.stop() + console.log('Bye') +}, 10000) diff --git a/binding/javascript/example/execution_management_simple_request/package.json b/binding/javascript/example/execution_management_simple_request/package.json new file mode 100644 index 00000000..f6e097dc --- /dev/null +++ b/binding/javascript/example/execution_management_simple_request/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ccapi": "file:../../../build/javascript/packaging/1.0.0" + } +} diff --git a/binding/javascript/example/execution_management_simple_subscription/index.js b/binding/javascript/example/execution_management_simple_subscription/index.js new file mode 100644 index 00000000..ede2e9fc --- /dev/null +++ b/binding/javascript/example/execution_management_simple_subscription/index.js @@ -0,0 +1,53 @@ +const ccapi = require('ccapi') +if (!process.env.OKX_API_KEY) { + console.error('Please set environment variable OKX_API_KEY') + process.exit(1) +} +if (!process.env.OKX_API_SECRET) { + console.error('Please set environment variable OKX_API_SECRET') + process.exit(1) +} +if (!process.env.OKX_API_PASSPHRASE) { + console.error('Please set environment variable OKX_API_PASSPHRASE') + process.exit(1) +} +const Session = ccapi.Session +const SessionConfigs = ccapi.SessionConfigs +const SessionOptions = ccapi.SessionOptions +const Subscription = ccapi.Subscription +const Request = ccapi.Request +const MapStringString = ccapi.MapStringString +const Event = ccapi.Event +const Message = ccapi.Message +const sessionConfigs = new SessionConfigs() +const sessionOptions = new SessionOptions() +const session = new Session(sessionOptions, sessionConfigs) +const subscription = new Subscription('okx', 'BTC-USDT', 'ORDER_UPDATE') +session.subscribe(subscription) +const intervalId = setInterval(() => { + const eventList = session.getEventQueue().purge() + for (let i = 0; i < eventList.size(); i++) { + const event = eventList.get(i) + if (event.getType() == Event.Type_SUBSCRIPTION_STATUS) { + console.log(`Received an event of type SUBSCRIPTION_STATUS:\n${event.toStringPretty(2, 2)}`) + const message = event.getMessageList().get(0) + if (message.getType() == Message.Type_SUBSCRIPTION_STARTED) { + const request = new Request(Request.Operation_CREATE_ORDER, 'okx', 'BTC-USDT') + const param = new MapStringString() + param.set('SIDE', 'BUY') + param.set('QUANTITY', '0.0005') + param.set('LIMIT_PRICE', '20000') + request.appendParam(param) + session.sendRequest(request) + } + } + else if (event.getType() == ccapi.Event.Type_SUBSCRIPTION_DATA) { + console.log(`Received an event of type SUBSCRIPTION_DATA:\n${event.toStringPretty(2, 2)}`) + } + } +}, 1) +setTimeout(() => { + clearInterval(intervalId) +session.stop() + console.log('Bye') +}, 10000) diff --git a/binding/javascript/example/execution_management_simple_subscription/package.json b/binding/javascript/example/execution_management_simple_subscription/package.json new file mode 100644 index 00000000..f6e097dc --- /dev/null +++ b/binding/javascript/example/execution_management_simple_subscription/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ccapi": "file:../../../build/javascript/packaging/1.0.0" + } +} diff --git a/binding/javascript/example/fix_simple/index.js b/binding/javascript/example/fix_simple/index.js new file mode 100644 index 00000000..0219e237 --- /dev/null +++ b/binding/javascript/example/fix_simple/index.js @@ -0,0 +1,59 @@ +const ccapi = require('ccapi') +if (!process.env.COINBASE_API_KEY) { + console.error('Please set environment variable COINBASE_API_KEY') + process.exit(1) +} +if (!process.env.COINBASE_API_SECRET) { + console.error('Please set environment variable COINBASE_API_SECRET') + process.exit(1) +} +if (!process.env.COINBASE_API_PASSPHRASE) { + console.error('Please set environment variable COINBASE_API_PASSPHRASE') + process.exit(1) +} +const Session = ccapi.Session +const SessionConfigs = ccapi.SessionConfigs +const SessionOptions = ccapi.SessionOptions +const Subscription = ccapi.Subscription +const Request = ccapi.Request +const VectorPairIntString = ccapi.VectorPairIntString +const PairIntString = ccapi.PairIntString +const Event = ccapi.Event +const Message = ccapi.Message +const sessionConfigs = new SessionConfigs() +const sessionOptions = new SessionOptions() +const session = new Session(sessionOptions, sessionConfigs) +const subscription = new Subscription('coinbase', '', 'FIX', '', 'same correlation id for subscription and request') +session.subscribeByFix(subscription) +const intervalId = setInterval(() => { + const eventList = session.getEventQueue().purge() + for (let i = 0; i < eventList.size(); i++) { + const event = eventList.get(i) + console.log(`Received an event of type any:\n${event.toStringPretty(2, 2)}`) + if (event.getType() == Event.Type_AUTHORIZATION_STATUS) { + const message = event.getMessageList().get(0) + if (message.getType() == Message.Type_AUTHORIZATION_SUCCESS) { + const request = new Request(ccapi.Request.Operation_FIX, 'coinbase', '', 'same correlation id for subscription and request') + const param = new VectorPairIntString() + param.add(new PairIntString(35, 'D')) + param.add(new PairIntString(11, '6d4eb0fb-2229-469f-873e-557dd78ac11e')) + param.add(new PairIntString(55, 'BTC-USD')) + param.add(new PairIntString(54, '1')) + param.add(new PairIntString(44, '20000')) + param.add(new PairIntString(38, '0.001')) + param.add(new PairIntString(40, '2')) + param.add(new PairIntString(59, '1')) + request.appendParamFix(param) + session.sendRequestByFix(request) + } + } + else if (event.getType() == ccapi.Event.Type_FIX) { + console.log(`Received an event of type FIX:\n${event.toStringPretty(2, 2)}`) + } + } +}, 1) +setTimeout(() => { + clearInterval(intervalId) +session.stop() + console.log('Bye') +}, 10000) diff --git a/binding/javascript/example/fix_simple/package.json b/binding/javascript/example/fix_simple/package.json new file mode 100644 index 00000000..f6e097dc --- /dev/null +++ b/binding/javascript/example/fix_simple/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ccapi": "file:../../../build/javascript/packaging/1.0.0" + } +} diff --git a/binding/javascript/example/handle_exception/index.js b/binding/javascript/example/handle_exception/index.js new file mode 100644 index 00000000..e647e620 --- /dev/null +++ b/binding/javascript/example/handle_exception/index.js @@ -0,0 +1,24 @@ +const ccapi = require('ccapi') +const Session = ccapi.Session +const SessionConfigs = ccapi.SessionConfigs +const SessionOptions = ccapi.SessionOptions +const Subscription = ccapi.Subscription +const Event = ccapi.Event +const sessionConfigs = new SessionConfigs() +const sessionOptions = new SessionOptions() +const session = new Session(sessionOptions, sessionConfigs) +const subscription = new Subscription('okx', 'BTC-USDT', 'MARKET_DEPTH') +session.subscribe(subscription) +const intervalId = setInterval(() => { + try { + throw new Error('oops') + } catch (error) { + console.error(error.stack) + process.exit(1) + } +}, 1) +setTimeout(() => { + clearInterval(intervalId) +session.stop() + console.log('Bye') +}, 10000) diff --git a/binding/javascript/example/handle_exception/package.json b/binding/javascript/example/handle_exception/package.json new file mode 100644 index 00000000..f6e097dc --- /dev/null +++ b/binding/javascript/example/handle_exception/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ccapi": "file:../../../build/javascript/packaging/1.0.0" + } +} diff --git a/binding/javascript/example/market_data_multiple_subscription/index.js b/binding/javascript/example/market_data_multiple_subscription/index.js new file mode 100644 index 00000000..6b34e213 --- /dev/null +++ b/binding/javascript/example/market_data_multiple_subscription/index.js @@ -0,0 +1,53 @@ +const ccapi = require('ccapi') +const Session = ccapi.Session +const SessionConfigs = ccapi.SessionConfigs +const SessionOptions = ccapi.SessionOptions +const SubscriptionList = ccapi.SubscriptionList +const Subscription = ccapi.Subscription +const Event = ccapi.Event +const sessionConfigs = new SessionConfigs() +const sessionOptions = new SessionOptions() +const session = new Session(sessionOptions, sessionConfigs) +const subscriptionList = new SubscriptionList() +subscriptionList.add(new Subscription('okx', 'BTC-USDT', 'MARKET_DEPTH', '', 'BTC')) +subscriptionList.add(new Subscription('okx', 'ETH-USDT', 'MARKET_DEPTH', '', 'ETH')) +session.subscribe(subscriptionList) +const intervalId = setInterval(() => { + const eventList = session.getEventQueue().purge() + for (let i = 0; i < eventList.size(); i++) { + const event = eventList.get(i) + if (event.getType() == Event.Type_SUBSCRIPTION_STATUS) { + console.log(`Received an event of type SUBSCRIPTION_STATUS:\n${event.toStringPretty(2, 2)}`) + } + else if (event.getType() == Event.Type_SUBSCRIPTION_DATA) { + const messageList = event.getMessageList() + for (let j = 0; j < messageList.size(); j++) { + const message = messageList.get(j) + const correlationId = message.getCorrelationIdList().get(0) + console.log(`${correlationId}: Best bid and ask at ${message.getTimeISO()} are:`) + const elementList = message.getElementList() + for (let k = 0; k < elementList.size(); k++) { + const element = elementList.get(k) + elementNameValueMap = element.getNameValueMap() + if (elementNameValueMap.has_key('BID_PRICE')) { + console.log(` BID_PRICE = ${elementNameValueMap.get('BID_PRICE')}`) + } + if (elementNameValueMap.has_key('BID_SIZE')) { + console.log(` BID_SIZE = ${elementNameValueMap.get('BID_SIZE')}`) + } + if (elementNameValueMap.has_key('ASK_PRICE')) { + console.log(` ASK_PRICE = ${elementNameValueMap.get('ASK_PRICE')}`) + } + if (elementNameValueMap.has_key('ASK_SIZE')) { + console.log(` ASK_SIZE = ${elementNameValueMap.get('ASK_SIZE')}`) + } + } + } + } + } +}, 1) +setTimeout(() => { + clearInterval(intervalId) +session.stop() + console.log('Bye') +}, 10000) diff --git a/binding/javascript/example/market_data_multiple_subscription/package.json b/binding/javascript/example/market_data_multiple_subscription/package.json new file mode 100644 index 00000000..f6e097dc --- /dev/null +++ b/binding/javascript/example/market_data_multiple_subscription/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ccapi": "file:../../../build/javascript/packaging/1.0.0" + } +} diff --git a/binding/javascript/example/market_data_simple_request/index.js b/binding/javascript/example/market_data_simple_request/index.js new file mode 100644 index 00000000..e04a17b8 --- /dev/null +++ b/binding/javascript/example/market_data_simple_request/index.js @@ -0,0 +1,26 @@ +const ccapi = require('ccapi') +const Session = ccapi.Session +const SessionConfigs = ccapi.SessionConfigs +const SessionOptions = ccapi.SessionOptions +const Request = ccapi.Request +const MapStringString = ccapi.MapStringString +const sessionConfigs = new SessionConfigs() +const sessionOptions = new SessionOptions() +const session = new Session(sessionOptions, sessionConfigs) +const request = new Request(Request.Operation_GET_RECENT_TRADES, 'okx', 'BTC-USDT') +const param = new MapStringString() +param.set('LIMIT', '1') +request.appendParam(param) +session.sendRequest(request) +const intervalId = setInterval(() => { + const eventList = session.getEventQueue().purge() + for (let i = 0; i < eventList.size(); i++) { + const event = eventList.get(i) + console.log(`Received an event:\n${event.toStringPretty(2, 2)}`) + } +}, 1) +setTimeout(() => { + clearInterval(intervalId) +session.stop() + console.log('Bye') +}, 10000) diff --git a/binding/javascript/example/market_data_simple_request/package.json b/binding/javascript/example/market_data_simple_request/package.json new file mode 100644 index 00000000..f6e097dc --- /dev/null +++ b/binding/javascript/example/market_data_simple_request/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ccapi": "file:../../../build/javascript/packaging/1.0.0" + } +} diff --git a/binding/javascript/example/market_data_simple_subscription/index.js b/binding/javascript/example/market_data_simple_subscription/index.js new file mode 100644 index 00000000..43575609 --- /dev/null +++ b/binding/javascript/example/market_data_simple_subscription/index.js @@ -0,0 +1,49 @@ +const ccapi = require('ccapi') +const Session = ccapi.Session +const SessionConfigs = ccapi.SessionConfigs +const SessionOptions = ccapi.SessionOptions +const Subscription = ccapi.Subscription +const Event = ccapi.Event +const sessionConfigs = new SessionConfigs() +const sessionOptions = new SessionOptions() +const session = new Session(sessionOptions, sessionConfigs) +const subscription = new Subscription('okx', 'BTC-USDT', 'MARKET_DEPTH') +session.subscribe(subscription) +const intervalId = setInterval(() => { + const eventList = session.getEventQueue().purge() + for (let i = 0; i < eventList.size(); i++) { + const event = eventList.get(i) + if (event.getType() == Event.Type_SUBSCRIPTION_STATUS) { + console.log(`Received an event of type SUBSCRIPTION_STATUS:\n${event.toStringPretty(2, 2)}`) + } + else if (event.getType() == Event.Type_SUBSCRIPTION_DATA) { + const messageList = event.getMessageList() + for (let j = 0; j < messageList.size(); j++) { + const message = messageList.get(j) + console.log(`Best bid and ask at ${message.getTimeISO()} are:`) + const elementList = message.getElementList() + for (let k = 0; k < elementList.size(); k++) { + const element = elementList.get(k) + elementNameValueMap = element.getNameValueMap() + if (elementNameValueMap.has_key('BID_PRICE')) { + console.log(` BID_PRICE = ${elementNameValueMap.get('BID_PRICE')}`) + } + if (elementNameValueMap.has_key('BID_SIZE')) { + console.log(` BID_SIZE = ${elementNameValueMap.get('BID_SIZE')}`) + } + if (elementNameValueMap.has_key('ASK_PRICE')) { + console.log(` ASK_PRICE = ${elementNameValueMap.get('ASK_PRICE')}`) + } + if (elementNameValueMap.has_key('ASK_SIZE')) { + console.log(` ASK_SIZE = ${elementNameValueMap.get('ASK_SIZE')}`) + } + } + } + } + } +}, 1) +setTimeout(() => { + clearInterval(intervalId) +session.stop() + console.log('Bye') +}, 10000) diff --git a/binding/javascript/example/market_data_simple_subscription/package.json b/binding/javascript/example/market_data_simple_subscription/package.json new file mode 100644 index 00000000..f6e097dc --- /dev/null +++ b/binding/javascript/example/market_data_simple_subscription/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ccapi": "file:../../../build/javascript/packaging/1.0.0" + } +} diff --git a/binding/javascript/package.json.in b/binding/javascript/package.json.in new file mode 100644 index 00000000..8e4eb365 --- /dev/null +++ b/binding/javascript/package.json.in @@ -0,0 +1,4 @@ +{ + "name": "ccapi", + "version": "@BUILD_VERSION@" +} diff --git a/binding/javascript/test/index.js b/binding/javascript/test/index.js new file mode 100644 index 00000000..9514fa0e --- /dev/null +++ b/binding/javascript/test/index.js @@ -0,0 +1,22 @@ +const ccapi = require('ccapi') +const Session = ccapi.Session +const SessionConfigs = ccapi.SessionConfigs +const SessionOptions = ccapi.SessionOptions +const Subscription = ccapi.Subscription +const Request = ccapi.Request +const MapStringString = ccapi.MapStringString +const Event = ccapi.Event +const sessionConfigs = new SessionConfigs() +const sessionOptions = new SessionOptions() +const session = new Session(sessionOptions, sessionConfigs) +const subscription = new Subscription('okx', 'BTC-USDT', 'MARKET_DEPTH') +session.subscribe(subscription) +const request = new Request(Request.Operation_GET_RECENT_TRADES, 'okx', 'BTC-USDT') +const param = new MapStringString() +param.set('LIMIT', '1') +request.appendParam(param) +session.sendRequest(request) +setTimeout(() => { + session.stop() + console.log('Bye') +}, 1) diff --git a/binding/python/CMakeLists.txt b/binding/python/CMakeLists.txt index a97748ee..2ccec4a3 100644 --- a/binding/python/CMakeLists.txt +++ b/binding/python/CMakeLists.txt @@ -29,7 +29,7 @@ target_link_libraries(${SWIG_TARGET_NAME} PRIVATE ${Python_LIBRARIES}) # ## Python Packaging ## # ####################### configure_file( - setup.py.in + ${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py.in @ONLY) file(GENERATE diff --git a/format.sh b/format.sh index eb10330a..1c57883e 100755 --- a/format.sh +++ b/format.sh @@ -3,3 +3,4 @@ ./format_csharp.sh ./format_go.sh ./format_java.sh +./format_javascript.sh diff --git a/format_javascript.sh b/format_javascript.sh new file mode 100755 index 00000000..f901d4cb --- /dev/null +++ b/format_javascript.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +find . -type f -path "*/binding/*" -not -path "*/build/*" -name "*.js" -exec clang-format -i -style=file {} \+