├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md └── bfcpp ├── CMakeLists.txt ├── bfcpp.sln ├── bfcpplib ├── CMakeLists.txt ├── Futures.cpp ├── Futures.hpp ├── IntervalTimer.cpp ├── IntervalTimer.hpp ├── bfcppCommon.hpp ├── bfcpplib.vcxproj └── bfcpplib.vcxproj.filters └── bfcpptest ├── CMakeLists.txt ├── Logger.hpp ├── OpenAndCloseLimitOrder.h ├── ScopedTimer.cpp ├── ScopedTimer.hpp ├── TestCommon.hpp ├── bfcpptest.cpp ├── bfcpptest.vcxproj └── bfcpptest.vcxproj.filters /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vcpkg_win"] 2 | path = vcpkg_win 3 | url = https://github.com/microsoft/vcpkg 4 | [submodule "vcpkg_linux"] 5 | path = vcpkg_linux 6 | url = https://github.com/microsoft/vcpkg 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | # UPDATE # 4 | I have created a new library, **Binance Beast**. It is a complete redesign, using Boost's Beast/ASIO and JSON libraries rather than cpprest. It has a much cleaner interface and is more usable. Use Binance Beast instead of this library: 5 | 6 | https://github.com/subatomicdev/binancebeast 7 | 8 | 9 | --- 10 | --- 11 | 12 | 13 | # Binance Futures C++ 14 | Binance Futures C++ is a C++17 library for Binance's REST and websockets API. 15 | 16 | The project uses Microsoft's cpprestsdk for asynchronous websockets/HTTP functionality. 17 | 18 | --- 19 | 20 | ## Design 21 | **bfcpplib** 22 | The library which handles all communications with the exchange 23 | 24 | 25 | **bfcpptest** 26 | A test app to show how to use the library. 27 | 28 | ### API 29 | The API is thin - it returns data in structs, which are wrappers for vectors and maps which contain the JSON. 30 | 31 | This is to avoid creating and populating objects when users will either already have, or intend to, create a class structure for their needs. 32 | 33 | ### WebSocket Monitor Functions 34 | Websocket streams are opened using the monitor functions, such as ```monitorMarkPrice()```. 35 | 36 | The monitor functions return a ```MonitorToken``` and take an ```std::function``` argument. The MonitorToken is used when cancelling the monitor function. 37 | 38 | ```cpp 39 | MonitorToken monitorMarkPrice(std::function onData); 40 | ``` 41 | 42 | 43 | ### Rest Functions 44 | Most of the Rest calls are synchronous, returning an appropriate object, e.g.: 45 | 46 | ```cpp 47 | AllOrdersResult allOrders(map&& query) 48 | ``` 49 | 50 | There are some which have an asynchronous version, such as ```newOrderAsync() ``` 51 | 52 | 53 | ## Examples 54 | 55 | ### Websockets - Monitor Mark Price and Mini Ticker 56 | This monitors the mark price and mini tickers for all symbols. 57 | 58 | ```cpp 59 | #include 60 | #include 61 | #include 62 | #include 63 | 64 | int main(int argc, char** argv) 65 | { 66 | std::cout << "\n\n--- USD-M Futures Multiple Streams on Futures ---\n"; 67 | 68 | auto markPriceHandler = [](std::any data) 69 | { 70 | auto priceData = std::any_cast (data); 71 | 72 | std::stringstream ss; 73 | 74 | for (const auto& pair : priceData.prices) 75 | { 76 | std::for_each(std::begin(pair), std::end(pair), [&ss](auto pair) { ss << "\n" << pair.first << "=" << pair.second; }); 77 | } 78 | 79 | logg(ss.str()); 80 | }; 81 | 82 | 83 | auto onSymbolMiniTickHandler = [](std::any data) 84 | { 85 | auto ticker = std::any_cast (data); 86 | stringstream ss; 87 | 88 | for (auto& tick : ticker.data) 89 | { 90 | std::for_each(std::begin(tick), std::end(tick), [&ss](auto pair) { ss << "\n" << pair.first << "=" << pair.second; }); 91 | } 92 | 93 | logg(ss.str()); 94 | }; 95 | 96 | UsdFuturesMarket usdFutures; 97 | usdFutures.monitorMarkPrice(markPriceHandler); 98 | usdFutures.monitorMiniTicker(onSymbolMiniTickHandler); 99 | 100 | std::this_thread::sleep_for(10s); 101 | 102 | return 0; 103 | } 104 | ``` 105 | 106 | ### New Order - Async 107 | This shows how to create orders asynchronously. The ```newOrder()``` returns a ```pplx::task``` which contains the API result (NewOrderResult). 108 | Each task is stored in a vector then we use ```pplx::when_all()``` to wait for all to complete. 109 | 110 | 111 | ```cpp 112 | #include 113 | #include 114 | #include 115 | #include 116 | 117 | static size_t NumNewOrders = 5; 118 | 119 | int main(int argc, char** argv) 120 | { 121 | std::cout << "\n\n--- USD-M Futures New Order Async ---\n"; 122 | 123 | UsdFuturesTestMarket market{ {"YOUR API KEY", "YOUR SECRET KEY"} }; 124 | 125 | vector> results; 126 | results.reserve(NumNewOrders); 127 | 128 | logg("Sending orders"); 129 | 130 | for (size_t i = 0; i < NumNewOrders; ++i) 131 | { 132 | map order = 133 | { 134 | {"symbol", "BTCUSDT"}, 135 | {"side", "BUY"}, 136 | {"type", "MARKET"}, 137 | {"quantity", "0.001"} 138 | }; 139 | 140 | results.emplace_back(std::move(market.newOrderAsync(std::move(order)))); 141 | } 142 | 143 | logg("Waiting for all to complete"); 144 | 145 | // note: you could use pplx::when_any() to handle each task as it completes, 146 | // then call when_any() until all are finished. 147 | 148 | // wait for the new order tasks to return, the majority of which is due to the REST call latency 149 | pplx::when_all(std::begin(results), std::end(results)).wait(); 150 | 151 | logg("Done: "); 152 | 153 | stringstream ss; 154 | ss << "\nOrder Ids: "; 155 | 156 | for (auto& task : results) 157 | { 158 | NewOrderResult result = task.get(); 159 | 160 | if (result.valid()) 161 | { 162 | // do stuff with result 163 | ss << "\n" << result.response["orderId"]; 164 | } 165 | } 166 | 167 | std::cout << ss.str(); 168 | 169 | return 0; 170 | } 171 | ``` 172 | Output: 173 | ``` 174 | [18:31:50.436] Sending orders 175 | [18:31:50.438] Waiting for all to complete 176 | [18:31:51.193] Done: 177 | [18:31:51.194] 178 | Order Ids: 179 | 2649069688 180 | 2649069693 181 | 2649069694 182 | 2649069692 183 | 2649069691 184 | ``` 185 | 186 | ### Get All Orders 187 | ```cpp 188 | int main(int argc, char** argv) 189 | { 190 | UsdFuturesTestMarket futuresTest { ApiAccess {"YOUR API KEY", "YOUR SECRET KEY"} }; 191 | 192 | framework::ScopedTimer timer; 193 | auto result = futuresTest.allOrders({ {"symbol", "BTCUSDT"} }); 194 | 195 | stringstream ss; 196 | ss << "\nFound " << result.response.size() << " orders in " << timer.stopLong() << " ms"; 197 | 198 | for (const auto& order : result.response) 199 | { 200 | ss << "\n{"; 201 | for (const auto& values : order) 202 | { 203 | ss << "\n\t" << values.first << "=" << values.second; 204 | } 205 | ss << "\n}"; 206 | } 207 | std::cout << ss.str(); 208 | 209 | return 0; 210 | } 211 | 212 | 213 | ``` 214 | --- 215 | 216 | 217 | ## Build 218 | 219 | Dependencies are handled by vcpkg, a cross platform package manager. 220 | 221 | ### Windows 222 | 1. Build vcpkg: open a command prompt in vcpkg_win and run: ```bootstrap-vcpkg.bat``` 223 | 2. Install dependencies: in the same prompt run: 224 | ``` 225 | .\vcpkg install cpprestsdk[websockets] poco boost-asio --triplet x64-windows-static 226 | ``` 227 | 3. Open the VS solution ```bfcpp/bfcpp.sln``` 228 | 4. Change to 'Release', right-click on the 'bfcpptest' project and select "Setup as startup project" 229 | 5. Run 230 | 231 | 232 | ### Linux 233 | _NOTE: testing on Linux has been limited, I hope to improve this in the coming weeks_ 234 | 235 | 1. Build vcpkg: open shell in vcpkg_linux and run: ```bootstrap-vcpkg.sh``` 236 | 2. Install dependencies: in the same prompt run: 237 | ``` 238 | ./vcpkg install cpprestsdk[websockets] poco boost-asio --triplet x64-linux 239 | ``` 240 | 3. Go up a directory then into 'bfcpp' directory and run: ```cmake . -DCMAKE_BUILD_TYPE=Release && make``` 241 | 4. The binary is in the 'bfcpptest' sub-dir ('bfcpp/bfcpptest' from the top level directory) 242 | 243 | 244 | # Run 245 | The provided ```bfcpptest/bfcpptest.cpp``` has a few functions to show the basics. 246 | 247 | - Some functions can run without an API or secret key, such as Kline/Candlesticks 248 | - You can pass an api/secret key by editing code or using a key file. The key file has 3 lines: 249 | ``` 250 | 251 | 252 | 253 | ``` 254 | e.g. : 255 | ``` 256 | test 257 | myapiKeyMyKey723423Ju&jNhuayNahas617238Jaiasjd31as52v46523435vs 258 | 8LBwbPvcub5GHtxLgWDZnm23KFcXwXwXwXwLBwbLBwbAABBca-sdasdasdas123 259 | ``` 260 | 261 | Run: ```>./bfcpptest /path/to/mykeyfile.txt``` 262 | -------------------------------------------------------------------------------- /bfcpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeList.txt : Top-level CMake project file, do global configuration 2 | # and include sub-projects here. 3 | # 4 | cmake_minimum_required (VERSION 3.8) 5 | 6 | project (bfcpp C CXX) 7 | 8 | # Include sub-projects. 9 | add_subdirectory("bfcpptest") 10 | add_subdirectory("bfcpplib") 11 | 12 | 13 | -------------------------------------------------------------------------------- /bfcpp/bfcpp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30804.86 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bfcpp", "bfcpptest\bfcpptest.vcxproj", "{4FEE7169-B7C3-4FB1-A962-B0CE975D765B}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {6D01FA21-81D5-452E-B5F4-D35DA664E602} = {6D01FA21-81D5-452E-B5F4-D35DA664E602} 9 | EndProjectSection 10 | EndProject 11 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bfcpplib", "bfcpplib\bfcpplib.vcxproj", "{6D01FA21-81D5-452E-B5F4-D35DA664E602}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|x64 = Release|x64 18 | Release|x86 = Release|x86 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {4FEE7169-B7C3-4FB1-A962-B0CE975D765B}.Debug|x64.ActiveCfg = Debug|x64 22 | {4FEE7169-B7C3-4FB1-A962-B0CE975D765B}.Debug|x64.Build.0 = Debug|x64 23 | {4FEE7169-B7C3-4FB1-A962-B0CE975D765B}.Debug|x86.ActiveCfg = Debug|Win32 24 | {4FEE7169-B7C3-4FB1-A962-B0CE975D765B}.Debug|x86.Build.0 = Debug|Win32 25 | {4FEE7169-B7C3-4FB1-A962-B0CE975D765B}.Release|x64.ActiveCfg = Release|x64 26 | {4FEE7169-B7C3-4FB1-A962-B0CE975D765B}.Release|x64.Build.0 = Release|x64 27 | {4FEE7169-B7C3-4FB1-A962-B0CE975D765B}.Release|x86.ActiveCfg = Release|Win32 28 | {4FEE7169-B7C3-4FB1-A962-B0CE975D765B}.Release|x86.Build.0 = Release|Win32 29 | {6D01FA21-81D5-452E-B5F4-D35DA664E602}.Debug|x64.ActiveCfg = Debug|x64 30 | {6D01FA21-81D5-452E-B5F4-D35DA664E602}.Debug|x64.Build.0 = Debug|x64 31 | {6D01FA21-81D5-452E-B5F4-D35DA664E602}.Debug|x86.ActiveCfg = Debug|Win32 32 | {6D01FA21-81D5-452E-B5F4-D35DA664E602}.Debug|x86.Build.0 = Debug|Win32 33 | {6D01FA21-81D5-452E-B5F4-D35DA664E602}.Release|x64.ActiveCfg = Release|x64 34 | {6D01FA21-81D5-452E-B5F4-D35DA664E602}.Release|x64.Build.0 = Release|x64 35 | {6D01FA21-81D5-452E-B5F4-D35DA664E602}.Release|x86.ActiveCfg = Release|Win32 36 | {6D01FA21-81D5-452E-B5F4-D35DA664E602}.Release|x86.Build.0 = Release|Win32 37 | EndGlobalSection 38 | GlobalSection(SolutionProperties) = preSolution 39 | HideSolutionNode = FALSE 40 | EndGlobalSection 41 | GlobalSection(ExtensibilityGlobals) = postSolution 42 | SolutionGuid = {8341C11A-B35A-4166-9E10-487057570908} 43 | EndGlobalSection 44 | EndGlobal 45 | -------------------------------------------------------------------------------- /bfcpp/bfcpplib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | include_directories("../../vcpkg_linux/installed/x64-linux/include") 3 | 4 | add_library(bfcpplib STATIC "IntervalTimer.cpp" "Futures.cpp") 5 | 6 | SET_TARGET_PROPERTIES(bfcpplib PROPERTIES LINKER_LANGUAGE CXX) 7 | SET_TARGET_PROPERTIES(bfcpplib PROPERTIES CXX_STANDARD 17) 8 | 9 | target_link_libraries(bfcpplib -lcpprest -lssl -lcrypto -lz -ldl) -------------------------------------------------------------------------------- /bfcpp/bfcpplib/Futures.cpp: -------------------------------------------------------------------------------- 1 | #include "Futures.hpp" 2 | 3 | 4 | namespace bfcpp 5 | { 6 | // -- Websocket monitors -- 7 | 8 | MonitorToken UsdFuturesMarket::monitorMiniTicker(std::function onData) 9 | { 10 | if (onData == nullptr) 11 | { 12 | throw BfcppException{ BFCPP_FUNCTION_MSG(" callback function null") }; 13 | } 14 | 15 | auto handler = [](ws::client::websocket_incoming_message websocketInMessage, shared_ptr session) 16 | { 17 | auto json = web::json::value::parse(websocketInMessage.extract_string().get()); 18 | 19 | AllMarketMiniTickerStream mtt; 20 | 21 | auto& data = json.as_array(); 22 | for (auto& entry : data) 23 | { 24 | map values; 25 | getJsonValues(entry, values, { "e", "E", "s", "c", "o", "h", "l", "v", "q" }); 26 | 27 | mtt.data.emplace_back(std::move(values)); 28 | } 29 | 30 | session->callback(std::any{ std::move(mtt) }); 31 | }; 32 | 33 | auto tokenAndSession = createMonitor(m_exchangeBaseUri + "/ws/!miniTicker@arr", handler); 34 | 35 | if (std::get<0>(tokenAndSession).isValid()) 36 | { 37 | std::get<1>(tokenAndSession)->callback = onData; 38 | } 39 | 40 | return std::get<0>(tokenAndSession); 41 | } 42 | 43 | 44 | 45 | MonitorToken UsdFuturesMarket::monitorKlineCandlestickStream(const string& symbol, const string& interval, std::function onData) 46 | { 47 | if (onData == nullptr) 48 | { 49 | throw BfcppException{ BFCPP_FUNCTION_MSG(" callback function null")}; 50 | } 51 | 52 | 53 | auto handler = [](ws::client::websocket_incoming_message websocketInMessage, shared_ptr session) 54 | { 55 | auto json = web::json::value::parse(websocketInMessage.extract_string().get()); 56 | 57 | CandleStream cs; 58 | 59 | cs.eventTime = jsonValueToString(json[utility::conversions::to_string_t("E")]); 60 | cs.symbol = jsonValueToString(json[utility::conversions::to_string_t("s")]); 61 | 62 | auto& candleData = json[utility::conversions::to_string_t("k")].as_object(); 63 | getJsonValues(candleData, cs.candle, { "t", "T", "s", "i", "f", "L", "o", "c", "h", "l", "v", "n", "x", "q", "V", "Q", "B" }); 64 | 65 | session->callback( std::any{ std::move(cs) }); 66 | }; 67 | 68 | auto tokenAndSession = createMonitor(m_exchangeBaseUri + "/ws/" + strToLower(symbol) + "@kline_" + interval, handler); 69 | 70 | if (std::get<0>(tokenAndSession).isValid()) 71 | { 72 | std::get<1>(tokenAndSession)->callback = onData; 73 | } 74 | 75 | return std::get<0>(tokenAndSession); 76 | } 77 | 78 | 79 | 80 | MonitorToken UsdFuturesMarket::monitorSymbol(const string& symbol, std::function onData) 81 | { 82 | if (onData == nullptr) 83 | { 84 | throw BfcppException{ BFCPP_FUNCTION_MSG(" callback function null") }; 85 | } 86 | 87 | auto handler = [](ws::client::websocket_incoming_message websocketInMessage, shared_ptr session) 88 | { 89 | auto json = web::json::value::parse(websocketInMessage.extract_string().get()); 90 | 91 | SymbolMiniTickerStream symbol; 92 | 93 | getJsonValues(json, symbol.data, { "e", "E", "s", "c", "o", "h", "l", "v", "q" }); 94 | 95 | session->callback(std::any{ std::move(symbol) }); 96 | }; 97 | 98 | 99 | auto tokenAndSession = createMonitor(m_exchangeBaseUri + "/ws/" + strToLower(symbol) + "@miniTicker", handler); 100 | 101 | if (std::get<0>(tokenAndSession).isValid()) 102 | { 103 | std::get<1>(tokenAndSession)->callback = onData; 104 | } 105 | 106 | return std::get<0>(tokenAndSession); 107 | } 108 | 109 | 110 | 111 | MonitorToken UsdFuturesMarket::monitorSymbolBookStream(const string& symbol, std::function onData) 112 | { 113 | if (onData == nullptr) 114 | { 115 | throw BfcppException{ BFCPP_FUNCTION_MSG("callback function null") }; 116 | } 117 | 118 | auto handler = [](ws::client::websocket_incoming_message websocketInMessage, shared_ptr session) 119 | { 120 | auto json = web::json::value::parse(websocketInMessage.extract_string().get()); 121 | 122 | SymbolBookTickerStream symbol; 123 | 124 | getJsonValues(json, symbol.data, { "e", "u","E","T","s","b","B", "a", "A" }); 125 | 126 | session->callback(std::any{ std::move(symbol) }); 127 | }; 128 | 129 | 130 | auto tokenAndSession = createMonitor(m_exchangeBaseUri + "/ws/" + strToLower(symbol) + "@bookTicker", handler); 131 | 132 | if (std::get<0>(tokenAndSession).isValid()) 133 | { 134 | std::get<1>(tokenAndSession)->callback = onData; 135 | } 136 | 137 | return std::get<0>(tokenAndSession); 138 | } 139 | 140 | 141 | 142 | MonitorToken UsdFuturesMarket::monitorMarkPrice(std::function onData, const string& symbol) 143 | { 144 | if (onData == nullptr) 145 | { 146 | throw BfcppException{ BFCPP_FUNCTION_MSG(" callback function null") }; 147 | } 148 | 149 | 150 | auto handler = [](ws::client::websocket_incoming_message websocketInMessage, shared_ptr session) 151 | { 152 | auto json = web::json::value::parse(websocketInMessage.extract_string().get()); 153 | 154 | MarkPriceStream mp; 155 | 156 | if (json.is_array()) 157 | { 158 | auto& prices = json.as_array(); 159 | for (auto& price : prices) 160 | { 161 | map values; 162 | getJsonValues(price, values, { "e", "E","s","p","i","P","r","T" }); 163 | 164 | mp.prices.emplace_back(std::move(values)); 165 | } 166 | } 167 | else 168 | { 169 | // called with the symbol set, so not an array 170 | map values; 171 | getJsonValues(json, values, { "e", "E","s","p","i","P","r","T" }); 172 | 173 | mp.prices.emplace_back(std::move(values)); 174 | } 175 | 176 | session->callback(std::any{ std::move(mp) }); 177 | }; 178 | 179 | string uri = "/ws/!markPrice@arr@1s"; 180 | 181 | if (!symbol.empty()) 182 | uri = "/ws/"+ strToLower(symbol)+"@markPrice@1s"; 183 | 184 | auto tokenAndSession = createMonitor(m_exchangeBaseUri + uri, handler); 185 | 186 | if (std::get<0>(tokenAndSession).isValid()) 187 | { 188 | std::get<1>(tokenAndSession)->callback = onData; 189 | } 190 | 191 | return std::get<0>(tokenAndSession); 192 | } 193 | 194 | 195 | 196 | MonitorToken UsdFuturesMarket::monitorUserData(std::function onData) 197 | { 198 | using namespace std::chrono_literals; 199 | 200 | if (onData == nullptr) 201 | { 202 | throw BfcppException{ BFCPP_FUNCTION_MSG("callback function null")}; 203 | } 204 | 205 | MonitorToken monitorToken; 206 | 207 | if (createListenKey(m_marketType)) 208 | { 209 | if (auto session = connect(m_exchangeBaseUri + "/ws/" + m_listenKey); session) 210 | { 211 | try 212 | { 213 | monitorToken.id = m_monitorId++; 214 | 215 | session->id = monitorToken.id; 216 | session->callback = onData; 217 | 218 | m_idToSession[monitorToken.id] = session; 219 | m_sessions.push_back(session); 220 | 221 | auto token = session->getCancelToken(); 222 | session->receiveTask = pplx::create_task([session, token, onData, this] 223 | { 224 | try 225 | { 226 | handleUserDataStream(session, onData); 227 | } 228 | catch (BfcppDisconnectException) 229 | { 230 | throw; 231 | } 232 | catch (std::exception ex) 233 | { 234 | pplx::cancel_current_task(); 235 | } 236 | }, token); 237 | 238 | 239 | auto timerFunc = std::bind(&UsdFuturesMarket::onUserDataTimer, this); 240 | 241 | if (m_marketType == MarketType::FuturesTest) 242 | { 243 | //TODO ISSUE this doesn't seem to please the testnet, creating orders on the site keeps the connection alive 244 | m_userDataStreamTimer.start(timerFunc, 45s); // the test net seems to kick us out after 60s of no activity 245 | } 246 | else 247 | { 248 | m_userDataStreamTimer.start(timerFunc, 60s * 45); // 45 mins 249 | } 250 | } 251 | catch (BfcppDisconnectException) 252 | { 253 | throw; 254 | } 255 | catch (std::exception ex) 256 | { 257 | throw BfcppException(ex.what()); 258 | } 259 | } 260 | } 261 | 262 | return monitorToken; 263 | } 264 | 265 | 266 | 267 | MonitorToken UsdFuturesMarket::monitorPartialBookDepth(const string& symbol, const string& level, const string& interval, std::function onData) 268 | { 269 | if (onData == nullptr) 270 | { 271 | throw BfcppException{ BFCPP_FUNCTION_MSG(" callback function null") }; 272 | } 273 | 274 | return doMonitorBookDepth(symbol, level, interval, onData); 275 | } 276 | 277 | 278 | 279 | MonitorToken UsdFuturesMarket::monitorDiffBookDepth(const string& symbol, const string& interval, std::function onData) 280 | { 281 | if (onData == nullptr) 282 | { 283 | throw BfcppException{ BFCPP_FUNCTION_MSG(" callback function null") }; 284 | } 285 | 286 | return doMonitorBookDepth(symbol, "", interval, onData); 287 | } 288 | 289 | 290 | 291 | MonitorToken UsdFuturesMarket::doMonitorBookDepth(const string& symbol, const string& level, const string& interval, std::function onData) 292 | { 293 | auto handler = [](ws::client::websocket_incoming_message websocketInMessage, shared_ptr session) 294 | { 295 | static const utility::string_t SymbolField = utility::conversions::to_string_t("s"); 296 | static const utility::string_t EventTimeField = utility::conversions::to_string_t("E"); 297 | static const utility::string_t TransactionTimeField = utility::conversions::to_string_t("T"); 298 | static const utility::string_t FirstUpdateIdField = utility::conversions::to_string_t("U"); 299 | static const utility::string_t FinalUpdateIdField = utility::conversions::to_string_t("u"); 300 | static const utility::string_t PreviousFinalUpdateIdField = utility::conversions::to_string_t("pu"); 301 | static const utility::string_t BidsField = utility::conversions::to_string_t("b"); 302 | static const utility::string_t AsksField = utility::conversions::to_string_t("a"); 303 | 304 | 305 | BookDepthStream result; 306 | 307 | auto json = web::json::value::parse(websocketInMessage.extract_string().get()); 308 | 309 | result.symbol = jsonValueToString(json[utility::conversions::to_string_t(SymbolField)]); 310 | result.eventTime = jsonValueToString(json[utility::conversions::to_string_t(EventTimeField)]); 311 | result.transactionTime = jsonValueToString(json[utility::conversions::to_string_t(TransactionTimeField)]); 312 | result.firstUpdateId = jsonValueToString(json[utility::conversions::to_string_t(FirstUpdateIdField)]); 313 | result.finalUpdateId = jsonValueToString(json[utility::conversions::to_string_t(FinalUpdateIdField)]); 314 | result.previousFinalUpdateId = jsonValueToString(json[utility::conversions::to_string_t(PreviousFinalUpdateIdField)]); 315 | 316 | // bids 317 | auto& bidsArray = json[BidsField].as_array(); 318 | 319 | for (auto& bid : bidsArray) 320 | { 321 | auto& bidValue = bid.as_array(); 322 | result.bids.emplace_back(std::make_pair(jsonValueToString(bidValue[0]), jsonValueToString(bidValue[1]))); 323 | } 324 | 325 | // asks 326 | auto& asksArray = json[AsksField].as_array(); 327 | 328 | for (auto& ask : asksArray) 329 | { 330 | auto& askValue = ask.as_array(); 331 | result.asks.emplace_back(std::make_pair(jsonValueToString(askValue[0]), jsonValueToString(askValue[1]))); 332 | } 333 | 334 | session->callback(std::any{ std::move(result) }); 335 | }; 336 | 337 | auto tokenAndSession = createMonitor(m_exchangeBaseUri + "/ws/" + strToLower(symbol) + "@depth" + level + "@" + interval, handler); 338 | 339 | if (std::get<0>(tokenAndSession).isValid()) 340 | { 341 | std::get<1>(tokenAndSession)->callback = onData; 342 | } 343 | 344 | return std::get<0>(tokenAndSession); 345 | } 346 | 347 | 348 | // -- REST Calls -- 349 | 350 | 351 | AccountInformation UsdFuturesMarket::accountInformation() 352 | { 353 | try 354 | { 355 | auto handler = [](web::http::http_response response) 356 | { 357 | AccountInformation info; 358 | 359 | auto json = response.extract_json().get(); 360 | 361 | const utility::string_t AssetField = utility::conversions::to_string_t("assets"); 362 | const utility::string_t PositionsField = utility::conversions::to_string_t("positions"); 363 | 364 | 365 | getJsonValues(json, info.data, vector { "feeTier", "canTrade", "canDeposit", "canWithdraw", "updateTime", "totalInitialMargin", "totalMaintMargin", "totalWalletBalance", 366 | "totalUnrealizedProfit", "totalMarginBalance", "totalPositionInitialMargin", "totalOpenOrderInitialMargin", "totalCrossWalletBalance", 367 | "totalCrossUnPnl", "availableBalance", "maxWithdrawAmount"}); 368 | 369 | 370 | auto& assetArray = json[AssetField].as_array(); 371 | for (const auto& entry : assetArray) 372 | { 373 | map order; 374 | getJsonValues(entry, order, vector { "asset", "walletBalance", "unrealizedProfit", "marginBalance", "maintMargin", "initialMargin", "positionInitialMargin", 375 | "openOrderInitialMargin", "crossWalletBalance", "crossUnPnl", "availableBalance", "maxWithdrawAmount"}); 376 | 377 | info.assets.emplace_back(std::move(order)); 378 | } 379 | 380 | 381 | auto& positionArray = json[PositionsField].as_array(); 382 | for (const auto& entry : positionArray) 383 | { 384 | map position; 385 | getJsonValues(entry, position, vector { "symbol", "initialMargin", "maintMargin", "unrealizedProfit", "positionInitialMargin", "openOrderInitialMargin", 386 | "leverage", "isolated", "entryPrice", "maxNotional", "positionSide", "positionAmt"}); 387 | 388 | info.positions.emplace_back(std::move(position)); 389 | } 390 | 391 | return info; 392 | }; 393 | 394 | return sendRestRequest(RestCall::AccountInfo, web::http::methods::GET, true, m_marketType, handler, receiveWindow(RestCall::AccountInfo)).get(); 395 | } 396 | catch (const pplx::task_canceled tc) 397 | { 398 | throw BfcppDisconnectException("accountInformation"); 399 | } 400 | catch (const std::exception ex) 401 | { 402 | throw BfcppException(ex.what()); 403 | } 404 | } 405 | 406 | 407 | 408 | AccountBalance UsdFuturesMarket::accountBalance() 409 | { 410 | try 411 | { 412 | auto handler = [](web::http::http_response response) 413 | { 414 | AccountBalance balance; 415 | 416 | auto json = response.extract_json().get(); 417 | for (const auto& entry : json.as_array()) 418 | { 419 | map order; 420 | getJsonValues(entry, order, { "accountAlias", "asset", "balance", "crossWalletBalance", "crossUnPnl", "availableBalance", "maxWithdrawAmount"}); 421 | 422 | balance.balances.emplace_back(std::move(order)); 423 | } 424 | 425 | return balance; 426 | }; 427 | 428 | return sendRestRequest(RestCall::AccountBalance, web::http::methods::GET, true, m_marketType, handler, receiveWindow(RestCall::AccountBalance)).get(); 429 | } 430 | catch (const pplx::task_canceled tc) 431 | { 432 | throw BfcppDisconnectException("accountBalance"); 433 | } 434 | catch (const std::exception ex) 435 | { 436 | throw BfcppException(ex.what()); 437 | } 438 | } 439 | 440 | 441 | 442 | TakerBuySellVolume UsdFuturesMarket::takerBuySellVolume(map&& query) 443 | { 444 | try 445 | { 446 | auto handler = [](web::http::http_response response) 447 | { 448 | TakerBuySellVolume result; 449 | 450 | auto json = response.extract_json().get(); 451 | 452 | for (const auto& entry : json.as_array()) 453 | { 454 | map order; 455 | getJsonValues(entry, order, { "buySellRatio", "buyVol", "sellVol", "timestamp"}); 456 | 457 | result.response.emplace_back(std::move(order)); 458 | } 459 | 460 | return result; 461 | }; 462 | 463 | return sendRestRequest(RestCall::TakerBuySellVolume, web::http::methods::GET, true, m_marketType, handler, receiveWindow(RestCall::TakerBuySellVolume), std::move(query)).get(); 464 | } 465 | catch (const pplx::task_canceled tc) 466 | { 467 | throw BfcppDisconnectException("takerBuySellVolume"); 468 | } 469 | catch (const std::exception ex) 470 | { 471 | throw BfcppException(ex.what()); 472 | } 473 | } 474 | 475 | 476 | 477 | KlineCandlestick UsdFuturesMarket::klines(map&& query) 478 | { 479 | try 480 | { 481 | auto handler = [](web::http::http_response response) 482 | { 483 | KlineCandlestick result; 484 | 485 | auto json = response.extract_json().get(); 486 | 487 | // kline does not return a key/value map, instead an array of arrays. 488 | // the outer array has an entry per interval, with each inner array containing 12 fields for that interval (i.e. open time, close time, open price, close price, etc) 489 | 490 | auto& intervalPeriod = json.as_array(); 491 | for (auto& interval : intervalPeriod) 492 | { 493 | vector stickValues; 494 | stickValues.reserve(12); 495 | 496 | auto& sticksArray = interval.as_array(); 497 | for (auto& stick : sticksArray) 498 | { 499 | stickValues.emplace_back(jsonValueToString(stick)); 500 | } 501 | 502 | result.response.emplace_back(std::move(stickValues)); 503 | } 504 | 505 | return result; 506 | }; 507 | 508 | return sendRestRequest(RestCall::KlineCandles, web::http::methods::GET, true, m_marketType, handler, receiveWindow(RestCall::KlineCandles), std::move(query)).get(); 509 | } 510 | catch (const pplx::task_canceled tc) 511 | { 512 | throw BfcppDisconnectException("klines"); 513 | } 514 | catch (const std::exception ex) 515 | { 516 | throw BfcppException(ex.what()); 517 | } 518 | } 519 | 520 | 521 | 522 | AllOrdersResult UsdFuturesMarket::allOrders(map&& query) 523 | { 524 | try 525 | { 526 | auto handler = [](web::http::http_response response) 527 | { 528 | AllOrdersResult result; 529 | 530 | auto json = response.extract_json().get(); 531 | 532 | for (const auto& entry : json.as_array()) 533 | { 534 | map order; 535 | getJsonValues(entry, order, { "avgPrice", "clientOrderId", "cumQuote", "executedQty", "orderId", "origQty", "origType", "price", "reduceOnly", "side", "positionSide", "status", 536 | "stopPrice", "closePosition", "symbol", "time", "timeInForce", "type", "activatePrice", "priceRate", "updateTime", "workingType", "priceProtect"}); 537 | 538 | result.response.emplace_back(std::move(order)); 539 | } 540 | 541 | return result; 542 | }; 543 | 544 | return sendRestRequest(RestCall::AllOrders, web::http::methods::GET, true, m_marketType, handler, receiveWindow(RestCall::AllOrders), std::move(query)).get(); 545 | } 546 | catch (const pplx::task_canceled tc) 547 | { 548 | throw BfcppDisconnectException("allOrders"); 549 | } 550 | catch (const std::exception ex) 551 | { 552 | throw BfcppException(ex.what()); 553 | } 554 | } 555 | 556 | 557 | 558 | ExchangeInfo UsdFuturesMarket::exchangeInfo() 559 | { 560 | try 561 | { 562 | auto handler = [](web::http::http_response response) 563 | { 564 | ExchangeInfo result; 565 | 566 | auto json = response.extract_json().get(); 567 | 568 | result.timezone = jsonValueToString(json[utility::conversions::to_string_t("timezone")]); 569 | result.serverTime = jsonValueToString(json[utility::conversions::to_string_t("serverTime")]); 570 | 571 | // rate limits 572 | auto& rateLimits = json[utility::conversions::to_string_t("rateLimits")].as_array(); 573 | for (auto& rate : rateLimits) 574 | { 575 | map values; 576 | getJsonValues(rate, values, { "rateLimitType", "interval", "intervalNum", "limit"}); 577 | 578 | result.rateLimits.emplace_back(std::move(values)); 579 | } 580 | 581 | 582 | // symbols 583 | auto& symbols = json[utility::conversions::to_string_t("symbols")].as_array(); 584 | for (auto& symbol : symbols) 585 | { 586 | ExchangeInfo::Symbol sym; 587 | 588 | getJsonValues(symbol, sym.data, { "symbol", "pair", "contractType", "deliveryDate", "onboardDate", "status", "maintMarginPercent", "requiredMarginPercent", "baseAsset", 589 | "quoteAsset", "marginAsset", "pricePrecision", "quantityPrecision", "baseAssetPrecision", "quotePrecision", "underlyingType", 590 | "settlePlan", "triggerProtect"}); 591 | 592 | 593 | auto& subType = symbol[utility::conversions::to_string_t("underlyingSubType")].as_array(); 594 | for (auto& st : subType) 595 | { 596 | sym.underlyingSubType.emplace_back(jsonValueToString(st)); 597 | } 598 | 599 | 600 | auto& filters = symbol[utility::conversions::to_string_t("filters")].as_array(); 601 | for (auto& filter : filters) 602 | { 603 | map values; 604 | getJsonValues(filter, values, {"filterType", "maxPrice", "minPrice", "tickSize", "stepSize", "maxQty", "minQty", "notional", "multiplierDown", "multiplierUp", "multiplierDecimal"}); 605 | 606 | sym.filters.emplace_back(std::move(values)); 607 | } 608 | 609 | 610 | auto& orderTypes = symbol[utility::conversions::to_string_t("orderTypes")].as_array(); 611 | for (auto& ot : orderTypes) 612 | { 613 | sym.orderTypes.emplace_back(jsonValueToString(ot)); 614 | } 615 | 616 | 617 | auto& tif = symbol[utility::conversions::to_string_t("timeInForce")].as_array(); 618 | for (auto& t : tif) 619 | { 620 | sym.timeInForce.emplace_back(jsonValueToString(t)); 621 | } 622 | 623 | result.symbols.emplace_back(sym); 624 | } 625 | 626 | return result; 627 | }; 628 | 629 | return sendRestRequest(RestCall::ExchangeInfo, web::http::methods::GET, true, m_marketType, handler, receiveWindow(RestCall::ExchangeInfo)).get(); 630 | } 631 | catch (const pplx::task_canceled tc) 632 | { 633 | throw BfcppDisconnectException("klines"); 634 | } 635 | catch (const std::exception ex) 636 | { 637 | throw BfcppException(ex.what()); 638 | } 639 | } 640 | 641 | 642 | 643 | OrderBook UsdFuturesMarket::orderBook(map&& query) 644 | { 645 | try 646 | { 647 | auto handler = [](web::http::http_response response) 648 | { 649 | OrderBook result; 650 | 651 | auto json = response.extract_json().get(); 652 | 653 | static const utility::string_t BidsField = utility::conversions::to_string_t("bids"); 654 | static const utility::string_t AsksField = utility::conversions::to_string_t("asks"); 655 | 656 | result.messageOutputTime = jsonValueToString(json[utility::conversions::to_string_t("E")]); 657 | result.transactionTime = jsonValueToString(json[utility::conversions::to_string_t("T")]); 658 | result.lastUpdateId = jsonValueToString(json[utility::conversions::to_string_t("lastUpdateId")]); 659 | 660 | // bids 661 | auto& bidsArray = json[BidsField].as_array(); 662 | 663 | for (auto& bid : bidsArray) 664 | { 665 | auto& bidValue = bid.as_array(); 666 | result.bids.emplace_back(std::make_pair(jsonValueToString(bidValue[0]), jsonValueToString(bidValue[1]))); 667 | } 668 | 669 | // asks 670 | auto& asksArray = json[AsksField].as_array(); 671 | 672 | for (auto& ask : asksArray) 673 | { 674 | auto& askValue = ask.as_array(); 675 | result.asks.emplace_back(std::make_pair(jsonValueToString(askValue[0]), jsonValueToString(askValue[1]))); 676 | } 677 | 678 | return result; 679 | }; 680 | 681 | return sendRestRequest(RestCall::OrderBook , web::http::methods::GET, false, m_marketType, handler, receiveWindow(RestCall::OrderBook), std::move(query)).get(); 682 | } 683 | catch (const pplx::task_canceled tc) 684 | { 685 | throw BfcppDisconnectException("klines"); 686 | } 687 | catch (const std::exception ex) 688 | { 689 | throw BfcppException(ex.what()); 690 | } 691 | } 692 | 693 | 694 | 695 | 696 | 697 | 698 | // -- connection/session --- 699 | 700 | void UsdFuturesMarket::disconnect(const MonitorToken& mt, const bool deleteSession) 701 | { 702 | if (auto itIdToSession = m_idToSession.find(mt.id); itIdToSession != m_idToSession.end()) 703 | { 704 | auto& session = itIdToSession->second; 705 | 706 | session->cancel(); 707 | 708 | session->client.close(ws::client::websocket_close_status::going_away).then([&session]() 709 | { 710 | session->connected = false; 711 | }).wait(); 712 | 713 | // calling wait() on a task that's already cancelled throws an exception 714 | if (!session->receiveTask.is_done()) 715 | { 716 | session->receiveTask.wait(); 717 | } 718 | 719 | 720 | // when called from disconnect() this flag is false to avoid invalidating iterators in m_idToSession, 721 | // this is tidier than returning the new iterator from erase() 722 | if (deleteSession) 723 | { 724 | if (auto storedSessionIt = std::find_if(m_sessions.cbegin(), m_sessions.cend(), [this, &mt](auto& sesh) { return sesh->id == mt.id; }); storedSessionIt != m_sessions.end()) 725 | { 726 | m_sessions.erase(storedSessionIt); 727 | } 728 | 729 | m_idToSession.erase(itIdToSession); 730 | } 731 | } 732 | } 733 | 734 | 735 | 736 | void UsdFuturesMarket::disconnect() 737 | { 738 | vector> disconnectTasks; 739 | 740 | for (const auto& idToSession : m_idToSession) 741 | { 742 | disconnectTasks.emplace_back(pplx::create_task([&idToSession, this] 743 | { 744 | disconnect(idToSession.first, false); 745 | })); 746 | } 747 | 748 | pplx::when_all(disconnectTasks.begin(), disconnectTasks.end()).wait(); 749 | 750 | m_idToSession.clear(); 751 | m_sessions.clear(); 752 | } 753 | 754 | 755 | 756 | bool UsdFuturesMarket::createListenKey(const MarketType marketType) 757 | { 758 | try 759 | { 760 | auto handler = [](web::http::http_response response) 761 | { 762 | ListenKey result; 763 | 764 | auto json = response.extract_json().get(); 765 | 766 | result.listenKey = utility::conversions::to_utf8string(json[utility::conversions::to_string_t(ListenKeyName)].as_string()); 767 | 768 | return result; 769 | }; 770 | 771 | auto lk = sendRestRequest(RestCall::ListenKey, web::http::methods::POST, true, marketType, handler, receiveWindow(RestCall::ListenKey)).get(); 772 | 773 | m_listenKey = lk.listenKey; 774 | 775 | return lk.valid() && !m_listenKey.empty(); 776 | } 777 | catch (const pplx::task_canceled tc) 778 | { 779 | throw BfcppDisconnectException("createListenKey"); 780 | } 781 | catch (const std::exception ex) 782 | { 783 | throw BfcppException(ex.what()); 784 | } 785 | } 786 | 787 | 788 | 789 | 790 | // -- data/util -- 791 | 792 | void UsdFuturesMarket::extractUsdFuturesUserData(shared_ptr session, web::json::value&& jsonVal) 793 | { 794 | const utility::string_t CodeField = utility::conversions::to_string_t("code"); 795 | const utility::string_t MsgField = utility::conversions::to_string_t("msg"); 796 | 797 | if (jsonVal.has_string_field(CodeField) && jsonVal.has_string_field(MsgField)) 798 | { 799 | throw BfcppException(utility::conversions::to_utf8string(jsonVal.at(CodeField).as_string()) + " : " + utility::conversions::to_utf8string(jsonVal.at(MsgField).as_string())); 800 | } 801 | else 802 | { 803 | const utility::string_t EventTypeField = utility::conversions::to_string_t("e"); 804 | const utility::string_t EventMarginCall = utility::conversions::to_string_t("MARGIN_CALL"); 805 | const utility::string_t EventOrderTradeUpdate = utility::conversions::to_string_t("ORDER_TRADE_UPDATE"); 806 | const utility::string_t EventAccountUpdate = utility::conversions::to_string_t("ACCOUNT_UPDATE"); 807 | const utility::string_t EventStreamExpired = utility::conversions::to_string_t("listenKeyExpired"); 808 | 809 | 810 | UsdFutureUserData::EventType type = UsdFutureUserData::EventType::Unknown; 811 | 812 | auto& eventValue = jsonVal.at(EventTypeField).as_string(); 813 | 814 | if (eventValue == EventMarginCall) 815 | { 816 | type = UsdFutureUserData::EventType::MarginCall; 817 | } 818 | else if (eventValue == EventOrderTradeUpdate) 819 | { 820 | type = UsdFutureUserData::EventType::OrderUpdate; 821 | } 822 | else if (eventValue == EventAccountUpdate) 823 | { 824 | type = UsdFutureUserData::EventType::AccountUpdate; 825 | } 826 | else if (eventValue == EventStreamExpired) 827 | { 828 | type = UsdFutureUserData::EventType::DataStreamExpired; 829 | } 830 | 831 | 832 | UsdFutureUserData userData(type); 833 | 834 | if (type != UsdFutureUserData::EventType::Unknown) 835 | { 836 | switch (type) 837 | { 838 | 839 | case UsdFutureUserData::EventType::MarginCall: 840 | { 841 | const utility::string_t BalancesField = utility::conversions::to_string_t("p"); 842 | 843 | getJsonValues(jsonVal, userData.mc.data, { "e", "E", "cw" }); 844 | 845 | for (auto& balance : jsonVal[BalancesField].as_array()) 846 | { 847 | map values; 848 | getJsonValues(balance, values, { "s", "ps", "pa", "mt", "iw", "mp", "up", "mm" }); 849 | 850 | userData.mc.positions[values["s"]] = std::move(values); 851 | } 852 | } 853 | break; 854 | 855 | 856 | case UsdFutureUserData::EventType::OrderUpdate: 857 | { 858 | const utility::string_t OrdersField = utility::conversions::to_string_t("o"); 859 | 860 | getJsonValues(jsonVal, userData.ou.data, { "e", "E", "T" }); 861 | 862 | map values; 863 | getJsonValues(jsonVal[OrdersField].as_object(), values, { "s", "c", "S", "o", "f", "q", "p", "ap", "sp", "x", "X", "i", "l", "z", "L", "N", 864 | "n", "T", "t", "b", "a", "m", "R", "wt", "ot", "ps", "cp", "AP", "cr", "rp" }); 865 | 866 | userData.ou.orders[values["s"]] = std::move(values); 867 | } 868 | break; 869 | 870 | 871 | case UsdFutureUserData::EventType::AccountUpdate: 872 | { 873 | const utility::string_t UpdateDataField = utility::conversions::to_string_t("a"); 874 | const utility::string_t ReasonDataField = utility::conversions::to_string_t("m"); 875 | const utility::string_t BalancesField = utility::conversions::to_string_t("B"); 876 | const utility::string_t PositionsField = utility::conversions::to_string_t("P"); 877 | 878 | 879 | getJsonValues(jsonVal, userData.au.data, { "e", "E", "T" }); 880 | 881 | auto& updateDataJson = jsonVal[UpdateDataField].as_object(); 882 | 883 | userData.au.reason = utility::conversions::to_utf8string(updateDataJson.at(ReasonDataField).as_string()); 884 | 885 | for (auto& balance : updateDataJson.at(BalancesField).as_array()) 886 | { 887 | map values; 888 | getJsonValues(balance, values, { "a", "wb", "cw" }); 889 | 890 | userData.au.balances.emplace_back(std::move(values)); 891 | } 892 | 893 | if (auto positions = updateDataJson.find(PositionsField); positions != updateDataJson.end()) 894 | { 895 | for (auto& position : positions->second.as_array()) 896 | { 897 | map values; 898 | getJsonValues(position, values, { "s", "pa", "ep", "cr", "up", "mt", "iw", "ps" }); 899 | 900 | userData.au.positions.emplace_back(std::move(values)); 901 | } 902 | } 903 | } 904 | break; 905 | 906 | 907 | case UsdFutureUserData::EventType::DataStreamExpired: 908 | throw BfcppException("Usd Futures user data stream has expired"); 909 | break; 910 | 911 | 912 | default: 913 | // handled above 914 | break; 915 | } 916 | 917 | 918 | session->callback(std::any{ std::move(userData) }); 919 | } 920 | } 921 | } 922 | 923 | } -------------------------------------------------------------------------------- /bfcpp/bfcpplib/Futures.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __BINANCE_FUTURES_HPP 2 | #define __BINANCE_FUTURES_HPP 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "IntervalTimer.hpp" 16 | #include "bfcppCommon.hpp" 17 | 18 | 19 | namespace bfcpp 20 | { 21 | /// 22 | /// Access the USD-M Future's market. You must have a Futures account. 23 | /// The APis keys must be enabled for Futures in the API Management settings. 24 | /// If you created the API key before you created your Futures account, you must create a new API key. 25 | /// 26 | class UsdFuturesMarket 27 | { 28 | inline const static string DefaultReceiveWindow = "5000"; 29 | 30 | 31 | protected: 32 | UsdFuturesMarket(MarketType mt, const string& exchangeUri, const ApiAccess& access) : m_marketType(mt), m_exchangeBaseUri(exchangeUri), m_apiAccess(access) 33 | { 34 | m_monitorId = 1; 35 | } 36 | 37 | 38 | public: 39 | UsdFuturesMarket(const ApiAccess& access = {}) : UsdFuturesMarket(MarketType::Futures, FuturestWebSockUri, access) 40 | { 41 | 42 | } 43 | 44 | 45 | virtual ~UsdFuturesMarket() 46 | { 47 | disconnect(); 48 | } 49 | 50 | /// 51 | /// Gets the receive window for the given call. This will return the default (DefaultReceiveWindow) unless 52 | /// overwritten with setReceiveWindow(). 53 | /// 54 | /// The rest call type 55 | /// Time, in milliseconds, as a string 56 | string receiveWindow(const RestCall rc) 57 | { 58 | if (auto it = m_receiveWindowMap.find(rc); it == m_receiveWindowMap.cend()) 59 | return DefaultReceiveWindow; 60 | else 61 | return it->second; 62 | } 63 | 64 | /// 65 | /// This measures the time it takes to send a "PING" request to the exchange and receive a reply. 66 | /// It includes near zero processing by bfcpp, so the returned duration can be assumed to be network latency plus Binance's processing time. 67 | /// Testing has seen this latency range from 300ms to 750ms between calls, whilst an ICMP ping is 18ms. 68 | /// See https://binance-docs.github.io/apidocs/futures/en/#test-connectivity. 69 | /// 70 | /// The latency in milliseconds 71 | std::chrono::milliseconds ping() 72 | { 73 | try 74 | { 75 | web::http::client::http_client client{ web::uri { utility::conversions::to_string_t(getApiUri(m_marketType)) } }; 76 | 77 | auto request = createHttpRequest(web::http::methods::POST, getApiPath(m_marketType, RestCall::Ping) + "?" + createQueryString({}, RestCall::Ping, false, receiveWindow(RestCall::Ping))); 78 | 79 | auto send = Clock::now(); 80 | auto rcv = client.request(std::move(request)).then([](web::http::http_response response) { return Clock::now(); }).get(); 81 | 82 | return std::chrono::duration_cast(rcv - send); 83 | } 84 | catch (const pplx::task_canceled tc) 85 | { 86 | throw BfcppDisconnectException("ping"); 87 | } 88 | catch (const std::exception ex) 89 | { 90 | throw BfcppException(ex.what()); 91 | } 92 | } 93 | 94 | 95 | 96 | 97 | // --- monitor functions 98 | 99 | 100 | /// 101 | /// See https://binance-docs.github.io/apidocs/futures/en/#mark-price-stream-for-all-market. 102 | /// This is hardwired to the max frequency of 1000ms. 103 | /// 104 | /// Your callback function. The any holds an MarkPriceStream object 105 | /// Optional symbol. If set, only mark price for that symbol is returned 106 | /// Monitor token, used to cancel the monitor 107 | MonitorToken monitorMarkPrice(std::function onData, const string& symbol = ""); 108 | 109 | 110 | /// 111 | /// Monitor data on the spot market. 112 | /// 113 | /// Your callback function. The any holds an UsdFutureUserData object 114 | /// Monitor token, used to cancel the monitor 115 | MonitorToken monitorUserData(std::function onData); 116 | 117 | 118 | /// 119 | /// Receives from the miniTicker stream for all symbols. Updates every 1000ms (limited by the Binance API). 120 | /// See https://binance-docs.github.io/apidocs/futures/en/#all-market-mini-tickers-stream 121 | /// 122 | /// Your callback function. The any holds an AllMarketMiniTickerStream object 123 | /// Monitor token, used to cancel the monitor 124 | MonitorToken monitorMiniTicker(std::function onData); 125 | 126 | 127 | /// 128 | /// Receives from the Kline/Candlestick stream. 129 | /// See https://binance-docs.github.io/apidocs/futures/en/#kline-candlestick-streams 130 | /// 131 | /// Which symbol receive sticks for 132 | /// Period, such as "15m". See Binance docs. 133 | /// Your callback function. The any holds an CandleStream object 134 | /// Monitor token, used to cancel the monitor 135 | MonitorToken monitorKlineCandlestickStream(const string& symbol, const string& interval, std::function onData); 136 | 137 | 138 | /// 139 | /// Receives from the symbol mini ticker. Updated every 500ms (limited by the Binance API). 140 | /// See https://binance-docs.github.io/apidocs/futures/en/#individual-symbol-mini-ticker-stream 141 | /// 142 | /// The symbol to monitor 143 | /// Your callback function. The any holds a SymbolMiniTickerStream 144 | /// 145 | MonitorToken monitorSymbol(const string& symbol, std::function onData); 146 | 147 | 148 | /// 149 | /// Receives from the Individual Symbol Book stream for a given symbol in real time. 150 | /// See https://binance-docs.github.io/apidocs/futures/en/#individual-symbol-book-ticker-streams 151 | /// 152 | /// The symbol 153 | /// Your callback function. The any holds a SymbolBookTickerStream 154 | /// 155 | MonitorToken monitorSymbolBookStream(const string& symbol, std::function onData); 156 | 157 | 158 | /// 159 | /// Receives from the Partial Book Depth Stream. 160 | /// See https://binance-docs.github.io/apidocs/futures/en/#partial-book-depth-streams. 161 | /// 162 | /// The symbol 163 | /// See docs 164 | /// See docs 165 | /// Your callback function. The any holds a BookDepthStream 166 | /// 167 | MonitorToken monitorPartialBookDepth(const string& symbol, const string& level, const string& interval, std::function onData); 168 | 169 | 170 | /// 171 | /// Receives from the Diff.Book Dept Stream. 172 | /// See https://binance-docs.github.io/apidocs/futures/en/#diff-book-depth-streams. 173 | /// 174 | /// See docs 175 | /// See docs 176 | /// Your callback function. The any holds a BookDepthStream 177 | /// 178 | MonitorToken monitorDiffBookDepth(const string& symbol, const string& interval, std::function onData); 179 | 180 | 181 | /// 182 | /// See See https://binance-docs.github.io/apidocs/futures/en/#long-short-ratio 183 | /// 184 | /// See docs 185 | /// See TakerBuySellVolume 186 | virtual TakerBuySellVolume takerBuySellVolume(map&& query); 187 | 188 | 189 | /// 190 | /// Becareful with the LIMIT value, it determines the weight of the API call and you want to only handle 191 | /// the data you require. Default LIMIT is 500. 192 | /// See https://binance-docs.github.io/apidocs/futures/en/#kline-candlestick-data 193 | /// 194 | /// 195 | /// See KlineCandlestick 196 | KlineCandlestick klines(map&& query); 197 | 198 | 199 | 200 | // --- account/useful/info 201 | 202 | /// 203 | /// See https://binance-docs.github.io/apidocs/futures/en/#account-information-v2-user_data 204 | /// 205 | /// 206 | AccountInformation accountInformation(); 207 | 208 | 209 | /// 210 | /// See https://binance-docs.github.io/apidocs/futures/en/#futures-account-balance-v2-user_data 211 | /// 212 | /// 213 | AccountBalance accountBalance(); 214 | 215 | 216 | /// 217 | /// See https://binance-docs.github.io/apidocs/futures/en/#exchange-information 218 | /// 219 | /// See ExchangeInfo 220 | ExchangeInfo exchangeInfo(); 221 | 222 | 223 | /// 224 | /// See https://binance-docs.github.io/apidocs/futures/en/#order-book 225 | /// 226 | /// See docs 227 | /// See OrderBook 228 | OrderBook orderBook(map&& query); 229 | 230 | 231 | 232 | // --- order management 233 | 234 | 235 | /// 236 | /// Create a new order synchronously. 237 | /// 238 | /// The NewOrderResult is returned which contains the response from the Rest call, 239 | /// see https://binance-docs.github.io/apidocs/futures/en/#new-order-trade. 240 | /// 241 | /// If the order is successful, the User Data Stream will be updated. 242 | /// 243 | /// Use the priceTransform() function to make the price value suitable. 244 | /// 245 | /// Order params, see link above. 246 | /// See NewOrderResult. 247 | NewOrderResult newOrder(map&& order) 248 | { 249 | return doNewOrder(std::move(order)).get(); 250 | } 251 | 252 | 253 | /// 254 | /// As newOrder() but async. 255 | /// 256 | /// 257 | /// The NewOrderResult in a task. 258 | pplx::task newOrderAsync(map&& order) 259 | { 260 | return doNewOrder(std::move(order)); 261 | } 262 | 263 | 264 | /// 265 | /// Allows up to a MAX of 5 orders in a single call. 266 | /// 267 | /// A vector of orders, i.e. a vector of the same map you'd create for newOrder() 268 | /// 269 | NewOrderBatchResult newOrderBatch(vector>&& order) 270 | { 271 | return doNewOrderBatch(std::move(order)).get(); 272 | } 273 | 274 | 275 | /// 276 | /// As newOrderBatch() but async. 277 | /// 278 | /// 279 | /// The NewOrderBatchResult in a task. 280 | pplx::task newOrderBatchAsync(vector>&& order) 281 | { 282 | return doNewOrderBatch(std::move(order)); 283 | } 284 | 285 | 286 | /// 287 | /// Returns all orders. What is returned is dependent on the status and order time, read: 288 | /// https://binance-docs.github.io/apidocs/futures/en/#all-orders-user_data 289 | /// 290 | /// 291 | /// 292 | AllOrdersResult allOrders(map&& query); 293 | 294 | 295 | /// 296 | /// Sends a cancel order message synchronously. 297 | /// See https://binance-docs.github.io/apidocs/futures/en/#cancel-order-trade 298 | /// 299 | /// 300 | CancelOrderResult cancelOrder(map&& order) 301 | { 302 | return doCancelOrder(std::move(order)).get(); 303 | } 304 | 305 | 306 | /// 307 | /// As cancelOrder() but asynchronously. 308 | /// 309 | /// 310 | /// The CancelOrderResult in a task. 311 | pplx::task cancelOrderAsync(map&& order) 312 | { 313 | return doCancelOrder(std::move(order)); 314 | } 315 | 316 | 317 | /// 318 | /// Close stream for the given token. 319 | /// 320 | /// 321 | void cancelMonitor(const MonitorToken& mt) 322 | { 323 | if (auto it = m_idToSession.find(mt.id); it != m_idToSession.end()) 324 | { 325 | disconnect(mt, true); 326 | } 327 | } 328 | 329 | 330 | /// 331 | /// Close all streams. 332 | /// 333 | void cancelMonitors() 334 | { 335 | disconnect(); 336 | } 337 | 338 | 339 | /// 340 | /// Set the API key(s). 341 | /// All calls require the API key. You only need secret key set if using a call which requires signing, such as newOrder. 342 | /// 343 | /// 344 | /// 345 | void setApiKeys(const ApiAccess access = {}) 346 | { 347 | m_apiAccess = access; 348 | } 349 | 350 | 351 | /// 352 | /// Sets the receive window. For defaults see member ReceiveWindowMap. 353 | /// Read about receive window in the "Timing Security" section at: https://binance-docs.github.io/apidocs/futures/en/#endpoint-security-type 354 | /// Note the receive window for RestCall::ListenKey has no affect 355 | /// 356 | /// The call for which this will set the time 357 | /// time in milliseconds 358 | void setReceiveWindow(const RestCall call, const std::chrono::milliseconds ms) 359 | { 360 | m_receiveWindowMap[call] = std::to_string(ms.count()); 361 | } 362 | 363 | MarketType marketType() const 364 | { 365 | return m_marketType; 366 | } 367 | 368 | 369 | private: 370 | 371 | constexpr bool mustConvertStringT() 372 | { 373 | return std::is_same_v == false; 374 | } 375 | 376 | 377 | pplx::task doNewOrder(map&& order) 378 | { 379 | try 380 | { 381 | auto handler = [](web::http::http_response response) 382 | { 383 | NewOrderResult result; 384 | 385 | auto json = response.extract_json().get(); 386 | 387 | getJsonValues(json, result.response, vector { "clientOrderId", "cumQty", "cumQuote", "executedQty", "orderId", "avgPrice", "origQty", "price", "reduceOnly", "side", "positionSide", "status", 388 | "stopPrice", "closePosition", "symbol", "timeInForce", "type", "origType", "activatePrice", "priceRate", "updateTime", "workingType", "priceProtect"}); 389 | 390 | return result; 391 | }; 392 | 393 | return sendRestRequest(RestCall::NewOrder, web::http::methods::POST, true, m_marketType, handler, receiveWindow(RestCall::NewOrder), std::move(order)); 394 | } 395 | catch (const pplx::task_canceled tc) 396 | { 397 | throw BfcppDisconnectException("newOrder"); 398 | } 399 | catch (const std::exception ex) 400 | { 401 | throw BfcppException(ex.what()); 402 | } 403 | } 404 | 405 | 406 | pplx::task doCancelOrder(map&& order) 407 | { 408 | try 409 | { 410 | auto handler = [](web::http::http_response response) 411 | { 412 | CancelOrderResult result; 413 | 414 | auto json = response.extract_json().get(); 415 | getJsonValues(json, result.response, vector {"clientOrderId", "cumQty", "cumQuote", "executedQty", "orderId", "origQty", "origType", "price", "reduceOnly", "side", "positionSide", 416 | "status", "stopPrice", "closePosition", "symbol", "timeInForce", "type", "activatePrice", "priceRate", "updateTime", "workingType", "priceProtect"}); 417 | 418 | return result; 419 | }; 420 | 421 | return sendRestRequest(RestCall::CancelOrder, web::http::methods::DEL, true, m_marketType, handler, receiveWindow(RestCall::CancelOrder), std::move(order)); 422 | } 423 | catch (const pplx::task_canceled tc) 424 | { 425 | throw BfcppDisconnectException("cancelOrder"); 426 | } 427 | catch (const std::exception ex) 428 | { 429 | throw BfcppException(ex.what()); 430 | } 431 | } 432 | 433 | 434 | pplx::task doNewOrderBatch(vector>&& orders) 435 | { 436 | try 437 | { 438 | auto handler = [](web::http::http_response response) 439 | { 440 | NewOrderBatchResult result; 441 | 442 | auto json = response.extract_json().get(); 443 | 444 | for (auto& order : json.as_array()) 445 | { 446 | map orderValues; 447 | getJsonValues(order, orderValues, vector { "clientOrderId", "cumQty", "cumQuote", "executedQty", "orderId", "avgPrice", "origQty", "price", "reduceOnly", "side", "positionSide", "status", 448 | "stopPrice", "closePosition", "symbol", "timeInForce", "type", "origType", "activatePrice", "priceRate", "updateTime", "workingType", "priceProtect"}); 449 | 450 | result.response.emplace_back(std::move(orderValues)); 451 | } 452 | 453 | return result; 454 | }; 455 | 456 | 457 | // convert the vector of orders to single JSON string and create the query string from that 458 | const static map NonStringTypes = { {"orderId", web::json::value::Number}, {"reduceOnly", web::json::value::Boolean}, 459 | {"updateTime", web::json::value::Number}, {"priceProtect", web::json::value::Boolean} 460 | }; 461 | 462 | web::json::value list = web::json::value::array(); 463 | size_t i = 0; 464 | 465 | for (auto& order : orders) 466 | { 467 | auto entry = list.object(); 468 | 469 | for (auto& pair : order) 470 | { 471 | auto key = utility::conversions::to_string_t(pair.first); 472 | 473 | if (auto typeEntry = NonStringTypes.find(pair.first); typeEntry == NonStringTypes.end()) 474 | { 475 | entry[key] = web::json::value::string(utility::conversions::to_string_t(pair.second)); 476 | } 477 | else 478 | { 479 | if (typeEntry->second == web::json::value::Number) 480 | { 481 | entry[key] = web::json::value::number(static_cast(std::stoll(utility::conversions::to_string_t(pair.second)))); // TODO confirm long long correct 482 | } 483 | else if (typeEntry->second == web::json::value::Boolean) 484 | { 485 | entry[key] = web::json::value::boolean(pair.second == "true" || pair.second == "TRUE"); 486 | } 487 | } 488 | } 489 | 490 | list[i++] = std::move(entry); 491 | } 492 | 493 | map query; 494 | query["batchOrders"] = utility::conversions::to_utf8string(web::http::uri::encode_data_string(list.serialize())); 495 | 496 | return sendRestRequest(RestCall::NewBatchOrder, web::http::methods::POST, true, m_marketType, handler, receiveWindow(RestCall::NewBatchOrder), std::move(query)); 497 | } 498 | catch (const pplx::task_canceled tc) 499 | { 500 | throw BfcppDisconnectException("doNewOrderBatch"); 501 | } 502 | catch (const std::exception ex) 503 | { 504 | throw BfcppException(ex.what()); 505 | } 506 | } 507 | 508 | 509 | MonitorToken doMonitorBookDepth(const string& symbol, const string& level, const string& interval, std::function onData); 510 | 511 | 512 | void onUserDataTimer() 513 | { 514 | auto request = createHttpRequest(web::http::methods::PUT, getApiPath(m_marketType, RestCall::ListenKey)); 515 | 516 | web::http::client::http_client client{ web::uri{utility::conversions::to_string_t(getApiUri(m_marketType))} }; 517 | client.request(std::move(request)).then([this](web::http::http_response response) 518 | { 519 | if (response.status_code() != web::http::status_codes::OK) 520 | { 521 | throw BfcppException("ERROR : keepalive for listen key failed"); 522 | } 523 | }).wait(); 524 | } 525 | 526 | 527 | void handleUserDataStream(shared_ptr session, std::function onData) 528 | { 529 | try 530 | { 531 | while (!session->getCancelToken().is_canceled()) 532 | { 533 | session->client.receive().then([=, token = session->getCancelToken()](pplx::task websocketInMessage) 534 | { 535 | if (!token.is_canceled()) 536 | { 537 | std::string strMsg; 538 | websocketInMessage.get().extract_string().then([=, &strMsg, cancelToken = session->getCancelToken()](pplx::task str_tsk) 539 | { 540 | if (!cancelToken.is_canceled()) 541 | { 542 | strMsg = str_tsk.get(); 543 | } 544 | }, token).wait(); 545 | 546 | 547 | if (!strMsg.empty()) 548 | { 549 | std::error_code errCode; 550 | if (auto json = web::json::value::parse(strMsg, errCode); errCode.value() == 0) 551 | { 552 | extractUsdFuturesUserData(session, std::move(json)); 553 | } 554 | } 555 | } 556 | }, session->getCancelToken()).wait(); 557 | } 558 | } 559 | catch (pplx::task_canceled) 560 | { 561 | throw BfcppDisconnectException(session->uri); 562 | } 563 | catch (std::exception ex) 564 | { 565 | throw; 566 | } 567 | } 568 | 569 | 570 | void extractUsdFuturesUserData(shared_ptr session, web::json::value&& jsonVal); 571 | 572 | 573 | void disconnect(const MonitorToken& mt, const bool deleteSession); 574 | void disconnect(); 575 | 576 | 577 | bool createListenKey(const MarketType marketType); 578 | 579 | 580 | shared_ptr connect(const string& uri) 581 | { 582 | auto session = std::make_shared(); 583 | session->uri = uri; 584 | 585 | try 586 | { 587 | web::uri wsUri(utility::conversions::to_string_t(uri)); 588 | session->client.connect(wsUri).then([&session] 589 | { 590 | session->connected = true; 591 | }).wait(); 592 | } 593 | catch (const std::exception ex) 594 | { 595 | throw BfcppException(ex.what()); 596 | } 597 | 598 | return session; 599 | } 600 | 601 | 602 | std::tuple> createMonitor(const string& uri, std::function)> handler) 603 | { 604 | std::tuple> tokenAndSession; 605 | 606 | if (shared_ptr session = connect(uri); session) 607 | { 608 | if (MonitorToken monitor = createReceiveTask(session, handler); monitor.isValid()) 609 | { 610 | session->id = monitor.id; 611 | 612 | m_sessions.push_back(session); 613 | m_idToSession[monitor.id] = session; 614 | 615 | tokenAndSession = std::make_tuple(monitor, session); 616 | } 617 | } 618 | 619 | return tokenAndSession; 620 | } 621 | 622 | 623 | MonitorToken createReceiveTask(shared_ptr session, std::function)> extractFunc) 624 | { 625 | MonitorToken monitorToken; 626 | 627 | auto token = session->getCancelToken(); 628 | monitorToken.id = m_monitorId++; 629 | 630 | session->receiveTask = pplx::create_task([session, token, extractFunc, mt = monitorToken.id, this] 631 | { 632 | try 633 | { 634 | while (!token.is_canceled()) 635 | { 636 | session->client.receive().then([=](pplx::task websocketInMessage) 637 | { 638 | if (!token.is_canceled()) 639 | { 640 | try 641 | { 642 | if (auto msg = websocketInMessage.get(); msg.message_type() == ws::client::websocket_message_type::text_message) 643 | { 644 | extractFunc(msg, session); 645 | } 646 | } 647 | catch (const std::exception ex) 648 | { 649 | throw BfcppException(ex.what()); 650 | } 651 | } 652 | }, token).wait(); 653 | } 654 | } 655 | catch (pplx::task_canceled) 656 | { 657 | throw BfcppDisconnectException(session->uri); 658 | } 659 | catch (const std::exception ex) 660 | { 661 | pplx::cancel_current_task(); 662 | } 663 | }, token); 664 | 665 | return monitorToken; 666 | } 667 | 668 | 669 | protected: 670 | 671 | string createQueryString(map&& queryValues, const RestCall call, const bool sign, const string& rcvWindow) 672 | { 673 | stringstream ss; 674 | 675 | // can leave a trailing '&' without borking the internets 676 | std::for_each(queryValues.begin(), queryValues.end(), [&ss](auto& it) 677 | { 678 | ss << std::move(it.first) << "=" << std::move(it.second) << "&"; // TODO doing a move() with operator<< here - any advantage? 679 | }); 680 | 681 | if (sign) 682 | { 683 | ss << "recvWindow=" << rcvWindow << "×tamp=" << getTimestamp(); 684 | 685 | string qs = ss.str(); 686 | return qs + "&signature=" + (createSignature(m_apiAccess.secretKey, qs)); 687 | } 688 | else 689 | { 690 | return ss.str(); 691 | } 692 | } 693 | 694 | 695 | web::http::http_request createHttpRequest(const web::http::method method, string uri) 696 | { 697 | web::http::http_request request{ method }; 698 | request.headers().add(utility::conversions::to_string_t(HeaderApiKeyName), utility::conversions::to_string_t(m_apiAccess.apiKey)); 699 | request.headers().add(utility::conversions::to_string_t(ContentTypeName), utility::conversions::to_string_t("application/json")); 700 | request.headers().add(utility::conversions::to_string_t(ClientSDKVersionName), utility::conversions::to_string_t("binance_futures_cpp")); 701 | request.set_request_uri(web::uri{ utility::conversions::to_string_t(uri) }); 702 | return request; 703 | } 704 | 705 | 706 | template 707 | pplx::task sendRestRequest(const RestCall call, const web::http::method method, const bool sign, const MarketType mt, std::function handler, const string& rcvWindow, map&& query = {}) 708 | { 709 | try 710 | { 711 | string queryString{ createQueryString(std::move(query), call, true, rcvWindow) }; 712 | 713 | auto request = createHttpRequest(method, getApiPath(mt, call) + "?" + queryString); 714 | 715 | web::http::client::http_client client{ web::uri { utility::conversions::to_string_t(getApiUri(mt)) } }; 716 | 717 | return client.request(std::move(request)).then([handler, this](web::http::http_response response) 718 | { 719 | if (response.status_code() == web::http::status_codes::OK) 720 | { 721 | return handler(response); 722 | } 723 | else 724 | { 725 | return createInvalidRestResult(handleRestCallError(response)); 726 | } 727 | }); 728 | } 729 | catch (const pplx::task_canceled tc) 730 | { 731 | throw; 732 | } 733 | catch (const std::exception ex) 734 | { 735 | throw; 736 | } 737 | } 738 | 739 | 740 | string handleRestCallError(web::http::http_response& response) 741 | { 742 | auto isJson = response.headers()[utility::conversions::to_string_t("content-type")].find(utility::conversions::to_string_t("json")) != utility::string_t::npos; 743 | return isJson ? utility::conversions::to_utf8string(response.extract_json().get().serialize()) : 744 | utility::conversions::to_utf8string(response.extract_string().get()); 745 | } 746 | 747 | private: 748 | MarketType m_marketType; 749 | 750 | vector> m_sessions; 751 | map> m_idToSession; 752 | 753 | 754 | std::atomic_size_t m_monitorId; 755 | string m_exchangeBaseUri; 756 | std::atomic_bool m_connected; 757 | std::atomic_bool m_running; 758 | string m_listenKey; 759 | ApiAccess m_apiAccess; 760 | 761 | IntervalTimer m_userDataStreamTimer; 762 | map m_receiveWindowMap; 763 | }; 764 | 765 | 766 | 767 | /// 768 | /// Uses Binance's Test Net market. Most endpoints are available, including data streams for orders. 769 | /// See: https://testnet.binancefuture.com/en/futures/BTC_USDT 770 | /// To use the TestNet you must: 771 | /// 1) Create/login to an account on https://testnet.binancefuture.com/en/futures/BTC_USDT 772 | /// 2) Unlike the 'real' accounts, there's no API Management page, instead there's an "API Key" section at the bottom of the trading page, to the right of Positions, Open Orders, etc 773 | /// 774 | class UsdFuturesTestMarket : public UsdFuturesMarket 775 | { 776 | public: 777 | UsdFuturesTestMarket(const ApiAccess& access = {}) : UsdFuturesMarket(MarketType::FuturesTest, TestFuturestWebSockUri, access) 778 | { 779 | 780 | } 781 | 782 | virtual ~UsdFuturesTestMarket() 783 | { 784 | } 785 | 786 | 787 | public: 788 | virtual TakerBuySellVolume takerBuySellVolume(map&& query) 789 | { 790 | throw BfcppException("Function unavailable on Testnet"); 791 | } 792 | }; 793 | 794 | 795 | 796 | class UsdFuturesTestMarketPerfomance : public UsdFuturesTestMarket 797 | { 798 | public: 799 | UsdFuturesTestMarketPerfomance(const ApiAccess& access) : UsdFuturesTestMarket(access) 800 | { 801 | 802 | } 803 | 804 | virtual ~UsdFuturesTestMarketPerfomance() 805 | { 806 | } 807 | 808 | 809 | NewOrderPerformanceResult newOrderPerfomanceCheck(map&& order) 810 | { 811 | return doNewOrderPerfomanceCheck(std::move(order)).get(); 812 | } 813 | 814 | 815 | pplx::task newOrderPerfomanceCheckAsync(map&& order) 816 | { 817 | return doNewOrderPerfomanceCheck(std::move(order)); 818 | } 819 | 820 | 821 | NewOrderBatchPerformanceResult newOrderBatchPerfomanceCheck(vector>&& orders) 822 | { 823 | return doNewOrderBatchPerfomanceCheck(std::move(orders)).get(); 824 | } 825 | 826 | pplx::task newOrderBatchPerfomanceCheckAsync(vector>&& orders) 827 | { 828 | return doNewOrderBatchPerfomanceCheck(std::move(orders)); 829 | } 830 | 831 | 832 | private: 833 | template 834 | pplx::task sendRestRequestPerformanceCheck(const RestCall call, const web::http::method method, const bool sign, const MarketType mt, std::function handler, const string& rcvWindow, map&& query = {}) 835 | { 836 | try 837 | { 838 | auto start = std::chrono::high_resolution_clock::now(); 839 | 840 | string queryString{ createQueryString(std::move(query), call, true, rcvWindow) }; 841 | 842 | auto request = createHttpRequest(method, getApiPath(mt, call) + "?" + queryString); 843 | 844 | web::http::client::http_client client{ web::uri { utility::conversions::to_string_t(getApiUri(mt)) } }; 845 | 846 | auto requestSent = std::chrono::high_resolution_clock::now(); 847 | return client.request(std::move(request)).then([handler, start, requestSent, this](web::http::http_response response) 848 | { 849 | try 850 | { 851 | auto restCallTime = std::chrono::high_resolution_clock::now() - requestSent; 852 | 853 | if (response.status_code() == web::http::status_codes::OK) 854 | { 855 | auto handlerCalled = std::chrono::high_resolution_clock::now(); 856 | auto result = handler(response); 857 | auto handlerDone = std::chrono::high_resolution_clock::now(); 858 | 859 | result.restQueryBuild = requestSent - start; 860 | result.restResponseHandler = handlerDone - handlerCalled; 861 | result.bfcppTotalProcess = result.restQueryBuild + result.restResponseHandler; 862 | result.restApiCall = restCallTime; 863 | return result; 864 | } 865 | else 866 | { 867 | ResultT result{}; 868 | result.bfcppTotalProcess = std::chrono::high_resolution_clock::now() - start; 869 | result.restApiCall = restCallTime; 870 | result.valid(false, handleRestCallError(response)); 871 | return result; 872 | } 873 | } 874 | catch (const std::exception ex) 875 | { 876 | throw BfcppException(ex.what()); 877 | } 878 | }); 879 | } 880 | catch (const pplx::task_canceled tc) 881 | { 882 | throw BfcppException("Receive task cancelled: " + string{ tc.what() }); 883 | } 884 | catch (const std::exception ex) 885 | { 886 | throw BfcppException(ex.what()); 887 | } 888 | } 889 | 890 | 891 | pplx::task doNewOrderPerfomanceCheck(map&& order) 892 | { 893 | try 894 | { 895 | Clock::time_point handlerStart, handlerStop; 896 | 897 | auto handler = [&handlerStart, &handlerStop](web::http::http_response response) 898 | { 899 | handlerStart = Clock::now(); 900 | 901 | NewOrderPerformanceResult result; 902 | 903 | auto json = response.extract_json().get(); 904 | 905 | getJsonValues(json, result.response, vector { "clientOrderId", "cumQty", "cumQuote", "executedQty", "orderId", "avgPrice", "origQty", "price", "reduceOnly", "side", "positionSide", "status", 906 | "stopPrice", "closePosition", "symbol", "timeInForce", "type", "origType", "activatePrice", "priceRate", "updateTime", "workingType", "priceProtect"}); 907 | 908 | return result; 909 | }; 910 | 911 | return sendRestRequestPerformanceCheck(RestCall::NewOrder, web::http::methods::POST, true, marketType(), handler, receiveWindow(RestCall::NewOrder), std::move(order)); 912 | } 913 | catch (const pplx::task_canceled tc) 914 | { 915 | throw BfcppDisconnectException("newOrder"); 916 | } 917 | catch (const std::exception ex) 918 | { 919 | throw BfcppException(ex.what()); 920 | } 921 | } 922 | 923 | 924 | 925 | pplx::task doNewOrderBatchPerfomanceCheck(vector>&& orders) 926 | { 927 | try 928 | { 929 | Clock::time_point handlerStart, handlerStop; 930 | 931 | auto handler = [&handlerStart, &handlerStop](web::http::http_response response) 932 | { 933 | handlerStart = Clock::now(); 934 | 935 | NewOrderBatchPerformanceResult result; 936 | 937 | auto json = response.extract_json().get(); 938 | 939 | auto& jsonArray = json.as_array(); 940 | for (auto& order : jsonArray) 941 | { 942 | map orderValues; 943 | getJsonValues(order, orderValues, vector { "clientOrderId", "cumQty", "cumQuote", "executedQty", "orderId", "avgPrice", "origQty", "price", "reduceOnly", "side", "positionSide", "status", 944 | "stopPrice", "closePosition", "symbol", "timeInForce", "type", "origType", "activatePrice", "priceRate", "updateTime", "workingType", "priceProtect"}); 945 | 946 | result.response.emplace_back(std::move(orderValues)); 947 | } 948 | 949 | return result; 950 | }; 951 | 952 | // convert the vector of orders to single JSON string and create the query string from that 953 | const static map NonStringTypes = { {"orderId", web::json::value::Number}, {"reduceOnly", web::json::value::Boolean}, 954 | {"updateTime", web::json::value::Number}, {"priceProtect", web::json::value::Boolean}}; 955 | 956 | web::json::value list = web::json::value::array(); 957 | size_t i = 0; 958 | 959 | for (auto& order : orders) 960 | { 961 | auto entry = list.object(); 962 | 963 | for (auto& pair : order) 964 | { 965 | auto key = utility::conversions::to_string_t(pair.first); 966 | 967 | if (auto typeEntry = NonStringTypes.find(pair.first); typeEntry == NonStringTypes.end()) 968 | { 969 | entry[key] = web::json::value::string(utility::conversions::to_string_t(pair.second)); 970 | } 971 | else 972 | { 973 | if (typeEntry->second == web::json::value::Number) 974 | { 975 | entry[key] = web::json::value::number(static_cast(std::stoll(utility::conversions::to_string_t(pair.second)))); // TODO confirm long long correct 976 | } 977 | else if (typeEntry->second == web::json::value::Boolean) 978 | { 979 | entry[key] = web::json::value::boolean(pair.second == "true" || pair.second == "TRUE"); 980 | } 981 | } 982 | } 983 | 984 | list[i++] = std::move(entry); 985 | } 986 | 987 | map query; 988 | query["batchOrders"] = utility::conversions::to_utf8string(web::http::uri::encode_data_string(list.serialize())); 989 | 990 | return sendRestRequestPerformanceCheck(RestCall::NewBatchOrder, web::http::methods::POST, true, marketType(), handler, receiveWindow(RestCall::NewBatchOrder), std::move(query)); 991 | } 992 | catch (const pplx::task_canceled tc) 993 | { 994 | throw BfcppDisconnectException("newOrder"); 995 | } 996 | catch (const std::exception ex) 997 | { 998 | throw BfcppException(ex.what()); 999 | } 1000 | } 1001 | }; 1002 | } 1003 | 1004 | #endif 1005 | -------------------------------------------------------------------------------- /bfcpp/bfcpplib/IntervalTimer.cpp: -------------------------------------------------------------------------------- 1 | #include "IntervalTimer.hpp" 2 | 3 | 4 | namespace bfcpp 5 | { 6 | IntervalTimer::IntervalTimer() : m_running(false), m_period(std::chrono::milliseconds{ 100 }) 7 | { 8 | 9 | } 10 | 11 | IntervalTimer::IntervalTimer(const std::chrono::milliseconds period) : m_period(period), m_running(false) 12 | { 13 | 14 | } 15 | 16 | 17 | IntervalTimer::IntervalTimer(const std::chrono::seconds period) : m_period(period), m_running(false) 18 | { 19 | 20 | } 21 | 22 | 23 | IntervalTimer::~IntervalTimer() 24 | { 25 | stop(); 26 | } 27 | 28 | 29 | void IntervalTimer::start(std::function callback, const std::chrono::milliseconds period) 30 | { 31 | m_period = period; 32 | start(callback); 33 | } 34 | 35 | 36 | void IntervalTimer::start(std::function callback) 37 | { 38 | m_callback = callback; 39 | m_running = true; 40 | 41 | m_future = std::async(std::launch::async, [this] 42 | { 43 | using namespace std::chrono_literals; 44 | const std::chrono::milliseconds CheckPeriod = 100ms; 45 | 46 | auto time = std::chrono::steady_clock::now(); 47 | auto triggerTime = time + m_period; 48 | 49 | while (m_running.load()) 50 | { 51 | std::this_thread::sleep_for(std::min(CheckPeriod, m_period)); 52 | 53 | time = std::chrono::steady_clock::now(); 54 | if (time >= triggerTime) 55 | { 56 | triggerTime += m_period; 57 | 58 | try 59 | { 60 | m_callback(); 61 | } 62 | catch (...) 63 | { 64 | // ignore callers' issues 65 | } 66 | } 67 | } 68 | }); 69 | } 70 | 71 | 72 | void IntervalTimer::stop() 73 | { 74 | m_running = false; 75 | 76 | if (m_future.valid()) 77 | { 78 | m_future.wait(); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /bfcpp/bfcpplib/IntervalTimer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace bfcpp 9 | { 10 | class IntervalTimer 11 | { 12 | public: 13 | IntervalTimer(); 14 | IntervalTimer(const std::chrono::milliseconds period); 15 | IntervalTimer(const std::chrono::seconds period); 16 | 17 | ~IntervalTimer(); 18 | 19 | void start(std::function callback); 20 | void start(std::function callback, const std::chrono::milliseconds period); 21 | 22 | void stop(); 23 | 24 | private: 25 | std::chrono::milliseconds m_period; 26 | std::future m_future; 27 | std::atomic_bool m_running; 28 | std::function m_callback; 29 | }; 30 | } -------------------------------------------------------------------------------- /bfcpp/bfcpplib/bfcppCommon.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __BINANCE_COMMON_HPP 2 | #define __BINANCE_COMMON_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | 23 | namespace bfcpp 24 | { 25 | using std::shared_ptr; 26 | using std::vector; 27 | using std::string; 28 | using std::map; 29 | using std::future; 30 | using std::pair; 31 | using std::set; 32 | using std::stringstream; 33 | 34 | namespace fs = std::filesystem; 35 | namespace ws = web::websockets; 36 | namespace json = web::json; 37 | 38 | 39 | typedef size_t MonitorTokenId; 40 | typedef std::chrono::system_clock Clock; 41 | typedef map> JsonKeys; 42 | typedef std::string MarketStringType; 43 | 44 | 45 | 46 | #define BFCPP_FUNCTION std::string {__func__} 47 | #define BFCPP_FUNCTION_MSG(msg) std::string {__func__} + msg 48 | 49 | 50 | enum class RestCall 51 | { 52 | None, 53 | NewOrder, 54 | ListenKey, 55 | CancelOrder, 56 | AllOrders, 57 | AccountInfo, 58 | AccountBalance, 59 | TakerBuySellVolume, 60 | KlineCandles, 61 | Ping, 62 | NewBatchOrder, 63 | ExchangeInfo, 64 | OrderBook 65 | }; 66 | 67 | 68 | enum class StreamCall 69 | { 70 | None, 71 | Candlesticks, 72 | MarkPrice, 73 | SymbolMiniTicker, 74 | SymbolBookTicker, 75 | AllMarketMiniTicker, 76 | BookDepth 77 | }; 78 | 79 | enum class MarketType 80 | { 81 | Futures, 82 | FuturesTest 83 | }; 84 | 85 | enum class OrderStatus 86 | { 87 | None, 88 | New, 89 | PartiallyFilled, 90 | Filled, 91 | Cancelled, 92 | Rejected, 93 | Expired 94 | }; 95 | 96 | 97 | const string FuturestWebSockUri = "wss://fstream.binance.com"; 98 | const string TestFuturestWebSockUri = "wss://stream.binancefuture.com"; 99 | 100 | const string UsdFuturesRestUri = "https://fapi.binance.com"; 101 | const string TestUsdFuturestRestUri = "https://testnet.binancefuture.com"; 102 | 103 | const string HeaderApiKeyName = "X-MBX-APIKEY"; 104 | const string ListenKeyName = "listenKey"; 105 | const string ClientSDKVersionName = "client_SDK_Version"; 106 | const string ContentTypeName = "Content-Type"; 107 | 108 | 109 | inline static const map OrderStatusMap = 110 | { 111 | {"None", OrderStatus::None}, 112 | {"NEW", OrderStatus::New}, 113 | {"PARTIALLY_FILLED", OrderStatus::PartiallyFilled}, 114 | {"FILLED", OrderStatus::Filled}, 115 | {"CANCELED", OrderStatus::Cancelled}, 116 | {"REJECTED", OrderStatus::Rejected}, 117 | {"EXPIRED", OrderStatus::Expired} 118 | }; 119 | 120 | 121 | inline static const map PathMap = 122 | { 123 | {RestCall::NewOrder, "/fapi/v1/order"}, 124 | {RestCall::ListenKey, "/fapi/v1/listenKey"}, 125 | {RestCall::CancelOrder, "/fapi/v1/order"}, 126 | {RestCall::AllOrders, "/fapi/v1/allOrders"}, 127 | {RestCall::AccountInfo, "/fapi/v2/account"}, 128 | {RestCall::AccountBalance, "/fapi/v2/balance"}, 129 | {RestCall::TakerBuySellVolume, "/futures/data/takerlongshortRatio"}, 130 | {RestCall::KlineCandles, "/fapi/v1/klines"}, 131 | {RestCall::Ping, "/fapi/v1/ping"}, 132 | {RestCall::NewBatchOrder, "/fapi/v1/batchOrders"}, 133 | {RestCall::ExchangeInfo, "/fapi/v1/exchangeInfo"}, 134 | {RestCall::OrderBook, "/fapi/v1/depth"} 135 | }; 136 | 137 | 138 | /// 139 | /// Struct used in some of the monitor functions to store a direct key/value pair. 140 | /// 141 | 142 | /* 143 | struct BinanceKeyValueData 144 | { 145 | BinanceKeyValueData() = default; 146 | 147 | BinanceKeyValueData(map&& vals) : values(vals) 148 | { 149 | 150 | } 151 | 152 | map values; 153 | }; 154 | 155 | 156 | 157 | /// 158 | /// Used in the monitor functions where a simple key/value pair is not suitable. 159 | /// 160 | /// NOTE: when handling price data, such as markPrice, you should sort the inner map by 'E', the event time. 161 | /// 162 | /// The top level is typically the symbol, with the value being that symbol's relevant data, i.e.: 163 | /// 164 | /// ["ZENUSDT", ["E", "1613317084088"], ["c", "50.54400000"], ["e", "2hrMiniTicker"], ... etc] 165 | /// 166 | /// 167 | struct BinanceKeyMultiValueData 168 | { 169 | BinanceKeyMultiValueData() = default; 170 | 171 | BinanceKeyMultiValueData(map>&& vals) : values(std::move(vals)) 172 | { 173 | 174 | } 175 | 176 | map> values; 177 | }; 178 | */ 179 | 180 | 181 | /// 182 | /// Contains data from the USD-M Futures stream. 183 | /// 184 | /// First check the type which determines member is populated: 185 | /// EventType::MarginCall - mc 186 | /// EventType::OrderUpdate - ou 187 | /// EventType::AccountUpdate - au 188 | /// 189 | /// See https://binance-docs.github.io/apidocs/futures/en/#user-data-streams 190 | /// 191 | struct UsdFutureUserData 192 | { 193 | enum class EventType { Unknown, MarginCall, OrderUpdate, AccountUpdate, DataStreamExpired }; 194 | 195 | UsdFutureUserData() = delete; 196 | 197 | UsdFutureUserData(const EventType t) : type(t) 198 | { 199 | 200 | } 201 | 202 | struct MarginCall 203 | { 204 | map data; 205 | map> positions; 206 | } mc; 207 | 208 | struct OrderUpdate 209 | { 210 | map data; 211 | map> orders; 212 | } ou; 213 | 214 | struct AccountUpdate 215 | { 216 | map data; 217 | string reason; 218 | vector> balances; 219 | vector> positions; 220 | } au; 221 | 222 | EventType type; 223 | }; 224 | 225 | 226 | struct RestResult 227 | { 228 | bool valid() const { return m_valid; } 229 | 230 | void valid(const bool v, string&& msg = {}) 231 | { 232 | m_valid = v; 233 | m_msg.assign(std::move(msg)); 234 | } 235 | 236 | const string& msg() const { return m_msg; } 237 | 238 | 239 | protected: 240 | RestResult(RestCall rc, bool valid = true) : m_rc(rc), m_valid{ valid } {} 241 | virtual ~RestResult(){} 242 | 243 | 244 | RestCall m_rc; 245 | bool m_valid; 246 | string m_msg; 247 | }; 248 | 249 | 250 | 251 | template 252 | inline RestResultT createInvalidRestResult(string&& msg) 253 | { 254 | RestResultT r{}; 255 | r.valid(false, std::move(msg)); 256 | return r; 257 | } 258 | 259 | 260 | /// 261 | /// Returned by newOrder(). 262 | /// The key/values in response are here: https://binance-docs.github.io/apidocs/testnet/en/#new-order-trade 263 | /// 264 | struct NewOrderResult : public RestResult 265 | { 266 | NewOrderResult() : RestResult(RestCall::NewOrder) {} 267 | 268 | NewOrderResult(map&& data) : RestResult(RestCall::NewOrder), response(data) 269 | { 270 | } 271 | 272 | map response; 273 | }; 274 | 275 | 276 | struct NewOrderBatchResult : public RestResult 277 | { 278 | NewOrderBatchResult() : RestResult(RestCall::NewBatchOrder) {} 279 | 280 | NewOrderBatchResult(vector>&& data) : RestResult(RestCall::NewBatchOrder), response(data) 281 | { 282 | } 283 | 284 | vector> response; 285 | }; 286 | 287 | 288 | struct NewOrderPerformanceResult : public NewOrderResult 289 | { 290 | std::chrono::high_resolution_clock::duration restApiCall; 291 | std::chrono::high_resolution_clock::duration restQueryBuild; 292 | std::chrono::high_resolution_clock::duration restResponseHandler; 293 | std::chrono::high_resolution_clock::duration bfcppTotalProcess; 294 | std::chrono::high_resolution_clock::duration total ; 295 | }; 296 | 297 | 298 | struct NewOrderBatchPerformanceResult : public NewOrderBatchResult 299 | { 300 | std::chrono::high_resolution_clock::duration restApiCall; 301 | std::chrono::high_resolution_clock::duration restQueryBuild; 302 | std::chrono::high_resolution_clock::duration restResponseHandler; 303 | std::chrono::high_resolution_clock::duration bfcppTotalProcess; 304 | std::chrono::high_resolution_clock::duration total; 305 | }; 306 | 307 | 308 | /// 309 | /// See https://binance-docs.github.io/apidocs/futures/en/#cancel-order-trade 310 | /// 311 | struct CancelOrderResult : public RestResult 312 | { 313 | CancelOrderResult() : RestResult(RestCall::CancelOrder) {} 314 | 315 | CancelOrderResult(map&& data) :RestResult(RestCall::CancelOrder), response(data) 316 | { 317 | 318 | } 319 | 320 | map response; 321 | }; 322 | 323 | 324 | /// 325 | /// See https://binance-docs.github.io/apidocs/futures/en/#all-orders-user_data 326 | /// 327 | struct AllOrdersResult : public RestResult 328 | { 329 | AllOrdersResult() : RestResult(RestCall::AllOrders) {} 330 | 331 | vector> response; 332 | }; 333 | 334 | 335 | /// 336 | /// See https://binance-docs.github.io/apidocs/futures/en/#account-information-v2-user_data 337 | /// 338 | struct AccountInformation : public RestResult 339 | { 340 | AccountInformation() : RestResult(RestCall::AccountInfo) {} 341 | 342 | map data; 343 | vector> assets; 344 | vector> positions; 345 | }; 346 | 347 | 348 | /// 349 | /// See https://binance-docs.github.io/apidocs/futures/en/#futures-account-balance-v2-user_data 350 | /// 351 | struct AccountBalance : public RestResult 352 | { 353 | AccountBalance() : RestResult(RestCall::AccountBalance) {} 354 | 355 | vector> balances; 356 | }; 357 | 358 | 359 | /// 360 | /// See https://binance-docs.github.io/apidocs/futures/en/#long-short-ratio 361 | /// 362 | struct TakerBuySellVolume : public RestResult 363 | { 364 | TakerBuySellVolume() : RestResult(RestCall::TakerBuySellVolume) {} 365 | 366 | vector> response; 367 | }; 368 | 369 | 370 | /// 371 | /// See https://binance-docs.github.io/apidocs/futures/en/#kline-candlestick-data 372 | /// 373 | struct KlineCandlestick : public RestResult 374 | { 375 | KlineCandlestick() : RestResult(RestCall::KlineCandles) {} 376 | 377 | vector> response; 378 | }; 379 | 380 | 381 | struct ListenKey : public RestResult 382 | { 383 | ListenKey() : RestResult(RestCall::ListenKey) {} 384 | 385 | string listenKey; 386 | }; 387 | 388 | 389 | struct ExchangeInfo : public RestResult 390 | { 391 | ExchangeInfo() : RestResult(RestCall::ExchangeInfo) {} 392 | 393 | struct Symbol 394 | { 395 | map data; // top level key/value pairs with in teh symbol, i.e. "status", "pricePrecision", etc 396 | vector> filters; 397 | vector orderTypes; 398 | vector timeInForce; 399 | vector underlyingSubType; 400 | }; 401 | 402 | string timezone; 403 | string serverTime; 404 | vector> exchangeFilters; 405 | vector> rateLimits; 406 | vector symbols; 407 | }; 408 | 409 | 410 | struct OrderBook : public RestResult 411 | { 412 | OrderBook() : RestResult(RestCall::OrderBook) {} 413 | 414 | string transactionTime; 415 | string messageOutputTime; 416 | string lastUpdateId; 417 | vector> bids; 418 | vector> asks; 419 | }; 420 | 421 | 422 | // Data used in Monitor callbacks 423 | 424 | struct StreamCallbackData 425 | { 426 | StreamCallbackData(const StreamCall sc) : call(sc) 427 | { 428 | 429 | } 430 | 431 | StreamCall call; 432 | }; 433 | 434 | struct CandleStream : public StreamCallbackData 435 | { 436 | CandleStream() : StreamCallbackData(StreamCall::Candlesticks) 437 | { 438 | } 439 | 440 | string eventTime; 441 | string symbol; 442 | map candle; 443 | }; 444 | 445 | struct MarkPriceStream : public StreamCallbackData 446 | { 447 | MarkPriceStream() : StreamCallbackData(StreamCall::MarkPrice) 448 | { 449 | } 450 | 451 | vector> prices; 452 | }; 453 | 454 | 455 | struct SymbolMiniTickerStream : public StreamCallbackData 456 | { 457 | SymbolMiniTickerStream() : StreamCallbackData(StreamCall::SymbolMiniTicker) 458 | { 459 | } 460 | 461 | map data; 462 | }; 463 | 464 | 465 | struct SymbolBookTickerStream : public StreamCallbackData 466 | { 467 | SymbolBookTickerStream() : StreamCallbackData(StreamCall::SymbolBookTicker) 468 | { 469 | } 470 | 471 | map data; 472 | }; 473 | 474 | 475 | struct AllMarketMiniTickerStream : public StreamCallbackData 476 | { 477 | AllMarketMiniTickerStream() : StreamCallbackData(StreamCall::AllMarketMiniTicker) 478 | { 479 | } 480 | 481 | vector> data; 482 | }; 483 | 484 | 485 | struct BookDepthStream : public StreamCallbackData 486 | { 487 | BookDepthStream() : StreamCallbackData(StreamCall::BookDepth) 488 | { 489 | } 490 | 491 | string eventTime; 492 | string transactionTime; 493 | string symbol; 494 | string firstUpdateId; 495 | string finalUpdateId; 496 | string previousFinalUpdateId; // 'pu' field 497 | vector> bids; 498 | vector> asks; 499 | }; 500 | 501 | 502 | /// 503 | /// Returned by monitor functions, containing an ID for use with cancelMonitor() to close this stream. 504 | /// 505 | struct MonitorToken 506 | { 507 | MonitorToken() : id (0) {} 508 | MonitorToken(MonitorTokenId mId) : id(mId) {} 509 | 510 | MonitorTokenId id; 511 | 512 | bool isValid() const { return id > 0; } 513 | }; 514 | 515 | 516 | /// 517 | /// Holds data required for API access. 518 | /// You require an API key, but the API is only require for certain features. 519 | /// 520 | struct ApiAccess 521 | { 522 | ApiAccess() = default; 523 | ApiAccess(const string& api, const string& secret = {}) : apiKey(api), secretKey(secret) 524 | { 525 | 526 | } 527 | ApiAccess(string&& api, string&& secret = {}) : apiKey(api), secretKey(secret) 528 | { 529 | 530 | } 531 | 532 | string apiKey; 533 | string secretKey; 534 | }; 535 | 536 | 537 | class BfcppException : public std::runtime_error 538 | { 539 | public: 540 | BfcppException(const string& msg) : runtime_error(msg) 541 | { 542 | 543 | } 544 | 545 | BfcppException(string&& msg) : runtime_error(msg) 546 | { 547 | 548 | } 549 | }; 550 | 551 | class BfcppDisconnectException : public std::runtime_error 552 | { 553 | public: 554 | BfcppDisconnectException(const string& source) : runtime_error("Disconnect: "+source), m_source(source) 555 | { 556 | 557 | } 558 | 559 | const string& source() const { return m_source; } 560 | 561 | private: 562 | string m_source; 563 | }; 564 | 565 | 566 | 567 | struct WebSocketSession 568 | { 569 | private: 570 | WebSocketSession(const WebSocketSession&) = delete; 571 | WebSocketSession& operator=(const WebSocketSession&) = delete; 572 | 573 | 574 | public: 575 | WebSocketSession() : connected(false), id(0), cancelToken(cancelTokenSource.get_token()) 576 | { 577 | 578 | } 579 | 580 | WebSocketSession(WebSocketSession&& other) = default; 581 | 582 | 583 | // end point 584 | string uri; 585 | 586 | // client for the websocket 587 | ws::client::websocket_client client; 588 | // the task which receives the websocket messages 589 | pplx::task receiveTask; 590 | 591 | // callback functions for user functions 592 | std::function callback; 593 | 594 | // the monitor id. The MonitorToken is returned to the caller which can be used to cancel the monitor 595 | MonitorTokenId id; 596 | std::atomic_bool connected; 597 | 598 | void cancel() 599 | { 600 | cancelTokenSource.cancel(); 601 | } 602 | 603 | 604 | pplx::cancellation_token getCancelToken() 605 | { 606 | return cancelToken; 607 | } 608 | 609 | 610 | 611 | private: 612 | pplx::cancellation_token_source cancelTokenSource; 613 | pplx::cancellation_token cancelToken; 614 | 615 | }; 616 | 617 | 618 | 619 | template 620 | string toString(const T a_value, const int n = 6) 621 | { 622 | std::ostringstream out; 623 | out.precision(n); 624 | out << std::fixed << a_value; 625 | return out.str(); 626 | } 627 | 628 | 629 | inline string jsonValueToString(const web::json::value& jsonVal) 630 | { 631 | switch (auto t = jsonVal.type(); t) 632 | { 633 | #if __cplusplus > 201703L 634 | [[likely]] 635 | #endif 636 | case json::value::value_type::String: 637 | return utility::conversions::to_utf8string(jsonVal.as_string()); 638 | break; 639 | 640 | 641 | case json::value::value_type::Number: 642 | return std::to_string(jsonVal.as_number().to_int64()); 643 | break; 644 | 645 | 646 | #if __cplusplus > 201703L 647 | [[unlikely]] 648 | #endif 649 | case json::value::value_type::Boolean: 650 | return jsonVal.as_bool() ? utility::conversions::to_utf8string("true") : utility::conversions::to_utf8string("false"); 651 | break; 652 | 653 | default: 654 | throw std::runtime_error("No handler for JSON type: " + std::to_string(static_cast(t))); 655 | break; 656 | } 657 | } 658 | 659 | 660 | inline void getJsonValues(const web::json::value& jsonVal, map& values, const string& key) 661 | { 662 | auto keyJsonString = utility::conversions::to_string_t(key); 663 | if (jsonVal.has_field(keyJsonString)) 664 | { 665 | values.emplace(key, jsonValueToString(jsonVal.at(keyJsonString))); 666 | } 667 | } 668 | 669 | 670 | inline void getJsonValues(const web::json::value& jsonVal, map& values, string&& key) 671 | { 672 | auto keyJsonString = utility::conversions::to_string_t(key); 673 | if (jsonVal.has_field(keyJsonString)) 674 | { 675 | values.emplace(std::move(key), jsonValueToString(jsonVal.at(keyJsonString))); 676 | } 677 | } 678 | 679 | 680 | 681 | inline void getJsonValues(const web::json::object& jsonObj, map& values, const set& keys) 682 | { 683 | for (const auto& v : jsonObj) 684 | { 685 | if (keys.find(utility::conversions::to_utf8string(v.first)) != keys.cend()) 686 | { 687 | values.emplace(utility::conversions::to_utf8string(v.first), jsonValueToString(v.second)); 688 | } 689 | } 690 | } 691 | 692 | 693 | inline void getJsonValues(const web::json::value& jsonVal, map& values, const vector& keys) 694 | { 695 | for (const auto& k : keys) 696 | { 697 | getJsonValues(jsonVal, values, k); 698 | } 699 | } 700 | 701 | 702 | inline void getJsonValues(const web::json::value& jsonVal, map& values, vector&& keys) 703 | { 704 | for (auto&& k : keys) 705 | { 706 | getJsonValues(jsonVal, values, std::move(k)); 707 | } 708 | } 709 | 710 | 711 | 712 | inline string getApiUri(const MarketType mt) 713 | { 714 | switch (mt) 715 | { 716 | case MarketType::Futures: 717 | return UsdFuturesRestUri; 718 | break; 719 | 720 | case MarketType::FuturesTest: 721 | return TestUsdFuturestRestUri; 722 | break; 723 | 724 | default: 725 | throw std::runtime_error("Unknown market type"); 726 | break; 727 | } 728 | } 729 | 730 | 731 | inline string getApiPath(const MarketType mt, const RestCall call) 732 | { 733 | switch (mt) 734 | { 735 | case MarketType::Futures: 736 | case MarketType::FuturesTest: 737 | return PathMap.at(call); 738 | break; 739 | 740 | default: 741 | throw std::runtime_error("Unknown market type"); 742 | break; 743 | } 744 | } 745 | 746 | 747 | inline string strToLower(const std::string& str) 748 | { 749 | string lower; 750 | lower.resize(str.size()); 751 | 752 | std::transform(str.cbegin(), str.cend(), lower.begin(), [](unsigned char c) { return std::tolower(c); }); 753 | return lower; 754 | } 755 | 756 | 757 | inline string strToLower(std::string&& str) 758 | { 759 | string lower{ std::move(str) }; 760 | 761 | std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); }); 762 | return lower; 763 | } 764 | 765 | 766 | 767 | /// 768 | /// Notice, this function taken from BinaCPP 769 | /// 770 | /// 771 | /// 772 | /// 773 | inline string b2a_hex(char* byte_arr, int n) 774 | { 775 | const static std::string HexCodes = "0123456789abcdef"; 776 | string HexString; 777 | for (int i = 0; i < n; ++i) 778 | { 779 | unsigned char BinValue = byte_arr[i]; 780 | HexString += HexCodes[(BinValue >> 4) & 0x0F]; 781 | HexString += HexCodes[BinValue & 0x0F]; 782 | } 783 | return HexString; 784 | } 785 | 786 | 787 | /// 788 | /// Notice, this function taken from BinaCPP 789 | /// 790 | /// 791 | /// 792 | /// 793 | inline string createSignature(const string& key, const string& data) 794 | { 795 | string hash; 796 | 797 | auto& dataString = utility::conversions::to_utf8string(data); 798 | 799 | if (unsigned char* digest = HMAC(EVP_sha256(), key.c_str(), static_cast(key.size()), (unsigned char*)dataString.c_str(), dataString.size(), NULL, NULL); digest) 800 | { 801 | hash = b2a_hex((char*)digest, 32); 802 | } 803 | 804 | return hash; 805 | } 806 | 807 | 808 | /// 809 | /// Ensure price is in a suitable format for the exchange, i.e. changing precision. 810 | /// You can get the precision for a symbol from exchangeInfo. 811 | /// 812 | /// The unformatted price 813 | /// The precision 814 | /// The price in a suitable format 815 | inline string priceTransform(const string& price, const std::streamsize precision = 2) 816 | { 817 | string p; 818 | 819 | stringstream ss; 820 | ss.precision(precision); 821 | ss << std::fixed << std::stod(price); 822 | ss >> p; 823 | 824 | return p; 825 | } 826 | 827 | 828 | /// 829 | /// Get a Binance API timestamp for the given time. 830 | /// 831 | /// 832 | inline auto getTimestamp(Clock::time_point t) -> Clock::duration::rep 833 | { 834 | return std::chrono::duration_cast (t.time_since_epoch()).count(); 835 | } 836 | 837 | 838 | /// 839 | /// Get a Binance API timestamp for now. 840 | /// 841 | /// 842 | inline auto getTimestamp() -> Clock::duration::rep 843 | { 844 | return getTimestamp(Clock::now()); 845 | } 846 | 847 | } 848 | 849 | #endif 850 | -------------------------------------------------------------------------------- /bfcpp/bfcpplib/bfcpplib.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 16.0 32 | Win32Proj 33 | {6d01fa21-81d5-452e-b5f4-d35da664e602} 34 | bfcpplib 35 | 10.0 36 | bfcpplib 37 | 38 | 39 | 40 | StaticLibrary 41 | true 42 | v142 43 | Unicode 44 | 45 | 46 | StaticLibrary 47 | false 48 | v142 49 | true 50 | Unicode 51 | 52 | 53 | StaticLibrary 54 | true 55 | v142 56 | NotSet 57 | 58 | 59 | StaticLibrary 60 | false 61 | v142 62 | true 63 | NotSet 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | true 85 | 86 | 87 | false 88 | 89 | 90 | true 91 | 92 | 93 | false 94 | 95 | 96 | true 97 | x64-windows-static 98 | false 99 | 100 | 101 | false 102 | x64-windows-static 103 | true 104 | 105 | 106 | 107 | Level3 108 | true 109 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 110 | true 111 | Use 112 | pch.h 113 | 114 | 115 | 116 | 117 | true 118 | 119 | 120 | 121 | 122 | Level3 123 | true 124 | true 125 | true 126 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 127 | true 128 | Use 129 | pch.h 130 | 131 | 132 | 133 | 134 | true 135 | true 136 | true 137 | 138 | 139 | 140 | 141 | Level3 142 | true 143 | BOOST_ASIO_HEADER_ONLY;_DEBUG;_LIB;_WINSOCK_DEPRECATED_NO_WARNINGS;_WIN32_WINNT=_WIN32_WINNT_WIN10;%(PreprocessorDefinitions) 144 | true 145 | NotUsing 146 | 147 | 148 | stdcpp17 149 | $(SolutionDir)..\vcpkg_win\installed\x64-windows-static\include;%(AdditionalIncludeDirectories) 150 | MultiThreadedDebug 151 | 152 | 153 | 154 | 155 | true 156 | 157 | 158 | $(SolutionDir)..\vcpkg_win\installed\x64-windows-static\debug\lib;%(AdditionalLibraryDirectories) 159 | PocoFoundationmtd.lib;cpprest_2_10d.lib;crypt32.lib;%(AdditionalDependencies) 160 | 161 | 162 | 163 | 164 | Level3 165 | true 166 | true 167 | true 168 | NDEBUG;BOOST_ASIO_HEADER_ONLY;_LIB;_WIN32_WINNT=_WIN32_WINNT_WIN10;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) 169 | true 170 | NotUsing 171 | 172 | 173 | stdcpp17 174 | $(SolutionDir)..\vcpkg_win\installed\x64-windows-static\include;%(AdditionalIncludeDirectories) 175 | MultiThreaded 176 | 177 | 178 | 179 | 180 | true 181 | true 182 | true 183 | 184 | 185 | $(SolutionDir)..\vcpkg_win\installed\x64-windows-static\lib;%(AdditionalLibraryDirectories) 186 | PocoFoundationmt.lib;cpprest_2_10.lib;crypt32.lib; 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /bfcpp/bfcpplib/bfcpplib.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | 29 | 30 | Source Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | -------------------------------------------------------------------------------- /bfcpp/bfcpptest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | include_directories("../../vcpkg_linux/installed/x64-linux/include") 4 | include_directories("../bfcpplib") 5 | 6 | 7 | LINK_DIRECTORIES("../../vcpkg_linux/installed/x64-linux/lib") 8 | 9 | 10 | # Add source to this project's executable. 11 | add_executable (bfcpptest "bfcpptest.cpp" "ScopedTimer.cpp") 12 | 13 | set_target_properties(bfcpptest PROPERTIES CXX_STANDARD 17) 14 | 15 | target_link_libraries(bfcpptest -lPocoFoundation -lpthread -lboost_system -lboost_date_time -lcpprest -lssl -lcrypto -lz -ldl) 16 | target_link_libraries(bfcpptest bfcpplib) 17 | 18 | -------------------------------------------------------------------------------- /bfcpp/bfcpptest/Logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "bfcppCommon.hpp" 11 | 12 | namespace bfcpp 13 | { 14 | enum class LogLevel { LogDebug, LogError }; 15 | 16 | #ifdef _DEBUG 17 | static const LogLevel Level = LogLevel::LogDebug; 18 | #else 19 | static const LogLevel Level = LogLevel::LogDebug; ;// LogLevel::LogError; 20 | #endif 21 | 22 | inline string timePointToString(const Clock::time_point& tp) 23 | { 24 | auto tm_t = Clock::to_time_t(tp); 25 | auto tm = std::localtime(&tm_t); 26 | std::stringstream ss; 27 | ss << std::put_time(tm, "%H:%M:%S"); 28 | return ss.str(); 29 | } 30 | 31 | 32 | inline string dateTimeToString(Poco::LocalDateTime ldt) 33 | { 34 | return Poco::DateTimeFormatter::format(ldt, "%H:%M:%S.%i"); 35 | } 36 | 37 | 38 | inline void logg(const std::string& str, const LogLevel l = LogLevel::LogDebug) 39 | { 40 | static std::mutex mux; 41 | 42 | if (!str.empty() && l == Level) 43 | { 44 | std::scoped_lock lock(mux); 45 | std::cout << "[" << dateTimeToString(Poco::LocalDateTime{}) << "] " << str << "\n"; 46 | } 47 | } 48 | 49 | 50 | inline void logg(std::string&& str, const LogLevel l = LogLevel::LogDebug) 51 | { 52 | static std::mutex mux; 53 | 54 | if (!str.empty() && l == Level) 55 | { 56 | std::scoped_lock lock(mux); 57 | std::cout << "[" << dateTimeToString(Poco::LocalDateTime{}) << "] " << str << "\n"; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /bfcpp/bfcpptest/OpenAndCloseLimitOrder.h: -------------------------------------------------------------------------------- 1 | #ifndef BFCPP_TEST_OPENANDCLOSELIMITORDER_H 2 | #define BFCPP_TEST_OPENANDCLOSELIMITORDER_H 3 | 4 | #include "TestCommon.hpp" 5 | #include 6 | 7 | 8 | /// 9 | /// A basic example of opening a LIMIT BUY order, waiting 8 seconds then either cancelling/closing. 10 | /// If the order is NEW it is cancelled. 11 | /// If the order is FILLED then a SELL is created. 12 | /// 13 | /// 14 | class OpenAndCloseLimitOrder : public BfcppTest 15 | { 16 | 17 | public: 18 | OpenAndCloseLimitOrder(const ApiAccess access) : BfcppTest(access), m_market{access} 19 | { 20 | m_status = OrderStatus::None; 21 | m_symbol = "BTCUSDT"; 22 | } 23 | 24 | 25 | void handleMarkPrice(std::any data) 26 | { 27 | auto priceData = std::any_cast (data); 28 | 29 | for (auto& price : priceData.prices) 30 | { 31 | if (price["s"] == m_symbol) 32 | { 33 | m_markPriceString = price["p"]; 34 | m_priceSet.notify_all(); 35 | break; 36 | } 37 | } 38 | } 39 | 40 | 41 | void handleUserDataUsdFutures(std::any userData) 42 | { 43 | UsdFutureUserData data = std::any_cast (userData); 44 | 45 | if (data.type == UsdFutureUserData::EventType::MarginCall) 46 | { 47 | std::stringstream ss; 48 | ss << "\nMargin Call\n{"; 49 | 50 | for (auto& p : data.mc.data) 51 | { 52 | logg(p.first + "=" + p.second); 53 | } 54 | 55 | for (auto& asset : data.mc.positions) 56 | { 57 | ss << "\n" << asset.first << "\n{"; // asset symbol 58 | 59 | for (const auto& balance : asset.second) 60 | { 61 | ss << "\n\t" << balance.first << "=" << balance.second; // the asset symbol, free and locked values for this symbol 62 | } 63 | 64 | ss << "\n}"; 65 | } 66 | 67 | ss << "\n}"; 68 | 69 | logg(ss.str()); 70 | } 71 | else if (data.type == UsdFutureUserData::EventType::OrderUpdate) 72 | { 73 | std::stringstream ss; 74 | ss << "\nOrder Update\n{"; 75 | 76 | if (auto orderEntry = data.ou.orders.find(m_symbol); orderEntry != data.ou.orders.cend()) 77 | { 78 | ss << "Order Status = " << orderEntry->second["X"]; 79 | 80 | m_status = OrderStatusMap.at(orderEntry->second["X"]); 81 | } 82 | 83 | logg(ss.str()); 84 | } 85 | else if (data.type == UsdFutureUserData::EventType::AccountUpdate) 86 | { 87 | std::stringstream ss; 88 | ss << "\nAccount Update\n{"; 89 | 90 | for (auto& p : data.au.data) 91 | { 92 | ss << "\n" << p.first << "=" << p.second; 93 | } 94 | 95 | ss << "\nReason: " << data.au.reason; 96 | 97 | ss << "\nBalances:"; 98 | for (const auto& balance : data.au.balances) 99 | { 100 | for (auto& asset : balance) 101 | { 102 | ss << "\n\t" << asset.first << "=" << asset.second; 103 | } 104 | } 105 | 106 | ss << "\nPositions:"; 107 | for (const auto& positions : data.au.positions) 108 | { 109 | for (auto& asset : positions) 110 | { 111 | ss << "\n\t" << asset.first << "=" << asset.second; 112 | } 113 | } 114 | 115 | ss << "\n}"; 116 | 117 | logg(ss.str()); 118 | } 119 | }; 120 | 121 | 122 | virtual void run() override 123 | { 124 | auto funcMarkPrice = std::bind(&OpenAndCloseLimitOrder::handleMarkPrice, std::ref(*this), std::placeholders::_1); 125 | m_market.monitorMarkPrice(funcMarkPrice); // to get an accurate price 126 | 127 | auto funcUserData = std::bind(&OpenAndCloseLimitOrder::handleUserDataUsdFutures, std::ref(*this), std::placeholders::_1); 128 | m_market.monitorUserData(funcUserData); // to get order updates 129 | 130 | logg("Create order"); 131 | m_orderId = createOrder(m_symbol); 132 | 133 | logg("Waiting"); 134 | std::this_thread::sleep_for(5s); 135 | 136 | closeOrder(m_orderId, m_status); 137 | } 138 | 139 | 140 | private: 141 | string createOrder(const string& symbol) 142 | { 143 | map order = 144 | { 145 | {"symbol", symbol}, 146 | {"side", "BUY"}, 147 | {"timeInForce", "GTC"}, 148 | {"type", "LIMIT"}, 149 | {"quantity", "0.001"}, 150 | {"price",""} // updated below with mark price from user data stream 151 | }; 152 | 153 | std::mutex mux; 154 | std::unique_lock lock(mux); 155 | m_priceSet.wait(lock); // notified by handleMarkPrice() 156 | 157 | // set price then send order 158 | order["price"] = priceTransform(std::to_string(std::stod(m_markPriceString))); 159 | 160 | auto result = m_market.newOrder(std::move(order)); 161 | 162 | 163 | stringstream ss; 164 | ss << "\nnewOrder() returned:\n"; 165 | for (const auto& val : result.response) 166 | { 167 | ss << val.first + "=" + val.second << "\n"; 168 | } 169 | logg(ss.str()); 170 | 171 | return result.response["orderId"]; 172 | } 173 | 174 | 175 | void closeOrder(const string& orderId, const OrderStatus status) 176 | { 177 | if (status == OrderStatus::New) 178 | { 179 | logg("Close order"); 180 | 181 | map cancel = 182 | { 183 | {"symbol", m_symbol}, 184 | {"orderId", m_orderId} 185 | }; 186 | 187 | auto cancelResult = m_market.cancelOrder(std::move(cancel)); 188 | 189 | stringstream ss; 190 | ss << "\ncancelOrder() returned:\n"; 191 | for (const auto& val : cancelResult.response) 192 | { 193 | ss << val.first + "=" + val.second << "\n"; 194 | } 195 | logg(ss.str()); 196 | } 197 | else if (status == OrderStatus::Filled) 198 | { 199 | logg("Close Filled position"); 200 | 201 | map cancel = 202 | { 203 | {"symbol", m_symbol}, 204 | {"side", "SELL"}, 205 | {"type", "MARKET"}, 206 | {"quantity", "0.001"}, 207 | {"orderId", m_orderId} 208 | }; 209 | 210 | auto cancelResult = m_market.newOrder(std::move(cancel)); 211 | 212 | stringstream ss; 213 | ss << "\nnewOrder() returned:\n"; 214 | for (const auto& val : cancelResult.response) 215 | { 216 | ss << val.first + "=" + val.second << "\n"; 217 | } 218 | logg(ss.str()); 219 | } 220 | else if (m_status == OrderStatus::PartiallyFilled) 221 | { 222 | logg("TODO Close PartiallyFilled position"); 223 | } 224 | } 225 | 226 | 227 | 228 | private: 229 | string m_symbol; 230 | string m_markPriceString; 231 | string m_orderId; 232 | 233 | std::condition_variable m_priceSet; 234 | UsdFuturesTestMarket m_market; 235 | std::atomic m_status; 236 | }; 237 | 238 | 239 | #endif 240 | 241 | -------------------------------------------------------------------------------- /bfcpp/bfcpptest/ScopedTimer.cpp: -------------------------------------------------------------------------------- 1 | #include "ScopedTimer.hpp" 2 | 3 | namespace framework 4 | { 5 | ScopedTimer::ScopedTimer() 6 | { 7 | restart(); 8 | } 9 | 10 | 11 | long long ScopedTimer::stopLong() 12 | { 13 | return stop().count(); 14 | } 15 | 16 | 17 | milliseconds ScopedTimer::stop() 18 | { 19 | return std::chrono::duration_cast (steady_clock::now() - m_start); 20 | } 21 | 22 | 23 | void ScopedTimer::restart() 24 | { 25 | m_start = steady_clock::now(); 26 | } 27 | } -------------------------------------------------------------------------------- /bfcpp/bfcpptest/ScopedTimer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __SCOPED_TIMER_H_ 2 | #define __SCOPED_TIMER_H_ 3 | 4 | #include 5 | 6 | /// 7 | /// Created by subatomicdev: https://github.com/subatomicdev 8 | /// Use all you want. 9 | namespace framework 10 | { 11 | using std::chrono::steady_clock; 12 | using std::chrono::duration; 13 | using std::chrono::milliseconds; 14 | 15 | /// Provides a timer, in milliseconds. 16 | /// 17 | /// The timer is started on object construction and ended by calling stop(); 18 | /// 19 | /// The time, in milliseconds, is returned by stop(). 20 | /// 21 | /// NOTE: only call stop() once, but elapsed() can be called multiple times. 22 | /// 23 | /// Usage: 24 | /// ScopedTimer timer; // this starts the timer 25 | /// ....... 26 | /// cout << "Time taken: " << timer.stop(); 27 | class ScopedTimer 28 | { 29 | public: 30 | /// Starts the timer. 31 | ScopedTimer(); 32 | ~ScopedTimer() = default; 33 | 34 | 35 | /// Stops the timer and returns the time. Only call once until next call to restart(). 36 | long long stopLong(); 37 | 38 | /// Stops the timer and returns the time. Only call once until next call to restart(). 39 | milliseconds stop(); 40 | 41 | void restart(); 42 | 43 | private: 44 | ScopedTimer& operator= (const ScopedTimer&) = delete; 45 | ScopedTimer(const ScopedTimer&) = delete; 46 | ScopedTimer(const ScopedTimer&&) = delete; 47 | 48 | private: 49 | steady_clock::time_point m_start; 50 | }; 51 | } 52 | 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /bfcpp/bfcpptest/TestCommon.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BFCPP_TEST_COMMON_H 2 | #define BFCPP_TEST_COMMON_H 3 | 4 | #include 5 | 6 | using namespace bfcpp; 7 | using namespace std::chrono_literals; 8 | 9 | class BfcppTest 10 | { 11 | 12 | public: 13 | virtual ~BfcppTest() 14 | { 15 | } 16 | 17 | virtual void run() = 0; 18 | 19 | protected: 20 | BfcppTest(ApiAccess access) : m_access(access) 21 | { 22 | } 23 | 24 | 25 | protected: 26 | ApiAccess m_access; 27 | }; 28 | 29 | 30 | #endif 31 | 32 | -------------------------------------------------------------------------------- /bfcpp/bfcpptest/bfcpptest.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include "Logger.hpp" 9 | 10 | #include "OpenAndCloseLimitOrder.h" 11 | #include "ScopedTimer.hpp" 12 | 13 | 14 | using namespace std::chrono_literals; 15 | using namespace bfcpp; 16 | 17 | 18 | 19 | auto handleUserDataUsdFutures = [](std::any userData) 20 | { 21 | UsdFutureUserData data = std::any_cast (userData); 22 | 23 | if (data.type == UsdFutureUserData::EventType::MarginCall) 24 | { 25 | std::stringstream ss; 26 | ss << "\nMargin Call\n{"; 27 | 28 | for (auto& p : data.mc.data) 29 | { 30 | logg(p.first + "=" + p.second); 31 | } 32 | 33 | for (auto& asset : data.mc.positions) 34 | { 35 | ss << "\n" << asset.first << "\n{"; // asset symbol 36 | 37 | for (const auto& balance : asset.second) 38 | { 39 | ss << "\n\t" << balance.first << "=" << balance.second; // the asset symbol, free and locked values for this symbol 40 | } 41 | 42 | ss << "\n}"; 43 | } 44 | 45 | ss << "\n}"; 46 | 47 | logg(ss.str()); 48 | } 49 | else if (data.type == UsdFutureUserData::EventType::OrderUpdate) 50 | { 51 | std::stringstream ss; 52 | ss << "\nOrder Update\n{"; 53 | 54 | for (auto& p : data.ou.data) 55 | { 56 | ss << "\n" << p.first << "=" << p.second; 57 | } 58 | 59 | for (auto& asset : data.ou.orders) 60 | { 61 | ss << "\n" << asset.first << "\n{"; // asset symbol 62 | 63 | for (const auto& order : asset.second) 64 | { 65 | ss << "\n\t" << order.first << "=" << order.second; 66 | } 67 | 68 | ss << "\n}"; 69 | } 70 | 71 | ss << "\n}"; 72 | 73 | logg(ss.str()); 74 | } 75 | else if (data.type == UsdFutureUserData::EventType::AccountUpdate) 76 | { 77 | std::stringstream ss; 78 | ss << "\nAccount Update\n{"; 79 | 80 | for (auto& p : data.au.data) 81 | { 82 | ss << "\n" << p.first << "=" << p.second; 83 | } 84 | 85 | ss << "\nReason: " << data.au.reason; 86 | 87 | ss << "\nBalances:"; 88 | for (const auto& balance : data.au.balances) 89 | { 90 | for (auto& asset : balance) 91 | { 92 | ss << "\n\t" << asset.first << "=" << asset.second; 93 | } 94 | } 95 | 96 | ss << "\nPositions:"; 97 | for (const auto& positions : data.au.positions) 98 | { 99 | for (auto& asset : positions) 100 | { 101 | ss << "\n\t" << asset.first << "=" << asset.second; 102 | } 103 | } 104 | 105 | ss << "\n}"; 106 | 107 | logg(ss.str()); 108 | } 109 | }; 110 | 111 | 112 | 113 | /// 114 | /// A simple function to receive mark price from the Future's market for all symbols. 115 | /// 116 | void monitorMarkPrice(string symbol = "") 117 | { 118 | auto markPriceHandler = [](std::any data) 119 | { 120 | auto priceData = std::any_cast (data); 121 | 122 | std::stringstream ss; 123 | 124 | for (const auto& pair : priceData.prices) 125 | { 126 | std::for_each(std::begin(pair), std::end(pair), [&ss](auto pair) { ss << "\n" << pair.first << "=" << pair.second; }); 127 | } 128 | 129 | logg(ss.str()); 130 | }; 131 | 132 | 133 | if (symbol.empty()) 134 | std::cout << "\n\n--- USD-M Futures All Symbols Mark Price ---\n"; 135 | else 136 | std::cout << "\n\n--- USD-M Futures " + symbol + " Mark Price ---\n"; 137 | 138 | 139 | UsdFuturesMarket usdFutures; 140 | 141 | try 142 | { 143 | usdFutures.monitorMarkPrice(markPriceHandler, symbol); 144 | } 145 | catch (bfcpp::BfcppDisconnectException dex) 146 | { 147 | logg(dex.source() + " has been disconnected"); 148 | } 149 | 150 | std::this_thread::sleep_for(10s); 151 | } 152 | 153 | 154 | /// 155 | /// Receive candlesticks from websocket stream 156 | /// 157 | void monitorCandleSticks() 158 | { 159 | std::cout << "\n\n--- USD-M Futures Candles ---\n"; 160 | 161 | auto onStickHandler = [](std::any data) 162 | { 163 | auto candleData = std::any_cast (data); 164 | 165 | std::stringstream ss; 166 | ss << "\neventType=" << candleData.eventTime; 167 | ss << "\nsymbol=" << candleData.symbol; 168 | 169 | std::for_each(std::begin(candleData.candle), std::end(candleData.candle), [&ss](auto pair) { ss << "\n" << pair.first << "=" << pair.second; }); 170 | logg(ss.str()); 171 | }; 172 | 173 | 174 | UsdFuturesTestMarket usdFutures; 175 | 176 | try 177 | { 178 | usdFutures.monitorKlineCandlestickStream("CVCUSDT", "15m", onStickHandler); 179 | } 180 | catch (bfcpp::BfcppDisconnectException dex) 181 | { 182 | logg(dex.source() + " has been disconnected"); 183 | } 184 | 185 | 186 | std::this_thread::sleep_for(5s); 187 | } 188 | 189 | 190 | 191 | /// 192 | /// Shows it's easy to receive multiple streams. 193 | /// 194 | void monitorMultipleStreams() 195 | { 196 | std::cout << "\n\n--- USD-M Futures Multiple Streams on Futures ---\n"; 197 | 198 | auto markPriceHandler = [](std::any data) 199 | { 200 | auto priceData = std::any_cast (data); 201 | 202 | std::stringstream ss; 203 | 204 | for (const auto& pair : priceData.prices) 205 | { 206 | std::for_each(std::begin(pair), std::end(pair), [&ss](auto pair) { ss << "\n" << pair.first << "=" << pair.second; }); 207 | } 208 | 209 | logg(ss.str()); 210 | }; 211 | 212 | 213 | auto onSymbolMiniTickHandler = [](std::any data) 214 | { 215 | auto ticker = std::any_cast (data); 216 | 217 | stringstream ss; 218 | 219 | for (auto& tick : ticker.data) 220 | { 221 | std::for_each(std::begin(tick), std::end(tick), [&ss](auto pair) { ss << "\n" << pair.first << "=" << pair.second; }); 222 | } 223 | 224 | logg(ss.str()); 225 | }; 226 | 227 | 228 | UsdFuturesMarket usdFutures; 229 | usdFutures.monitorMarkPrice(markPriceHandler); 230 | usdFutures.monitorMiniTicker(onSymbolMiniTickHandler); 231 | 232 | std::this_thread::sleep_for(10s); 233 | } 234 | 235 | 236 | /// 237 | /// Receive user data from the Future's TestNet market: https://testnet.binancefuture.com/en/futures/BTC_USDT 238 | /// Note, they seem to disconnect clients after a few orders are created/closed. 239 | /// 240 | /// Create an account on the above URL. This key is available at the bottom of the page 241 | /// Create an account on the above URL. This key is available at the bottom of the page 242 | void usdFutureTestNetDataStream(const ApiAccess& access) 243 | { 244 | std::cout << "\n\n--- USD-M Futures TESTNET User Data ---\n"; 245 | std::cout << "You must create/cancel etc an order for anything to show here\n"; 246 | 247 | UsdFuturesTestMarket futuresTest { access} ; 248 | futuresTest.monitorUserData(handleUserDataUsdFutures); 249 | 250 | std::this_thread::sleep_for(10s); 251 | } 252 | 253 | 254 | 255 | /// 256 | /// 257 | /// 258 | /// 259 | void usdFutureDataStream(const ApiAccess& access) 260 | { 261 | std::cout << "\n\n--- USD-M Futures User Data ---\n"; 262 | std::cout << "You must create/cancel etc an order for anything to show here\n"; 263 | 264 | UsdFuturesMarket futures { access }; 265 | futures.monitorUserData(handleUserDataUsdFutures); 266 | 267 | std::this_thread::sleep_for(10s); 268 | } 269 | 270 | 271 | 272 | /// 273 | /// Receives from the symbol mini ticker, updated every 1000ms. 274 | /// 275 | void monitorSymbol() 276 | { 277 | std::cout << "\n\n--- USD-M Futures Monitor Symbol Mini Ticker ---\n"; 278 | 279 | auto onSymbolMiniTickHandler = [](std::any data) 280 | { 281 | auto tick = std::any_cast (data); 282 | 283 | stringstream ss; 284 | std::for_each(std::begin(tick.data), std::end(tick.data), [&ss](auto pair) { ss << "\n" << pair.first << "=" << pair.second; }); 285 | 286 | logg(ss.str()); 287 | }; 288 | 289 | 290 | UsdFuturesMarket futures; 291 | futures.monitorSymbol("BTCUSDT", onSymbolMiniTickHandler); 292 | 293 | std::this_thread::sleep_for(10s); 294 | } 295 | 296 | 297 | /// 298 | /// Received any update to the best bid or ask's price or quantity in real-time for a specified symbol. 299 | /// 300 | void monitorSymbolBook() 301 | { 302 | std::cout << "\n\n--- USD-M Futures Monitor Symbol Book Ticker ---\n"; 303 | 304 | auto onBookSymbolMiniTickHandler = [](std::any data) 305 | { 306 | auto tick = std::any_cast (data); 307 | 308 | stringstream ss; 309 | std::for_each(std::begin(tick.data), std::end(tick.data), [&ss](auto pair) { ss << "\n" << pair.first << "=" << pair.second; }); 310 | 311 | logg(ss.str()); 312 | }; 313 | 314 | 315 | UsdFuturesMarket futures; 316 | futures.monitorSymbolBookStream("BTCUSDT", onBookSymbolMiniTickHandler); 317 | 318 | std::this_thread::sleep_for(10s); 319 | } 320 | 321 | 322 | 323 | void monitorAllMarketMiniTicker() 324 | { 325 | std::cout << "\n\n--- USD-M Futures Monitor All Market Symbol Ticker ---\n"; 326 | 327 | auto onSymbolMiniTickHandler = [](std::any data) 328 | { 329 | auto ticker = std::any_cast (data); 330 | 331 | stringstream ss; 332 | 333 | for (auto& tick : ticker.data) 334 | { 335 | std::for_each(std::begin(tick), std::end(tick), [&ss](auto pair) { ss << "\n" << pair.first << "=" << pair.second; }); 336 | } 337 | 338 | logg(ss.str()); 339 | }; 340 | 341 | 342 | UsdFuturesMarket futures; 343 | futures.monitorMiniTicker(onSymbolMiniTickHandler); 344 | 345 | std::this_thread::sleep_for(10s); 346 | } 347 | 348 | 349 | 350 | void monitorPartialBookDepth() 351 | { 352 | std::cout << "\n\n--- USD-M Futures Monitor Partial Book ---\n"; 353 | 354 | auto onPartialBookDepthHandler = [](std::any data) 355 | { 356 | auto result = std::any_cast (data); 357 | 358 | stringstream ss; 359 | 360 | ss << "\nsymbol: " << result.symbol << "\nfirstUpdateId: " << result.firstUpdateId << "\nfinalUpdateId: " << result.finalUpdateId << "\npreviousFinalUpdateId: " << result.previousFinalUpdateId 361 | << "\ntransactionTime: " << result.transactionTime; 362 | 363 | ss << "\nBids:\n{"; 364 | std::for_each(std::begin(result.bids), std::end(result.bids), [&ss](auto& value) { ss << "\n\tPrice: " << value.first << ", Qty: " << value.second; }); 365 | ss << "\n}"; 366 | 367 | ss << "\nBids:\n{"; 368 | std::for_each(std::begin(result.asks), std::end(result.asks), [&ss](auto& value) { ss << "\n\tPrice: " << value.first << ", Qty: " << value.second; }); 369 | ss << "\n}"; 370 | 371 | logg(ss.str()); 372 | }; 373 | 374 | 375 | UsdFuturesMarket futures; 376 | if (auto token = futures.monitorPartialBookDepth("BTCUSDT", "20", "100ms", onPartialBookDepthHandler); !token.isValid()) 377 | { 378 | logg("Failed to create monitor"); 379 | futures.cancelMonitor(token); 380 | } 381 | else 382 | { 383 | std::this_thread::sleep_for(10s); 384 | } 385 | } 386 | 387 | 388 | 389 | void monitorDiffBookDepth() 390 | { 391 | std::cout << "\n\n--- USD-M Futures Monitor Diff Book ---\n"; 392 | 393 | auto onPartialBookDepthHandler = [](std::any data) 394 | { 395 | auto result = std::any_cast (data); 396 | 397 | stringstream ss; 398 | 399 | ss << "\nsymbol: " << result.symbol << "\nfirstUpdateId: " << result.firstUpdateId << "\nfinalUpdateId: " << result.finalUpdateId << "\npreviousFinalUpdateId: " << result.previousFinalUpdateId 400 | << "\ntransactionTime: " << result.transactionTime; 401 | 402 | ss << "\nBids:\n{"; 403 | std::for_each(std::begin(result.bids), std::end(result.bids), [&ss](auto& value) { ss << "\n\tPrice: " << value.first << ", Qty: " << value.second; }); 404 | ss << "\n}"; 405 | 406 | ss << "\nBids:\n{"; 407 | std::for_each(std::begin(result.asks), std::end(result.asks), [&ss](auto& value) { ss << "\n\tPrice: " << value.first << ", Qty: " << value.second; }); 408 | ss << "\n}"; 409 | 410 | logg(ss.str()); 411 | }; 412 | 413 | 414 | UsdFuturesMarket futures; 415 | if (auto token = futures.monitorDiffBookDepth("BTCUSDT", "100ms", onPartialBookDepthHandler); !token.isValid()) 416 | { 417 | logg("Failed to create monitor"); 418 | futures.cancelMonitor(token); 419 | } 420 | else 421 | { 422 | std::this_thread::sleep_for(10s); 423 | } 424 | } 425 | 426 | 427 | /// 428 | /// 1. Get all orders (which defaults to within 7 days) 429 | /// 2. Get all orders for today 430 | /// 431 | /// Read Market::allOrders() for what will be returned. 432 | /// 433 | /// 434 | void allOrders(const ApiAccess& access) 435 | { 436 | std::cout << "\n\n--- USD-M Futures TESTNET All Orders ---\n"; 437 | 438 | auto showResults = [](const AllOrdersResult& result) 439 | { 440 | stringstream ss; 441 | 442 | if (result.valid()) 443 | { 444 | ss << "\nFound " << result.response.size() << " orders"; 445 | 446 | for (const auto& order : result.response) 447 | { 448 | ss << "\n{"; 449 | std::for_each(std::begin(order), std::end(order), [&ss](auto& values) { ss << "\n\t" << values.first << "=" << values.second; }); 450 | ss << "\n}"; 451 | } 452 | } 453 | else 454 | { 455 | ss << "Invalid: " << result.msg(); 456 | } 457 | 458 | logg(ss.str()); 459 | }; 460 | 461 | UsdFuturesTestMarket futuresTest { access }; 462 | 463 | 464 | // --- Get all orders --- 465 | auto result = futuresTest.allOrders({ {"symbol", "BTCUSDT"} }); 466 | logg("All orders"); 467 | showResults(result); 468 | 469 | 470 | // --- Get all orders for today --- 471 | 472 | // get timestamp for start of today 473 | Clock::time_point startOfDay; 474 | { 475 | time_t tm = Clock::to_time_t(Clock::now()); 476 | auto lt = std::localtime(&tm); 477 | lt->tm_hour = 0; 478 | lt->tm_min = 0; 479 | lt->tm_sec = 0; 480 | 481 | startOfDay = Clock::from_time_t(std::mktime(lt)); 482 | } 483 | 484 | logg("All orders for today"); 485 | result = futuresTest.allOrders({ {"symbol", "BTCUSDT"}, {"startTime", std::to_string(bfcpp::getTimestamp(startOfDay))} }); 486 | 487 | showResults(result); 488 | } 489 | 490 | 491 | 492 | void accountInformation(const ApiAccess& access) 493 | { 494 | std::cout << "\n\n--- USD-M Futures TESTNET Account Information ---\n"; 495 | 496 | auto showResults = [](const AccountInformation& result) 497 | { 498 | stringstream ss; 499 | 500 | std::for_each(std::begin(result.data), std::end(result.data), [&ss](auto& values) { ss << "\n" << values.first << "=" << values.second; }); 501 | 502 | ss << "\nFound " << result.assets.size() << " assets"; 503 | for (const auto& asset : result.assets) 504 | { 505 | ss << "\n{"; 506 | std::for_each(std::begin(asset), std::end(asset), [&ss](auto& values) { ss << "\n\t" << values.first << "=" << values.second; }); 507 | ss << "\n}"; 508 | } 509 | 510 | ss << "\nFound " << result.positions.size() << " positions"; 511 | for (const auto& position : result.positions) 512 | { 513 | ss << "\n{"; 514 | std::for_each(std::begin(position), std::end(position), [&ss](auto& values) { ss << "\n\t" << values.first << "=" << values.second; }); 515 | ss << "\n}"; 516 | } 517 | 518 | logg(ss.str()); 519 | }; 520 | 521 | 522 | UsdFuturesTestMarket futuresTest{ access }; 523 | 524 | auto result = futuresTest.accountInformation(); 525 | 526 | showResults(result); 527 | } 528 | 529 | 530 | 531 | void accountBalance(const ApiAccess& access) 532 | { 533 | std::cout << "\n\n--- USD-M Futures TESTNET Account Balance ---\n"; 534 | 535 | UsdFuturesTestMarket futuresTest{ access }; 536 | 537 | auto result = futuresTest.accountBalance(); 538 | 539 | stringstream ss; 540 | ss << "\nFound " << result.balances.size() << " balances"; 541 | for (const auto& asset : result.balances) 542 | { 543 | ss << "\n{"; 544 | std::for_each(std::begin(asset), std::end(asset), [&ss](auto& values) { ss << "\n\t" << values.first << "=" << values.second; }); 545 | ss << "\n}"; 546 | } 547 | 548 | logg(ss.str()); 549 | } 550 | 551 | 552 | 553 | void takerBuySellVolume(const ApiAccess& access) 554 | { 555 | std::cout << "\n\n--- USD-M Futures Taker Buy Sell Volume ---\n"; 556 | 557 | UsdFuturesMarket futuresTest{ access }; 558 | 559 | auto result = futuresTest.takerBuySellVolume({ {"symbol","BTCUSDT"}, {"period","15m"} }); 560 | 561 | stringstream ss; 562 | ss << "\nFound " << result.response.size() << " volumes"; 563 | for (const auto& entry : result.response) 564 | { 565 | ss << "\n{"; 566 | std::for_each(std::begin(entry), std::end(entry), [&ss](auto& values) { ss << "\n\t" << values.first << "=" << values.second; }); 567 | ss << "\n}"; 568 | } 569 | 570 | logg(ss.str()); 571 | } 572 | 573 | 574 | 575 | void klines() 576 | { 577 | std::cout << "\n\n--- USD-M Futures Klines ---\n"; 578 | 579 | UsdFuturesMarket futuresTest{}; 580 | futuresTest.setReceiveWindow(RestCall::KlineCandles, 3000ms); 581 | 582 | auto result = futuresTest.klines({ {"symbol","BTCUSDT"}, {"limit","5"}, {"interval", "15m"} }); 583 | 584 | 585 | 586 | stringstream ss; 587 | ss << "\nFound " << result.response.size() << " kline sticks"; 588 | for (const auto& entry : result.response) 589 | { 590 | ss << "\n{"; 591 | std::for_each(std::begin(entry), std::end(entry), [&ss](auto& value) { ss << "\n\t" << value; }); 592 | ss << "\n}"; 593 | } 594 | 595 | logg(ss.str()); 596 | } 597 | 598 | 599 | 600 | using namespace std::chrono; 601 | 602 | static size_t NumNewOrders = 5; 603 | 604 | 605 | void performanceNewOrderCheckSync(const ApiAccess& access) 606 | { 607 | std::cout << "\n\n--- USD-M Futures New Order Sync Performance ---\n"; 608 | 609 | map order = 610 | { 611 | {"symbol", "BTCUSDT"}, 612 | {"side", "BUY"}, 613 | {"type", "MARKET"}, 614 | {"quantity", "0.001"} 615 | }; 616 | 617 | UsdFuturesTestMarketPerfomance market{ access }; 618 | 619 | vector results; 620 | results.reserve(NumNewOrders); 621 | 622 | for (size_t i = 0; i < NumNewOrders; ++i) 623 | { 624 | auto start = Clock::now(); 625 | auto result = market.newOrderPerfomanceCheck(std::move(order)); 626 | result.total = Clock::now() - start; 627 | results.emplace_back(std::move(result)); 628 | } 629 | 630 | 631 | high_resolution_clock::duration total{}; 632 | high_resolution_clock::duration avgQueryBuild{}, avgApiCall{}, avgResponseHandler{}, 633 | maxApiCall{ high_resolution_clock::duration::min() }, 634 | minApiCall{ high_resolution_clock::duration::max() }; 635 | 636 | for (const auto& result : results) 637 | { 638 | if (result.valid()) 639 | { 640 | total += result.total; 641 | 642 | avgQueryBuild += result.restQueryBuild; 643 | avgApiCall += result.restApiCall; 644 | avgResponseHandler += result.restResponseHandler; 645 | minApiCall = std::min(minApiCall, result.restApiCall); 646 | maxApiCall = std::max(maxApiCall, result.restApiCall); 647 | } 648 | else 649 | { 650 | logg("Error: " + result.msg()); 651 | } 652 | 653 | total += result.total; 654 | } 655 | 656 | 657 | avgQueryBuild /= NumNewOrders; 658 | avgApiCall /= NumNewOrders; 659 | avgResponseHandler /= NumNewOrders; 660 | 661 | stringstream ss; 662 | ss << "\nTotal: " << NumNewOrders << " orders in " << duration_cast(total).count() << " milliseconds\n"; 663 | ss << "\n|\t\t\t| time (nanoseconds) |" << 664 | "\n------------------------------------------" << 665 | "\nAvg. Rest Query Build:\t\t" << duration_cast(avgQueryBuild).count() << 666 | "\nAvg. Rest Call Latency:\t\t" << duration_cast(avgApiCall).count() << " (Min:" << duration_cast(minApiCall).count() << ", Max: " << duration_cast(maxApiCall).count() << ")" << 667 | "\nAvg. Rest Response Handler:\t" << duration_cast(avgResponseHandler).count() << 668 | "\n------------------------------------------"; 669 | 670 | logg(ss.str()); 671 | } 672 | 673 | 674 | void performanceNewOrderCheckAsync(const ApiAccess& access) 675 | { 676 | std::cout << "\n\n--- USD-M Futures New Order Async Performance ---\n"; 677 | 678 | UsdFuturesTestMarketPerfomance market{ access }; 679 | 680 | vector> results; 681 | results.reserve(NumNewOrders); 682 | 683 | auto start = Clock::now(); 684 | for (size_t i = 0; i < NumNewOrders; ++i) 685 | { 686 | map order = 687 | { 688 | {"symbol", "BTCUSDT"}, 689 | {"side", "BUY"}, 690 | {"type", "MARKET"}, 691 | {"quantity", "0.001"} 692 | }; 693 | 694 | auto result = market.newOrderPerfomanceCheckAsync(std::move(order)); 695 | results.emplace_back(std::move(result)); 696 | } 697 | 698 | 699 | // note: you could use pplx::when_any() to handle each task as it completes, then call again when_any() until all 700 | // are finished. 701 | 702 | // wait for the new order tasks to return, the majority of which is due to the REST call latency 703 | pplx::when_all(std::begin(results), std::end(results)).wait(); 704 | auto end = Clock::now(); 705 | 706 | 707 | high_resolution_clock::duration avgQueryBuild{}, avgApiCall{}, avgResponseHandler{}, 708 | maxApiCall{ high_resolution_clock::duration::min() }, 709 | minApiCall{ high_resolution_clock::duration::max()}; 710 | 711 | for (const auto& taskResult : results) 712 | { 713 | const auto& result = taskResult.get(); 714 | 715 | if (result.valid()) 716 | { 717 | avgQueryBuild += result.restQueryBuild; 718 | avgApiCall += result.restApiCall; 719 | avgResponseHandler += result.restResponseHandler; 720 | minApiCall = std::min(minApiCall, result.restApiCall); 721 | maxApiCall = std::max(maxApiCall, result.restApiCall); 722 | } 723 | else 724 | { 725 | logg("Error: " + result.msg()); 726 | } 727 | } 728 | 729 | avgQueryBuild /= NumNewOrders; 730 | avgApiCall /= NumNewOrders; 731 | avgResponseHandler /= NumNewOrders; 732 | 733 | stringstream ss; 734 | ss << "\nTotal: " << NumNewOrders << " orders in " << std::chrono::duration_cast(end - start).count() << " milliseconds\n"; 735 | ss << "\n|\t\t\t| time (nanoseconds) |" << 736 | "\n------------------------------------------" << 737 | "\nAvg. Rest Query Build:\t\t" << duration_cast(avgQueryBuild).count() << 738 | "\nAvg. Rest Call Latency:\t\t" << duration_cast(avgApiCall).count() << " (Min:" << duration_cast(minApiCall).count() << ", Max: " << duration_cast(maxApiCall).count() << ")" << 739 | "\nAvg. Rest Response Handler:\t" << duration_cast(avgResponseHandler).count() << 740 | "\n------------------------------------------"; 741 | 742 | logg(ss.str()); 743 | } 744 | 745 | 746 | // The batch order isn't very reliable, at least on the Testnet, there's a regular Bad Gateway error 747 | void performanceNewOrderBatchCheckAsync(const ApiAccess& access) 748 | { 749 | std::cout << "\n\n--- USD-M Futures New Order Async Performance ---\n"; 750 | 751 | static size_t NumBatchOrders = 10; 752 | static size_t MaxOrdersPerBatchCall = 5; 753 | 754 | UsdFuturesTestMarketPerfomance market{ access }; 755 | 756 | vector> results; 757 | results.reserve(NumBatchOrders/MaxOrdersPerBatchCall); 758 | 759 | 760 | auto start = Clock::now(); 761 | for (size_t i = 0; i < NumBatchOrders/MaxOrdersPerBatchCall; ++i) 762 | { 763 | vector> orders; 764 | for (size_t i = 0; i < MaxOrdersPerBatchCall; ++i) 765 | { 766 | orders.emplace_back(map { { "symbol", "BTCUSDT" }, { "side", "BUY" }, { "type", "MARKET" }, { "quantity", "0.001" } }); 767 | } 768 | 769 | auto result = market.newOrderBatchPerfomanceCheckAsync(std::move(orders)); 770 | results.emplace_back(std::move(result)); 771 | } 772 | 773 | 774 | // wait for the new order tasks to return 775 | pplx::when_all(std::begin(results), std::end(results)).wait(); 776 | 777 | auto end = Clock::now(); 778 | 779 | high_resolution_clock::duration avgQueryBuild{}, avgApiCall{}, avgResponseHandler{}, 780 | maxApiCall{ high_resolution_clock::duration::min() }, 781 | minApiCall{ high_resolution_clock::duration::max() }; 782 | 783 | for (const auto& taskResult : results) 784 | { 785 | const auto& result = taskResult.get(); 786 | 787 | if (result.valid()) 788 | { 789 | avgQueryBuild += result.restQueryBuild; 790 | avgApiCall += result.restApiCall; 791 | avgResponseHandler += result.restResponseHandler; 792 | minApiCall = std::min(minApiCall, result.restApiCall); 793 | maxApiCall = std::max(maxApiCall, result.restApiCall); 794 | } 795 | else 796 | { 797 | logg("Error: " + result.msg()); 798 | } 799 | } 800 | 801 | avgQueryBuild /= NumBatchOrders; 802 | avgApiCall /= (NumBatchOrders/MaxOrdersPerBatchCall); 803 | avgResponseHandler /= NumBatchOrders; 804 | 805 | stringstream ss; 806 | ss << "\nTotal: " << NumBatchOrders << " orders in " << std::chrono::duration_cast(end - start).count() << " milliseconds\n"; 807 | ss << "\n|\t\t\t| time (nanoseconds) |" << 808 | "\n------------------------------------------" << 809 | "\nAvg. Rest Query Build:\t\t" << duration_cast(avgQueryBuild).count() << 810 | "\nAvg. Rest Call Latency:\t\t" << duration_cast(avgApiCall).count() << " (Min:" << duration_cast(minApiCall).count() << ", Max: " << duration_cast(maxApiCall).count() << ")" << 811 | "\nAvg. Rest Response Handler:\t" << duration_cast(avgResponseHandler).count() << 812 | "\n------------------------------------------"; 813 | 814 | logg(ss.str()); 815 | } 816 | 817 | 818 | void newOrderAsync(const ApiAccess& access) 819 | { 820 | std::cout << "\n\n--- USD-M Futures New Order Async ---\n"; 821 | 822 | UsdFuturesTestMarket market{ access }; 823 | 824 | vector> results; 825 | results.reserve(NumNewOrders); 826 | 827 | 828 | logg("Sending orders"); 829 | 830 | for (size_t i = 0; i < NumNewOrders; ++i) 831 | { 832 | map order = 833 | { 834 | {"symbol", "BTCUSDT"}, 835 | {"side", "BUY"}, 836 | {"type", "MARKET"}, 837 | {"quantity", "0.001"} 838 | }; 839 | 840 | results.emplace_back(std::move(market.newOrderAsync(std::move(order)))); 841 | } 842 | 843 | logg("Waiting for all to complete"); 844 | 845 | // note: you could use pplx::when_any() to handle each task as it completes, 846 | // then call when_any() until all are finished. 847 | 848 | // wait for the new order tasks to return, the majority of which is due to the REST call latency 849 | pplx::when_all(std::begin(results), std::end(results)).wait(); 850 | 851 | logg("Done: "); 852 | 853 | stringstream ss; 854 | ss << "\nOrder Ids: "; 855 | for (auto& task : results) 856 | { 857 | NewOrderResult result = task.get(); 858 | 859 | if (result.valid()) 860 | { 861 | // do stuff with result 862 | ss << "\n" << result.response["orderId"]; 863 | } 864 | } 865 | 866 | logg(ss.str()); 867 | } 868 | 869 | 870 | void newOrderBatch(const ApiAccess& access) 871 | { 872 | std::cout << "\n\n--- USD-M Futures New Order Batch ---\n"; 873 | 874 | map order = 875 | { 876 | {"symbol", "BTCUSDT"}, 877 | {"side", "BUY"}, 878 | {"type", "MARKET"}, 879 | {"quantity", "0.001"} 880 | }; 881 | 882 | vector> orders; 883 | orders.push_back(order); 884 | orders.push_back(order); 885 | orders.push_back(order); 886 | 887 | 888 | UsdFuturesTestMarket market{ access }; 889 | 890 | try 891 | { 892 | auto result = market.newOrderBatch(std::move(orders)); 893 | 894 | stringstream ss; 895 | ss << "\nResponse:"; 896 | for (const auto& order : result.response) 897 | { 898 | ss << "\n{"; 899 | std::for_each(std::begin(order), std::end(order), [&ss](auto& info) { ss << "\n\t" << info.first + "=" + info.second ; }); 900 | ss << "\n}"; 901 | } 902 | logg(ss.str()); 903 | } 904 | catch (std::exception ex) 905 | { 906 | logg(ex.what()); 907 | } 908 | } 909 | 910 | 911 | 912 | void exchangeInfo () 913 | { 914 | std::cout << "\n\n--- USD-M Futures Exchange Info ---\n"; 915 | 916 | 917 | UsdFuturesTestMarket market{}; 918 | 919 | try 920 | { 921 | auto result = market.exchangeInfo(); 922 | 923 | stringstream ss; 924 | ss << "\nResponse:"; 925 | 926 | ss << "\nSymbols\n{"; 927 | for (const auto& symbol: result.symbols) 928 | { 929 | ss << "\n\tdata\n\t{"; 930 | std::for_each(std::begin(symbol.data), std::end(symbol.data), [&ss](auto& info) { ss << "\n\t\t" << info.first + "=" + info.second; }); 931 | ss << "\n\t}"; 932 | 933 | ss << "\n\tfilters\n\t{"; 934 | for (const auto& filter : symbol.filters) 935 | { 936 | std::for_each(std::begin(filter), std::end(filter), [&ss](auto& info) { ss << "\n\t\t" << info.first + "=" + info.second; }); 937 | } 938 | ss << "\n\t}"; 939 | 940 | 941 | ss << "\n\torderType\n\t{"; 942 | std::for_each(std::begin(symbol.orderTypes), std::end(symbol.orderTypes), [&ss](auto& info) { ss << "\n\t\t" << info; }); 943 | ss << "\n\t}"; 944 | 945 | ss << "\n\ttimeInForce\n\t{"; 946 | std::for_each(std::begin(symbol.timeInForce), std::end(symbol.timeInForce), [&ss](auto& info) { ss << "\n\t\t" << info; }); 947 | ss << "\n\t}"; 948 | } 949 | ss << "\n}"; 950 | 951 | 952 | ss << "\nRate Limits\n{"; 953 | for (const auto& rate : result.rateLimits) 954 | { 955 | std::for_each(std::begin(rate), std::end(rate), [&ss](auto& info) { ss << "\n\t" << info.first + "=" + info.second; }); 956 | } 957 | ss << "\n}"; 958 | 959 | 960 | ss << "\nExchange Filters\n{"; 961 | for (const auto& filter : result.exchangeFilters) 962 | { 963 | std::for_each(std::begin(filter), std::end(filter), [&ss](auto& info) { ss << "\n\t" << info.first + "=" + info.second; }); 964 | } 965 | ss << "\n}"; 966 | 967 | 968 | ss << "\nserverTime=" << result.serverTime << "\ntimezone=" << result.timezone; 969 | 970 | logg(ss.str()); 971 | } 972 | catch (BfcppException bef) 973 | { 974 | logg("error: " + string{ bef.what() }); 975 | } 976 | } 977 | 978 | 979 | 980 | void orderBook() 981 | { 982 | std::cout << "\n\n--- USD-M Futures Order Book/Depth ---\n"; 983 | 984 | UsdFuturesMarket futuresTest{}; 985 | 986 | logg("Sending request"); 987 | 988 | auto result = futuresTest.orderBook({ {"symbol","BTCUSDT"}, {"limit","50"}}); 989 | 990 | 991 | stringstream ss; 992 | 993 | ss << "\nlastUpdateId: " << result.lastUpdateId << "\nE: " << result.messageOutputTime << "\nT: " << result.transactionTime; 994 | 995 | ss << "\nBids:\n{"; 996 | std::for_each(std::begin(result.bids), std::end(result.bids), [&ss](auto& value) { ss << "\n\tPrice: " << value.first << ", Qty: " << value.second; }); 997 | ss << "\n}"; 998 | 999 | ss << "\nBids:\n{"; 1000 | std::for_each(std::begin(result.asks), std::end(result.asks), [&ss](auto& value) { ss << "\n\tPrice: " << value.first << ", Qty: " << value.second; }); 1001 | ss << "\n}"; 1002 | 1003 | logg(ss.str()); 1004 | } 1005 | 1006 | 1007 | 1008 | 1009 | int main(int argc, char** argv) 1010 | { 1011 | try 1012 | { 1013 | std::string apiFutTest, secretFutTest; 1014 | std::string apiFut, secretFut; 1015 | 1016 | bool testNetMode = true; 1017 | 1018 | if (argc == 2) 1019 | { 1020 | // keyfile used - format is 3 lines: 1021 | // 1022 | // 1023 | // 1024 | if (auto fileSize = std::filesystem::file_size(std::filesystem::path {argv[1]}) ; fileSize > 140) 1025 | { 1026 | logg("Key file should be format with 3 lines:\nLine 1: \nLine 2: api key\nLine 3: secret key"); 1027 | } 1028 | else 1029 | { 1030 | string line; 1031 | 1032 | std::ifstream fileStream(argv[1]); 1033 | std::getline(fileStream, line); 1034 | 1035 | if (line == "live") 1036 | { 1037 | std::getline(fileStream, apiFut); 1038 | std::getline(fileStream, secretFut); 1039 | testNetMode = false; 1040 | } 1041 | else 1042 | { 1043 | std::getline(fileStream, apiFutTest); 1044 | std::getline(fileStream, secretFutTest); 1045 | } 1046 | } 1047 | } 1048 | 1049 | 1050 | 1051 | // these don't require keys 1052 | //monitorMarkPrice(); 1053 | //monitorMarkPrice("BTCUSDT"); 1054 | //monitorCandleSticks(); 1055 | //monitorSymbol(); 1056 | //monitorSymbolBook(); 1057 | //monitorAllMarketMiniTicker(); 1058 | //monitorMultipleStreams(); 1059 | //monitorPartialBookDepth(); 1060 | monitorDiffBookDepth(); 1061 | 1062 | 1063 | //klines(); 1064 | //exchangeInfo(); 1065 | //orderBook(); 1066 | 1067 | 1068 | if (testNetMode) 1069 | { 1070 | ApiAccess access { apiFutTest, secretFutTest }; 1071 | //usdFutureTestNetDataStream(access); 1072 | 1073 | //OpenAndCloseLimitOrder openCloseLimit{ access }; 1074 | //openCloseLimit.run(); 1075 | 1076 | //allOrders(access); 1077 | 1078 | //accountInformation(access); 1079 | 1080 | //accountBalance(access); 1081 | 1082 | //performanceNewOrderCheckSync(access); 1083 | 1084 | //performanceNewOrderCheckAsync(access); 1085 | 1086 | //performanceNewOrderBatchCheckAsync(access); 1087 | 1088 | //newOrderAsync(access); 1089 | 1090 | //newOrderBatch(access); 1091 | 1092 | //usdFutureDataStream(ApiAccess{ apiFut, secretFut }); 1093 | } 1094 | else 1095 | { 1096 | ApiAccess access{ apiFut, secretFut }; 1097 | 1098 | //takerBuySellVolume(access); 1099 | } 1100 | } 1101 | catch (const std::exception ex) 1102 | { 1103 | logg(ex.what()); 1104 | } 1105 | 1106 | return 0; 1107 | } 1108 | -------------------------------------------------------------------------------- /bfcpp/bfcpptest/bfcpptest.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {4fee7169-b7c3-4fb1-a962-b0ce975d765b} 25 | bfcpp 26 | 10.0 27 | bfcpptest 28 | 29 | 30 | 31 | Application 32 | true 33 | v142 34 | Unicode 35 | 36 | 37 | Application 38 | false 39 | v142 40 | true 41 | Unicode 42 | 43 | 44 | Application 45 | true 46 | v142 47 | NotSet 48 | 49 | 50 | Application 51 | false 52 | v142 53 | true 54 | NotSet 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | false 79 | 80 | 81 | true 82 | 83 | 84 | false 85 | 86 | 87 | true 88 | false 89 | x64-windows-static 90 | 91 | 92 | true 93 | false 94 | x64-windows-static 95 | 96 | 97 | 98 | Level3 99 | true 100 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 101 | true 102 | 103 | 104 | Console 105 | true 106 | 107 | 108 | 109 | 110 | Level3 111 | true 112 | true 113 | true 114 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 115 | true 116 | 117 | 118 | Console 119 | true 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | Level3 127 | true 128 | BOOST_ASIO_HEADER_ONLY;_DEBUG;_CONSOLE;_WIN32_WINNT=_WIN32_WINNT_WIN10;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) 129 | true 130 | $(SolutionDir)..\vcpkg_win\installed\x64-windows-static\include;$(SolutionDir)bfcpplib;%(AdditionalIncludeDirectories) 131 | stdcpp17 132 | MultiThreadedDebug 133 | 134 | 135 | Console 136 | true 137 | PocoFoundationmtd.lib;cpprest_2_10d.lib;crypt32.lib;libssl.lib;libcrypto.lib;zlibd.lib;brotlienc-static.lib;brotlidec-static.lib;brotlicommon-static.lib;Winhttp.lib;$(OutDir)\bfcpplib.lib;%(AdditionalDependencies) 138 | $(SolutionDir)..\vcpkg_win\installed\x64-windows-static\debug\lib;%(AdditionalLibraryDirectories) 139 | 140 | 141 | 142 | 143 | Level3 144 | true 145 | true 146 | true 147 | BOOST_ASIO_HEADER_ONLY;_CONSOLE;_WIN32_WINNT=_WIN32_WINNT_WIN10;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;NDEBUG;%(PreprocessorDefinitions) 148 | true 149 | $(SolutionDir)..\vcpkg_win\installed\x64-windows-static\include;$(SolutionDir)bfcpplib;%(AdditionalIncludeDirectories) 150 | stdcpp17 151 | MultiThreaded 152 | 153 | 154 | Console 155 | true 156 | true 157 | false 158 | PocoFoundationmt.lib;cpprest_2_10.lib;crypt32.lib;libssl.lib;libcrypto.lib;zlib.lib;brotlienc-static.lib;brotlidec-static.lib;brotlicommon-static.lib;Winhttp.lib;$(OutDir)\bfcpplib.lib;%(AdditionalDependencies) 159 | $(SolutionDir)..\vcpkg_win\installed\x64-windows-static\lib;%(AdditionalLibraryDirectories) 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /bfcpp/bfcpptest/bfcpptest.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | 32 | 33 | Source Files 34 | 35 | 36 | Source Files 37 | 38 | 39 | --------------------------------------------------------------------------------