Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e974edfc6e | ||
|
|
3b7740cee0 | ||
|
|
258d4a3fc1 | ||
|
|
c53f6425cd | ||
|
|
e5d77593d1 | ||
|
|
6580f3883b | ||
|
|
a66eae66bb | ||
|
|
090a9dfb17 | ||
|
|
27aa43f44d | ||
|
|
19e087aa69 |
No files matched your search
@@ -37,3 +37,4 @@ trace_data/
|
||||
*.rej
|
||||
/build
|
||||
.clangd/
|
||||
*CMakeDeps.cmake
|
||||
@@ -1,15 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(spark)
|
||||
|
||||
set (USE_ONIX "FALSE")
|
||||
|
||||
if (WIN32)
|
||||
set (platform "windows")
|
||||
else ()
|
||||
set (platform "linux")
|
||||
endif ()
|
||||
|
||||
|
||||
set (EXTERNALS_ROOT "${CMAKE_SOURCE_DIR}/externals/${platform}")
|
||||
set (SPARK_PYTHON "${EXTERNALS_ROOT}/python")
|
||||
set (SPARK_BIN "${EXTERNALS_ROOT}/bin")
|
||||
@@ -43,11 +40,6 @@ else ()
|
||||
-fPIC -Wall -pedantic -Wextra -Werror \
|
||||
-Wno-missing-braces -Wno-ignored-qualifiers \
|
||||
--std=c++17 -fdiagnostics-color")
|
||||
|
||||
if (${USE_ONIX})
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DUSE_ONIX")
|
||||
endif()
|
||||
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -DNDEBUG -O3")
|
||||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
|
||||
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wno-undef")
|
||||
|
||||
@@ -1 +1 @@
|
||||
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
set(sources /home/pfisher/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
+1
-1
Submodule docker/linux updated: 78ed1605aa...7258a028d7.
@@ -0,0 +1,26 @@
|
||||
2.14.0
|
||||
- Load Algos in libspark.so automatically (no symlink needed)
|
||||
- Specify commands to run when sparkd starts. These will automatically be killed when sparkd terminates if they are still running.
|
||||
|
||||
1.5.7
|
||||
- Fixed Aggressor coloring and ladder high/low coloring in SIM
|
||||
|
||||
1.4.2
|
||||
- Be sure the system has a bts user before deployment. bts is the superuser, he has all permissions and can see all market data
|
||||
- After deployment re-permission all existing users for trading, riskadmin, and/or md admin permissions as appropriate
|
||||
- Also note which users can add other users and accounts and permission them appropriately (Can Add?)
|
||||
- Permission users for appropriate market data entitlements; this has monetary consequences so ask Kevin if you aren't sure.
|
||||
- MIC reporting needs to be added to audit Jenkins job (https://bluetradesys.atlassian.net/browse/SPARK-1900)
|
||||
|
||||
1.3.5
|
||||
- Change in market data wire format. Old shell will get only direct market data instead of merged which may not match what the servers is driving from.
|
||||
|
||||
1.3.4
|
||||
- Changed default logging config for Windows. If Logging config is overridden for a system it should be adjusted to override only linux.
|
||||
|
||||
1.1.0
|
||||
- With this release we dropped wild cards from limits, so any user
|
||||
that has limits expressed in terms of wild cards will have to have
|
||||
those re-entered explicitly
|
||||
- With this release we add per user location and smp id tags. At least
|
||||
the location will have to be set through the admin UI for every user
|
||||
@@ -10,16 +10,18 @@ using System.Threading.Tasks;
|
||||
namespace mbts.ui {
|
||||
[Export(typeof(IShellDaemon))]
|
||||
class RecenterDaemon : IShellDaemon, IDisposable {
|
||||
static readonly TimeSpan _time_to_center = TimeSpan.FromSeconds(5);
|
||||
bool _run = true;
|
||||
readonly IBus _bus;
|
||||
readonly TimeSpan _time_to_center;
|
||||
DateTime _last_recenter;
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public RecenterDaemon(IBus bus) {
|
||||
_bus = bus;
|
||||
_time_to_center = TimeSpan.FromSeconds(config.Configuration.get<bts.config.LadderKnobs>().seconds_to_recenter);
|
||||
_last_recenter = DateTime.UtcNow;
|
||||
|
||||
_dispose = _bus.subscribe(SparkTags.RequestRecenterLadder).Subscribe(_ => trigger_recenter(SparkTags.RecenterLadderNow));
|
||||
run();
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
set(sources files/audit_ilink3.py;files/audit_upload.sh;files/audit_uploader.py;files/example.conf;files/futures.coconut.xz;files/onixs_ice/ConnectivityConfiguration.APITest.xml;files/onixs_ice/ConnectivityConfiguration.APITest.xml.orig;files/onixs_ice/ConnectivityConfiguration.PerfTest.xml;files/onixs_ice/ConnectivityConfiguration.Production.xml;files/perf_trace/compiled.css;files/perf_trace/index.html;files/perf_trace/jquery-3.3.1.min.js;files/perf_trace/main.js;files/perf_trace/perf_log.sql;files/perf_trace/perf_trace_analysis.sql;files/perf_trace/plotly-latest.min.js)
|
||||
set(sources files/audit_ilink3.py;files/audit_upload.sh;files/audit_uploader.py;files/cert_secure_logon.txt;files/example.conf;files/futures.coconut.xz;files/onixs_ice/ConnectivityConfiguration.APITest.xml;files/onixs_ice/ConnectivityConfiguration.APITest.xml.orig;files/onixs_ice/ConnectivityConfiguration.PerfTest.xml;files/onixs_ice/ConnectivityConfiguration.Production.xml;files/perf_trace/compiled.css;files/perf_trace/index.html;files/perf_trace/jquery-3.3.1.min.js;files/perf_trace/main.js;files/perf_trace/perf_log.sql;files/perf_trace/perf_trace_analysis.sql;files/perf_trace/plotly-latest.min.js)
|
||||
@@ -0,0 +1,19 @@
|
||||
Session ID Access ID Secret Key Creation Date Expiration Date Environment
|
||||
|
||||
M4D lLtjH74y1Kh5XUJTLyH2 f1K_tROQp2lTIplVp34WxKa7xDUgfFnIteolfik061o 2019-09-03 2020-09-03 CERT
|
||||
|
||||
MC1 VEMlkDuLz74OVHSuacQB IuHyYT3b-9KUo3WsuXykt-fXsjzp7L-JOrdCSQZLNl0 2019-09-03 2020-09-03 CERT
|
||||
|
||||
MC5 77lzSUwPLlM9obaXh2mK z6aA9qfASDKfZkYTVKFqJd-78i0HZ2Q9ec3CKCYIqHg 2019-09-03 2020-09-03 CERT
|
||||
|
||||
MC7 RphzI3rn71oGUEiYnX9e y6OvvPPyoyj2S8YAnE1gFduXT8v29HIMjO5EngYxSNg 2019-09-03 2020-09-03 CERT
|
||||
|
||||
MC8 PYBIm2TI9gFPIxmHbuYl 6o-ZolGH2DdeQOlGtJ5Zqoh_kg7VdbxkmjeOHmigKwU 2019-09-03 2020-09-03 CERT
|
||||
|
||||
NGG RYcigz3bSLIIBBF2A2Hq qB1dm9VkTKeXcdQpFzla8B3_jwTgya_-Fk5iyEJvr2E 2020-06-15 2021-06-15 NEWRELEASE
|
||||
|
||||
NGF o4OTmmTLQ7Lv4g6bCm26 HbD3FneMyb9Fu-FkCDLscCczHqFcFE2PyWv978RXGyA 2019-12-20 2020-12-20 NEWRELEASE
|
||||
|
||||
UZE EeFdZ1FPepk6U4k3xBZA 2LjEtdLvBLLB1QuDJ4KiJW6M4dF6IHWd_UKmo7p3_Us 2020-06-24 2021-06-24 NEWRELEASE
|
||||
|
||||
VAE Y8iqatTewuoHX16gghw8 FNcCB3Ks1qleS56quYzH4V7zAu78YY2bCdLRi9tmU9E 2020-06-24 2021-06-24 NEWRELEASE
|
||||
@@ -15,8 +15,8 @@
|
||||
<server name="default">
|
||||
<ip>63.247.113.163</ip>
|
||||
<port>3000</port>
|
||||
<userName></userName>
|
||||
<password></password>
|
||||
<userName>katas2f-ps</userName>
|
||||
<password>Starts123</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
<localNetworkInterface></localNetworkInterface>
|
||||
@@ -24,8 +24,8 @@
|
||||
<server name="endex">
|
||||
<ip>63.247.113.77</ip>
|
||||
<port>3000</port>
|
||||
<userName>Value from ICE Support</userName>
|
||||
<password>Value from ICE Support</password>
|
||||
<userName>katas2f-ps</userName>
|
||||
<password>Starts123</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
<localNetworkInterface></localNetworkInterface>
|
||||
@@ -33,8 +33,8 @@
|
||||
<server name="liffe-eu">
|
||||
<ip>63.247.113.229</ip>
|
||||
<port>3000</port>
|
||||
<userName></userName>
|
||||
<password></password>
|
||||
<userName>bluetrdl-ps</userName>
|
||||
<password>Starts123</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
<localNetworkInterface></localNetworkInterface>
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
<server name="default">
|
||||
<ip>63.247.113.214</ip>
|
||||
<port>3000</port>
|
||||
<userName></userName>
|
||||
<password></password>
|
||||
<userName>bt_ps</userName>
|
||||
<password>Starts123</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
<localNetworkInterface></localNetworkInterface>
|
||||
|
||||
@@ -1 +1 @@
|
||||
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/AlgoTypes.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cLine truncated
|
||||
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/AlgoTypes.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cLine truncated
|
||||
@@ -319,7 +319,7 @@ if __name__ == "__main__":
|
||||
|
||||
if DEBUG:
|
||||
for f in response.file:
|
||||
print f.content
|
||||
print (f.content)
|
||||
else:
|
||||
output = response.SerializeToString()
|
||||
sys.stdout.write(output)
|
||||
|
||||
@@ -1645,9 +1645,9 @@ void AdminServiceUtils::bootstrap_data (bts::api::admin::AdminData& data){
|
||||
|
||||
exchange_user_data.mutable_ice_data()->set_operator_id("op_id_" + user_1->name());
|
||||
exchange_user_data.mutable_ice_data()->set_company_id(51251);
|
||||
exchange_user_data.mutable_ice_data()->set_authorized_trader("");
|
||||
exchange_user_data.mutable_ice_data()->set_authorized_trader("bluetrd-fx");
|
||||
exchange_user_data.mutable_ice_data()->set_authorized_group("Group");
|
||||
exchange_user_data.mutable_ice_data()->set_password("");
|
||||
exchange_user_data.mutable_ice_data()->set_password("Starts123");
|
||||
exchange_user_data.mutable_ice_data()->set_mifid_direct_electronic_access(-1);
|
||||
exchange_user_data.mutable_ice_data()->set_mifid_trading_capacity(-1);
|
||||
exchange_user_data.mutable_ice_data()->set_mifid_liquidity_provision(-1);
|
||||
@@ -1666,9 +1666,9 @@ void AdminServiceUtils::bootstrap_data (bts::api::admin::AdminData& data){
|
||||
|
||||
exchange_user_data.mutable_ice_data()->set_operator_id("op_id_" + user_2->name());
|
||||
exchange_user_data.mutable_ice_data()->set_company_id(51251);
|
||||
exchange_user_data.mutable_ice_data()->set_authorized_trader("");
|
||||
exchange_user_data.mutable_ice_data()->set_authorized_trader("bluetrd-fx");
|
||||
exchange_user_data.mutable_ice_data()->set_authorized_group("Group");
|
||||
exchange_user_data.mutable_ice_data()->set_password("");
|
||||
exchange_user_data.mutable_ice_data()->set_password("Starts123");
|
||||
exchange_user_data.mutable_ice_data()->set_mifid_direct_electronic_access(-1);
|
||||
exchange_user_data.mutable_ice_data()->set_mifid_trading_capacity(-1);
|
||||
exchange_user_data.mutable_ice_data()->set_mifid_liquidity_provision(-1);
|
||||
@@ -1688,9 +1688,9 @@ void AdminServiceUtils::bootstrap_data (bts::api::admin::AdminData& data){
|
||||
|
||||
exchange_user_data.mutable_ice_data()->set_operator_id("op_id_" + user_3->name());
|
||||
exchange_user_data.mutable_ice_data()->set_company_id(51251);
|
||||
exchange_user_data.mutable_ice_data()->set_authorized_trader("");
|
||||
exchange_user_data.mutable_ice_data()->set_authorized_trader("bluetrd-fx");
|
||||
exchange_user_data.mutable_ice_data()->set_authorized_group("Group");
|
||||
exchange_user_data.mutable_ice_data()->set_password("");
|
||||
exchange_user_data.mutable_ice_data()->set_password("Starts123");
|
||||
exchange_user_data.mutable_ice_data()->set_mifid_direct_electronic_access(-1);
|
||||
exchange_user_data.mutable_ice_data()->set_mifid_trading_capacity(-1);
|
||||
exchange_user_data.mutable_ice_data()->set_mifid_liquidity_provision(-1);
|
||||
|
||||
@@ -1,143 +1,136 @@
|
||||
#
|
||||
# Common Market data options
|
||||
#
|
||||
|
||||
MarketDataConfig {
|
||||
rule: "DeploymentType.type == 'PROD'"
|
||||
value {
|
||||
use_sim: false
|
||||
}
|
||||
}
|
||||
|
||||
MarketDataConfig {
|
||||
value {
|
||||
use_sim: true
|
||||
use_onixs: false
|
||||
}
|
||||
}
|
||||
|
||||
PublisherOptions {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
disable_heartbeat: true
|
||||
}
|
||||
}
|
||||
|
||||
PlatformMap {
|
||||
value {
|
||||
glbx_platform: "PLATFORM_MDP3ONIXS"
|
||||
ice_platform: "PLATFORM_ICEONIXS"
|
||||
}
|
||||
}
|
||||
|
||||
PublisherNamespace {
|
||||
value {
|
||||
shared_memory_namespace: @System.os_user
|
||||
}
|
||||
}
|
||||
|
||||
CMEOptions {
|
||||
value {
|
||||
feed_engine {
|
||||
name: "mdp3feed"
|
||||
# udp_socket_buffer_size: 425984
|
||||
# channels_per_thread: 1
|
||||
spin_time: 0
|
||||
}
|
||||
channel {
|
||||
id: "*"
|
||||
feed_engine: "mdp3feed"
|
||||
use_tcp_replay: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Common Market data options
|
||||
#
|
||||
|
||||
MarketDataConfig {
|
||||
rule: "DeploymentType.type == 'PROD'"
|
||||
value {
|
||||
use_sim: false
|
||||
}
|
||||
}
|
||||
|
||||
PublisherOptions {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
disable_heartbeat: true
|
||||
}
|
||||
}
|
||||
|
||||
PlatformMap {
|
||||
value {
|
||||
glbx_platform: "PLATFORM_MDP3ONIXS"
|
||||
ice_platform: "PLATFORM_ICEONIXS"
|
||||
}
|
||||
}
|
||||
|
||||
PublisherNamespace {
|
||||
value {
|
||||
shared_memory_namespace: @System.os_user
|
||||
}
|
||||
}
|
||||
|
||||
CMEOptions {
|
||||
value {
|
||||
feed_engine {
|
||||
name: "mdp3feed"
|
||||
# udp_socket_buffer_size: 425984
|
||||
# channels_per_thread: 1
|
||||
spin_time: 0
|
||||
}
|
||||
channel {
|
||||
id: "*"
|
||||
feed_engine: "mdp3feed"
|
||||
use_tcp_replay: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ICEPublisherOptions {
|
||||
value {
|
||||
network_interface: "eth1"
|
||||
}
|
||||
}
|
||||
|
||||
#to make this work in dev clone the operations repo
|
||||
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
|
||||
#modify the easyTunnelProxy.properties file to replace
|
||||
#tunnel.multicast.interface=10.96.18.37 with your local ip
|
||||
#run easyTunnelProxy.bat BRENT-FI for Brent Full implied
|
||||
#you should see ice data
|
||||
|
||||
ICEOptions {
|
||||
value {
|
||||
channel {
|
||||
name: "oil" #these names are arbitrary and are for debugging
|
||||
market_type: 3
|
||||
uds_market_type: 3
|
||||
market_type: 4 #ICE IPE Gas Oil Futures
|
||||
uds_market_type: 4
|
||||
market_type: 5 #ICE Futures Brent, IPE Brent Futures
|
||||
uds_market_type: 5
|
||||
market_type: 9 #ICE Futures WTI, ICE WTI Crude Futures
|
||||
uds_market_type: 9
|
||||
market_type: 13 #ICE Heating Oil Futures
|
||||
uds_market_type: 13
|
||||
market_type: 15 #ICE NYH (RBOB) Gasoline Futures
|
||||
uds_market_type: 15
|
||||
##market_type: 21 #ICE Futures US Agricultures, Coffee C
|
||||
##uds_market_type: 21
|
||||
##market_type: 23 #ICE Futures US Agricultures, FCOJ A
|
||||
##uds_market_type: 23
|
||||
|
||||
##market_type: 26 #ICE Futures US Financials DX
|
||||
##uds_market_type: 26
|
||||
|
||||
uds_market_type: 11 #ICE WTI-Brent spreads
|
||||
uds_market_type: 14 #ICE Heating Oil/WTI Futures Crack
|
||||
uds_market_type: 16 #ICE NYH (RBOB) Gasoline/WTI Futures Crack
|
||||
uds_market_type: 58 #ICE Futures UI Heating Oil / Brent
|
||||
uds_market_type: 59 #ICE NYH (RBOB) Gasoline/Brent Futures Crack
|
||||
uds_market_type: 66 #ICE NYH (RBOB) Gasoline/Heating Oil Spread
|
||||
uds_market_type: 70 #ICE Heating Oil/LS Gasoil Futures Spread
|
||||
uds_market_type: 71 #ICE LS Gasoil/Brent Futures Crack
|
||||
uds_market_type: 72 #ICE NYH (RBOB) Gasoline/LS Gasoil Futures Spread
|
||||
|
||||
#onixs_log_mode: Debug
|
||||
}
|
||||
# channel {
|
||||
# name: "juice"
|
||||
# market_type: 26 #ICE Futures US Agricultures, FCOJ A
|
||||
# }
|
||||
ice_config_file: "ConnectivityConfiguration.APITest.xml"
|
||||
#ice_config_file: "ConnectivityConfiguration.PerfTest.xml"
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
delay_seconds: 0
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
value {
|
||||
delay_seconds: 120
|
||||
standard {
|
||||
start: "16:30"
|
||||
end: "16:40"
|
||||
}
|
||||
sunday {
|
||||
start: "14:00"
|
||||
end: "14:10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayFactorOverride {
|
||||
value {
|
||||
override {
|
||||
symbol: "SOM"
|
||||
exchange: "GLBX"
|
||||
type: "SPREAD"
|
||||
price_multiplier: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
#to make this work in dev clone the operations repo
|
||||
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
|
||||
#modify the easyTunnelProxy.properties file to replace
|
||||
#tunnel.multicast.interface=10.96.18.37 with your local ip
|
||||
#run easyTunnelProxy.bat BRENT-FI for Brent Full implied
|
||||
#you should see ice data
|
||||
|
||||
ICEOptions {
|
||||
value {
|
||||
channel {
|
||||
name: "oil" #these names are arbitrary and are for debugging
|
||||
market_type: 3
|
||||
uds_market_type: 3
|
||||
market_type: 4 #ICE IPE Gas Oil Futures
|
||||
uds_market_type: 4
|
||||
market_type: 5 #ICE Futures Brent, IPE Brent Futures
|
||||
uds_market_type: 5
|
||||
market_type: 9 #ICE Futures WTI, ICE WTI Crude Futures
|
||||
uds_market_type: 9
|
||||
market_type: 13 #ICE Heating Oil Futures
|
||||
uds_market_type: 13
|
||||
market_type: 15 #ICE NYH (RBOB) Gasoline Futures
|
||||
uds_market_type: 15
|
||||
##market_type: 21 #ICE Futures US Agricultures, Coffee C
|
||||
##uds_market_type: 21
|
||||
##market_type: 23 #ICE Futures US Agricultures, FCOJ A
|
||||
##uds_market_type: 23
|
||||
|
||||
##market_type: 26 #ICE Futures US Financials DX
|
||||
##uds_market_type: 26
|
||||
|
||||
uds_market_type: 11 #ICE WTI-Brent spreads
|
||||
uds_market_type: 14 #ICE Heating Oil/WTI Futures Crack
|
||||
uds_market_type: 16 #ICE NYH (RBOB) Gasoline/WTI Futures Crack
|
||||
uds_market_type: 58 #ICE Futures UI Heating Oil / Brent
|
||||
uds_market_type: 59 #ICE NYH (RBOB) Gasoline/Brent Futures Crack
|
||||
uds_market_type: 66 #ICE NYH (RBOB) Gasoline/Heating Oil Spread
|
||||
uds_market_type: 70 #ICE Heating Oil/LS Gasoil Futures Spread
|
||||
uds_market_type: 71 #ICE LS Gasoil/Brent Futures Crack
|
||||
uds_market_type: 72 #ICE NYH (RBOB) Gasoline/LS Gasoil Futures Spread
|
||||
|
||||
#onixs_log_mode: Debug
|
||||
}
|
||||
# channel {
|
||||
# name: "juice"
|
||||
# market_type: 26 #ICE Futures US Agricultures, FCOJ A
|
||||
# }
|
||||
ice_config_file: "ConnectivityConfiguration.APITest.xml"
|
||||
#ice_config_file: "ConnectivityConfiguration.PerfTest.xml"
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
delay_seconds: 0
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
value {
|
||||
delay_seconds: 120
|
||||
standard {
|
||||
start: "16:30"
|
||||
end: "16:40"
|
||||
}
|
||||
sunday {
|
||||
start: "14:00"
|
||||
end: "14:10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayFactorOverride {
|
||||
value {
|
||||
override {
|
||||
symbol: "SOM"
|
||||
exchange: "GLBX"
|
||||
type: "SPREAD"
|
||||
price_multiplier: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+28
-3
@@ -60,10 +60,13 @@ class IntPrice {
|
||||
return delta.mantissa() < 0;
|
||||
}
|
||||
}
|
||||
bool operator <=(const IntPrice& other) {
|
||||
bool operator <=(const IntPrice& other) const {
|
||||
return *this == other || *this < other;
|
||||
}
|
||||
bool operator >=(const IntPrice& other) {
|
||||
bool operator >(const IntPrice& other) const {
|
||||
return other < *this;
|
||||
}
|
||||
bool operator >=(const IntPrice& other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
IntPrice operator - (const IntPrice& o) const {
|
||||
@@ -72,9 +75,31 @@ class IntPrice {
|
||||
IntPrice operator + (const IntPrice& o) const {
|
||||
return op(o, [](int64_t m1, int64_t m2) { return m1 + m2; });
|
||||
}
|
||||
IntPrice& operator +=(const IntPrice& o) {
|
||||
auto temp = *this + o;
|
||||
_mantissa = temp._mantissa;
|
||||
_exponent = temp._exponent;
|
||||
return *this;
|
||||
}
|
||||
IntPrice& operator -=(const IntPrice& o) {
|
||||
auto temp = *this - o;
|
||||
_mantissa = temp._mantissa;
|
||||
_exponent = temp._exponent;
|
||||
return *this;
|
||||
}
|
||||
IntPrice operator -() {
|
||||
return IntPrice(-_mantissa, _exponent);
|
||||
}
|
||||
IntPrice operator * (const IntPrice& other) const {
|
||||
return op(other, [](int64_t m1, int64_t m2) { return m1 * m2; });
|
||||
}
|
||||
IntPrice operator * (const int& other) const {
|
||||
return IntPrice(_mantissa * other, _exponent);
|
||||
}
|
||||
IntPrice& operator *=(const int& other) {
|
||||
_mantissa *= other;
|
||||
return *this;
|
||||
}
|
||||
IntPrice operator / (double v) const {
|
||||
return { (int64_t)(_mantissa / v), exponent() };
|
||||
}
|
||||
@@ -183,4 +208,4 @@ inline std::ostream& operator<<(std::ostream& os, const IntPrice& p) {
|
||||
return os;
|
||||
}
|
||||
|
||||
}}
|
||||
}}
|
||||
@@ -1,4 +1,3 @@
|
||||
#ifdef USE_ONIX
|
||||
#ifndef _WIN32
|
||||
#include "ICEUtils.h"
|
||||
|
||||
@@ -130,4 +129,3 @@ const std::vector<ICEChannel> get_configured_ice_channels() {
|
||||
|
||||
}}} //namespace
|
||||
#endif
|
||||
#endif
|
||||
Vendored
+14
@@ -13,6 +13,20 @@ namespace md {
|
||||
// when we write it out in MDP3main.cpp
|
||||
const char * onixs_license[] = {
|
||||
"<license version=\"1.0\">",
|
||||
" <type>production</type>",
|
||||
" <customer>",
|
||||
" <name>Blue Trading Systems OF 9420-2</name>",
|
||||
" </customer>",
|
||||
" <period>",
|
||||
" <since>2020-05-01</since>",
|
||||
" <till>2021-05-12</till>",
|
||||
" </period>",
|
||||
" <products>",
|
||||
" <product id=\"4\" name=\"CME Market Data Handler for C++\" />",
|
||||
" <product id=\"7\" name=\"ICE iMpact Multicast Price Feed Handler for C++\" />",
|
||||
" <product id=\"67\" name=\"Eurex Market and Reference Data Interfaces (EMDI, RDI) Handler for C++\" />",
|
||||
" </products>",
|
||||
" <checksum>1C9210495AA3C9C884B7A37A4C7AD8F9</checksum>",
|
||||
"</license>"
|
||||
};
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ void GLBXiLink3ExecutionGateway::on_admin_data(
|
||||
const mercury::Update<NewAdminDataEvent> &) {}
|
||||
|
||||
void GLBXiLink3ExecutionGateway::send_msgs(std::vector<OrderMsg> const &msgs,
|
||||
trace::CorID) {
|
||||
trace::CorID corid) {
|
||||
|
||||
if (me->state == State::READY) {
|
||||
for (auto &m : msgs) {
|
||||
@@ -248,6 +248,11 @@ void GLBXiLink3ExecutionGateway::send_msgs(std::vector<OrderMsg> const &msgs,
|
||||
// CancelReplace
|
||||
auto cancel_replace_ptr = std::get_if<CancelReplaceOrderMsg>(&m);
|
||||
if (cancel_replace_ptr) {
|
||||
LOG(iLink3,Info,"Cancel Replace. CorID:{}.{} Order:{}",
|
||||
log::CharPtrWrapper{corid.channel},
|
||||
corid.sn,
|
||||
cancel_replace_ptr->exchange_order_id);
|
||||
|
||||
me->gw->send_cancel_replace(*cancel_replace_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
* iLink3 Reference Documentation
|
||||
** Overview
|
||||
The iLink3 work leverages and extends the iLink2 functionality. Most of the order management logic is unchanged from iLink2 with the substantial changes being confined to session management and encoding/decoding. In an effort to facilitate future addition of new execution protocols a set of protocol independent data structures and methods was added.
|
||||
** Main classes
|
||||
*** GLBXNativeorder
|
||||
GLBXNativeOrder implements the common iLink2 and iLink3 order management logic, including status updates, buffering of cancel/updates, recovery from disk, handling of execution reports, special order handling and routing. This is essentially unchanged from iLink2. From the point of view of this class the differences between iLink2 and iLink2 are delegated to the IGLBXOrderEncoder interface. At run time depending on the type of connection used an implementation of this interface for iLink2 or iLink3 is selected.
|
||||
*** IGLBXOrderEncoder
|
||||
Implementations of the IGLBXOrderEncoder interface below realize wire protocol specific implementations of encoding allowing GLBXNativeOrder to manage both iLink2 and iLink3 orders. The interface provides methods for creating,updating, canceling and dispatching orders. Currently there are two concrete implementations of this interface: iLink2OrderEncoder and iLink3OrderEncoder.
|
||||
|
||||
#+BEGIN_SRC c++
|
||||
class IGLBXOrderEncoder {
|
||||
public:
|
||||
virtual ~IGLBXOrderEncoder() {}
|
||||
virtual void init(OrderKey key, api::orderman::RoutingRecord const &,
|
||||
api::Security const &security,
|
||||
api::orderman::OrderDefinition const &def,
|
||||
int price_encoding_precision,
|
||||
bool stop_order_has_been_triggered) = 0;
|
||||
|
||||
virtual void update_exchange_order_id(ExchangeOrderIDVar const&) = 0;
|
||||
|
||||
virtual void update_create_order_encoding(Price p, int price_precision,
|
||||
Quantity q, OrderServices &) = 0;
|
||||
|
||||
virtual void update_cancel_replace_stop_px(Price stop_px,
|
||||
int price_precision) = 0;
|
||||
virtual void update_cancel_replace_max_show(Quantity max_show) = 0;
|
||||
virtual void update_cancel_replace_encoding(Price p, int price_precision,
|
||||
Quantity q, OrderServices &) = 0;
|
||||
virtual void update_cancel_replace_encoding(Quantity q, OrderServices &) = 0;
|
||||
virtual void update_cancel_replace_encoding(Price p,
|
||||
int price_precision,
|
||||
OrderServices &) = 0;
|
||||
virtual void update_cancel_encoding(OrderServices&) = 0;
|
||||
virtual void send_create(OrderServices&,SessionServices&) = 0;
|
||||
virtual void send_cancel_replace(OrderServices&,SessionServices&) = 0;
|
||||
virtual void send_cancel(OrderServices&,SessionServices&) = 0;
|
||||
};
|
||||
|
||||
#+END_SRC
|
||||
*** class iLink2OrderEncoder : public IGLBXOrderEncoder
|
||||
iLink2OrderEncoder provides FIX based encoding of GLBX orders. This logic is carried over essentially unchanged from previous versions of Spark with care being taken to re-use as much as possible elements of previous encodings on every call. The generated FIX messages are forwarded, as FIX, to the OrderServices::queue_fix template member function:
|
||||
|
||||
#+BEGIN_SRC c++
|
||||
void queue_fix(SessionServices &ss, jetfix::MessageTypes mt,
|
||||
jetfix::FIXFragment const &f) {
|
||||
ss.queue_fix(mt, f, corid());
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
*** class iLink3OrderEncoder : public IGLBXOrderEncoder
|
||||
iLink3OrderEncoder provides a thin adapter that accepts order encoding requests from GLBXNativeOrder and maps those requests to the protocol agnostic order management data structures: CreateOrderMsg, CancelReplaceOrderMsg and CancelOrderMsg.
|
||||
|
||||
After this mapping, iLink3OrderEncoder forwards the order operation in question to its OrderServices instance, through the protocol independent queue_order_msg template function:
|
||||
|
||||
#+BEGIN_SRC c++
|
||||
template <class OP>
|
||||
void queue_order_msg (SessionServices &ss, OP const&msg) {
|
||||
ss.queue_order_msg(msg,corid());
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
*** Protocol independent order operation data structures
|
||||
The simple data structures in IExecutionGateway.h provide an exchange protocol independent way of representing order operations. They are intended to facilitate the addition of new exchange protocols. They are as follows:
|
||||
|
||||
#+BEGIN_SRC c++
|
||||
struct CreateOrderMsg {
|
||||
OrderKey key;
|
||||
Price price;
|
||||
Quantity quantity;
|
||||
api::Security const *security;
|
||||
api::Side side;
|
||||
Price stop_px;
|
||||
Quantity display_quantity;
|
||||
api::orderman::NativeOrderType order_type;
|
||||
api::orderman::TimeInForce time_in_force;
|
||||
api::orderman::ManualOrderIndicator manual_order_indicator;
|
||||
api::orderman::RoutingRecord const *routing_record;
|
||||
};
|
||||
|
||||
struct CancelReplaceOrderMsg {
|
||||
CreateOrderMsg create_fields;
|
||||
ExchangeOrderID64 exchange_order_id;
|
||||
};
|
||||
|
||||
struct CancelOrderMsg {
|
||||
OrderKey key;
|
||||
ExchangeOrderID64 exchange_order_id;
|
||||
api::orderman::ManualOrderIndicator manual_order_indicator;
|
||||
api::Security const *security;
|
||||
api::orderman::RoutingRecord const *routing_record;
|
||||
api::Side side;
|
||||
};
|
||||
|
||||
using OrderMsg =
|
||||
std::variant<CreateOrderMsg, CancelReplaceOrderMsg, CancelOrderMsg>;
|
||||
|
||||
#+END_SRC
|
||||
|
||||
These data structures are used as vocabulary data types understood by all future exchange execution implementation code. They are consumed by implementations of the IExecutionGateway interface.
|
||||
|
||||
*** class IExecutionGateway
|
||||
IExecutionGateway provides an abstraction for the implementation of exchange gateways. It provides methods for both FIX and non FIX based exchanges. Ordinarily implementations of this interface will either provide FIX or non FIX functionality but not both.
|
||||
|
||||
#+BEGIN_SRC c++
|
||||
class IExecutionGateway {
|
||||
public:
|
||||
IExecutionGateway() = default;
|
||||
virtual ~IExecutionGateway() {}
|
||||
|
||||
virtual void start() = 0;
|
||||
virtual void dispatch_incoming_fix_messages() = 0;
|
||||
virtual void on_securities(const mercury::Update<NewSecuritiesEvent> &) = 0;
|
||||
virtual void on_admin_data(const mercury::Update<NewAdminDataEvent> &) = 0;
|
||||
virtual void send_fix(const jetfix::OutFIXPtr&, trace::CorID) = 0;
|
||||
virtual void send_msgs(std::vector<OrderMsg> const&, trace::CorID) = 0;
|
||||
virtual OnExeReport& on_exe_report() = 0;
|
||||
virtual OnGwStatusChange& on_gw_status_change() = 0;
|
||||
virtual OnCancelReject &on_cancel_reject() = 0;
|
||||
private:
|
||||
BTS_NO_COPY(IExecutionGateway);
|
||||
};
|
||||
#+END_SRC
|
||||
|
||||
IExecutionGateway implements are responsible for establishing execution sessions as well as mapping to and from protocol independent order operations to their gateway specific implementations. Presently there are three implementations of IExecutionGateway: ICE, iLink2 and iLink3. Only the iLink3 implementation is new.
|
||||
|
||||
*** class GLBXiLink3ExecutionGateway : public IExecutionGateway
|
||||
This class implements provides a facade that hides, coordinates and interfaces with all the components needed to implement an iLink3 gateway including a TCP connection, a persistent store and the actual iLink3 encoding/decoding logic. GLBXiLink3ExecutionGateway role is identical to those of the GLBXExecutionGateway and ICEExecutionGateway, which existed prior to iLink3. In essence GLBXilink3executiongateway is in charge of managing the life-cycle of an iLink3 exchange connection as well as translating between exchange agnostic order operations and the iLink3 specific interfaces. The actual exchange interaction is delegated to the IL3GW class.
|
||||
*** class IL3GW : IL3ConnectionHandler
|
||||
The IL3GW class is responsible for iLink3 session management, including login, logout, recovery and heartbeat response. Additionally this class is responsible for mapping order messages to and from their network binary representation. This class is able to handle network events including connection, disconnection and data arrival via its implementation of the IL3ConnectionHandler interface:
|
||||
|
||||
|
||||
#+BEGIN_SRC c++
|
||||
struct IL3ConnectionHandler {
|
||||
virtual ~IL3ConnectionHandler() ;
|
||||
virtual void on_connect_success() = 0;
|
||||
virtual void on_disconnect() = 0;
|
||||
// Send available data and length. Handler will return what it consumed
|
||||
virtual size_t on_data(const std::string_view&) = 0;
|
||||
};
|
||||
#+END_SRC
|
||||
|
||||
IL3GW integrates these external events with the instructions it receives from the rest of the system through its public interface:
|
||||
|
||||
#+BEGIN_SRC c++
|
||||
void connect();
|
||||
void send_order(const CreateOrderMsg &);
|
||||
void send_cancel_replace(const CancelReplaceOrderMsg &);
|
||||
void send_cancel(const CancelOrderMsg &);
|
||||
void send_terminate(uint16_t code = 0);
|
||||
#+END_SRC
|
||||
|
||||
And provides an integrated high-level view of the connection state via its event callbacks:
|
||||
|
||||
#+BEGIN_SRC c++
|
||||
void subscribe_state(OnStateChange o);
|
||||
void subscribe_execution_reports(OnExecutionReport sub);
|
||||
void subscribe_session(OnSessionChange cb);
|
||||
void subscribe_cancel_reject(OnCancelReject cb);
|
||||
#+END_SRC
|
||||
|
||||
** Code Generation
|
||||
To facilitate encoding IL3GW uses code generated by the CME sponsored tools by real logic. These tools can be found here:
|
||||
|
||||
https://github.com/real-logic/simple-binary-encoding/wiki/Sbe-Tool-Guide
|
||||
|
||||
The CME provides template files that be used as an input to these tools. For more details see:
|
||||
|
||||
https://www.cmegroup.com/confluence/display/EPICSANDBOX/iLink+3+-+Simple+Binary+Encoding
|
||||
|
||||
The generation was done manually and the generated code integrated into source control.
|
||||
|
||||
** Operational Notes
|
||||
- Like iLink2, the iLink3 implementation uses weekly sessions
|
||||
- Sessions are stored under sparkdata/ilink3/session_name/date-of-session
|
||||
- Within each session directory there are two files:
|
||||
- log: is a json log of all messages sent and received from the exchange
|
||||
- seqnum: contains the current sequence numbers for the session. Additionally the session binary name is also represented for use in trouble-shooting
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "BookUtils.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
using Size = bts::md::Size;
|
||||
using Side = bts::api::Side;
|
||||
namespace bts::orderman {
|
||||
using Size = md::Size;
|
||||
using Side = api::Side;
|
||||
using PriceSize = md::PriceSize;
|
||||
|
||||
Size BookUtils::volume_at_or_better(const Book& book, Side side, Price price) {
|
||||
return volume_at_or_better(book, side, price.value());
|
||||
@@ -22,5 +22,154 @@ Size BookUtils::volume_at_or_better(const Book& book, Side side, double price) {
|
||||
SidePriceComp BookUtils::price_comp(Side side) {
|
||||
return SidePriceComp(side);
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
|
||||
inline bool is_empty(const PriceSize& ps) {
|
||||
return ps.size() == 0 || ps.int_price().is_nan();
|
||||
}
|
||||
|
||||
void BookUtils::change_unit_qty(
|
||||
const BookSide& book_in, std::vector<md::PriceSize>& book_out, int unit_qty) {
|
||||
//
|
||||
bts_assert(unit_qty > 0);
|
||||
int mlev = 0, mlev_qty_used = 0;
|
||||
std::vector<md::PriceSize> unit_lev_span;
|
||||
for (uint32_t i = 0; i < book_in.size();) {
|
||||
int mlev_qty_rem = book_in[i].size() - mlev_qty_used;
|
||||
if (mlev_qty_rem == 0) {
|
||||
mlev_qty_used = 0;
|
||||
mlev++;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (unit_lev_span.size() == 0) {
|
||||
md::Size mlev_units_rem = mlev_qty_rem / unit_qty;
|
||||
if (mlev_units_rem > 0) {
|
||||
book_out.emplace_back(book_in[i].int_price(), mlev_units_rem);
|
||||
mlev_qty_used += mlev_units_rem * unit_qty;
|
||||
} else {
|
||||
unit_lev_span.emplace_back(book_in[i].int_price(), mlev_qty_rem);
|
||||
mlev_qty_used = 0;
|
||||
mlev++;
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
// the unit spans across multiple [input]price levels: compute its
|
||||
// [output]price. It will be in its own [output]price level.
|
||||
auto unit_rem = unit_qty;
|
||||
for (auto lev : unit_lev_span)
|
||||
unit_rem -= lev.size();
|
||||
if (mlev_qty_rem >= unit_rem) {
|
||||
mlev_qty_used += unit_rem;
|
||||
auto unit_price = md::INT_NAN;
|
||||
for (uint32_t adj_lev = 0; adj_lev < unit_lev_span.size(); adj_lev++) {
|
||||
auto leg_price = unit_lev_span[adj_lev].int_price() *
|
||||
static_cast<int>(unit_lev_span[adj_lev].size());
|
||||
if (unit_price.is_nan())
|
||||
unit_price = leg_price;
|
||||
else
|
||||
unit_price += leg_price;
|
||||
}
|
||||
unit_lev_span.clear();
|
||||
book_out.emplace_back(unit_price, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BookUtils::compute_synthetic_book(
|
||||
std::vector<PriceSize>& dest,
|
||||
const std::vector<std::tuple<Size, md::IntPrice, BookSide>>&
|
||||
leg_mkts,
|
||||
uint32_t depth) {
|
||||
//
|
||||
if (leg_mkts.size() == 0)
|
||||
return;
|
||||
|
||||
struct Leg {
|
||||
std::vector<PriceSize> book;
|
||||
uint32_t curr_level;
|
||||
Size curr_level_qty_used;
|
||||
int leg_qty;
|
||||
|
||||
Leg() : book(), curr_level(0), curr_level_qty_used(0), leg_qty(0) {}
|
||||
|
||||
md::IntPrice curr_price() const {
|
||||
// Assert that the book isn't 'empty' yet.
|
||||
// Note: one could argue that nan be returned in this event,
|
||||
// but since the loop is designed to return "greedily,"
|
||||
// 'curr_price' should not be accessed after it has been emptied.
|
||||
// This assertion enforces that the behavior is only changed
|
||||
// as a result of deliberate action, rather than an inadvertant
|
||||
// side-effect.
|
||||
bts_assert(!is_empty());
|
||||
return book[curr_level].int_price();
|
||||
}
|
||||
Size curr_level_qty_rem() const {
|
||||
return book[curr_level].size() - curr_level_qty_used;
|
||||
}
|
||||
// The implied market's volume is limited by its thinnest market.
|
||||
// When one leg 'is_empty,' the full volume of the implied market
|
||||
// has been realized.
|
||||
bool is_empty() const { return curr_level >= book.size(); }
|
||||
};
|
||||
|
||||
std::vector<Leg> legs;
|
||||
for (const auto& [qty, price_mult, book] : leg_mkts) {
|
||||
// todo: implement price mult logic (for now just assert nan or 1)
|
||||
bts_assert(price_mult.is_nan() || price_mult.price() == 1);
|
||||
auto& l = legs.emplace_back();
|
||||
bts_assert(qty != 0);
|
||||
l.leg_qty = qty;
|
||||
if (book.empty() || is_empty(book.front()))
|
||||
return;
|
||||
change_unit_qty(
|
||||
book, l.book, std::abs(l.leg_qty));
|
||||
if (l.book.empty())
|
||||
return;
|
||||
}
|
||||
|
||||
auto any_legs_empty = [&]() {
|
||||
for (auto& l : legs)
|
||||
if (l.is_empty())
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
while (!any_legs_empty() && depth-- > 0) {
|
||||
// legs can't be empty, because it is a 1:1 mapping of leg_mkts,
|
||||
// and it has already been established that the latter isn't empty.
|
||||
auto limiting_factor = legs[0].curr_level_qty_rem();
|
||||
for (uint32_t i = 0; i < legs.size(); i++) {
|
||||
if (legs[i].curr_level_qty_rem() == 0) {
|
||||
legs[i].curr_level++;
|
||||
if (legs[i].is_empty()) {
|
||||
// book is complete
|
||||
return;
|
||||
}
|
||||
legs[i].curr_level_qty_used = Size(0);
|
||||
}
|
||||
limiting_factor =
|
||||
std::min(legs[i].curr_level_qty_rem(), limiting_factor);
|
||||
if (limiting_factor == 0)
|
||||
return;
|
||||
}
|
||||
bts_assert(limiting_factor > 0);
|
||||
for (uint32_t i = 0; i < legs.size(); i++)
|
||||
legs[i].curr_level_qty_used += limiting_factor;
|
||||
auto marginal_cost = md::INT_NAN;
|
||||
for (auto& l : legs) {
|
||||
// The book's prices have already been multiplied by abs(leg_qty);
|
||||
// Subtract the leg's cost if qty is negative, otherwise add it
|
||||
// to the synthetic's marginal cost.
|
||||
auto lcost = l.curr_price();
|
||||
if (l.leg_qty < 0)
|
||||
lcost = -lcost;
|
||||
if (marginal_cost.is_nan())
|
||||
marginal_cost = lcost;
|
||||
else
|
||||
marginal_cost += lcost;
|
||||
}
|
||||
dest.emplace_back(marginal_cost, limiting_factor);
|
||||
}
|
||||
}
|
||||
} // namespace bts::orderman
|
||||
@@ -6,13 +6,18 @@
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orderman_utils/PriceComp.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
namespace bts::orderman {
|
||||
struct BookUtils {
|
||||
static md::Size volume_at_or_better(const Book& book, api::Side side, Price price);
|
||||
static md::Size volume_at_or_better(const Book& book, api::Side side, double price);
|
||||
static SidePriceComp price_comp(api::Side side);
|
||||
|
||||
static void change_unit_qty(
|
||||
const BookSide& book_in, std::vector<md::PriceSize>& book_out, int unit_qty);
|
||||
|
||||
static void compute_synthetic_book(
|
||||
std::vector<md::PriceSize>& dest,
|
||||
const std::vector<std::tuple<md::Size, md::IntPrice, BookSide>>& leg_mkts,
|
||||
uint32_t depth);
|
||||
};
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
} // namespace bts::orderman
|
||||
@@ -0,0 +1,376 @@
|
||||
#include "DormanAlgo.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orders/algo/IAlgoServices.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/orderman/orders/algo/utils/Accumulator.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace dorman {
|
||||
|
||||
using namespace api::orderman;
|
||||
|
||||
struct FutureQty {
|
||||
Quantity size;
|
||||
TimeStamp when;
|
||||
};
|
||||
|
||||
struct DormanAlgo::Impl {
|
||||
IAlgoServices *algo = nullptr;
|
||||
AlgoParams params;
|
||||
api::Side side;
|
||||
Quantity exposed_qty = {};
|
||||
std::optional<FutureQty> next_size;
|
||||
|
||||
OrderDefinition splitter_definition;
|
||||
OrderKey child_id;
|
||||
ChildState child_state;
|
||||
|
||||
uint32_t fills ={};
|
||||
|
||||
std::vector<OrderKey> profit_orders;
|
||||
std::optional<Accumulator> trade_accumulator;
|
||||
|
||||
std::optional<TimeStamp> pause_until;
|
||||
};
|
||||
|
||||
DormanAlgo::DormanAlgo() :
|
||||
me(new Impl())
|
||||
{ }
|
||||
DormanAlgo::~DormanAlgo() { }
|
||||
|
||||
OrderOpResult DormanAlgo::do_before_launch(IBeforeLaunchAlgoServices& ibl) {
|
||||
const auto& def = ibl.definition();
|
||||
// Validation
|
||||
bts_assert(def.has_custom_def());
|
||||
bts_assert(def.custom_def().has_dynamic());
|
||||
|
||||
auto params = to_dorman_params(def.custom_def().dynamic());
|
||||
if (auto error = std::get_if<OrderOpResult>(¶ms)) {
|
||||
return *error;
|
||||
}
|
||||
me->params = std::get<AlgoParams>(params);
|
||||
me->side = def.side();
|
||||
if (me->side != api::Side::SIDE_BUY && me->side != api::Side::SIDE_SELL) {
|
||||
return OrderOpResult { ORDER_ERROR_INVALID_ORDER_DEFINITON, "Invalid side" };
|
||||
}
|
||||
|
||||
if (me->params.trade_accumulation) {
|
||||
auto& t = me->params.trade_accumulation.value();
|
||||
me->trade_accumulator = {Accumulator {t.contracts, t.seconds, ibl.get_time()}};
|
||||
}
|
||||
|
||||
CustomOrderDefinition default_order;
|
||||
default_order.mutable_limit();
|
||||
auto child_def =
|
||||
get_if<CustomOrderDefinition>(def.custom_def().dynamic().order_params(), "Child")
|
||||
.value_or(default_order);
|
||||
|
||||
update_child_template(ibl, child_def);
|
||||
|
||||
if (ibl.is_attaching()) {
|
||||
return OrderOpResult{ORDER_ERROR_OP_NOT_SUPPORTED,
|
||||
"This algo does not support attach."};
|
||||
}
|
||||
|
||||
return {/*ok*/};
|
||||
}
|
||||
|
||||
|
||||
void DormanAlgo::do_launch(IAlgoServices& algo) {
|
||||
me->algo = &algo;
|
||||
auto skey = SecurityKey(algo.security().id());
|
||||
algo.subscribe_book(skey);
|
||||
algo.subscribe_timer();
|
||||
if (me->trade_accumulator) {
|
||||
algo.subscribe_ticker(skey);
|
||||
}
|
||||
}
|
||||
|
||||
void DormanAlgo::do_launch_attached(IAlgoServices&, OrderKey, ChildState) {
|
||||
bts_assert(false);
|
||||
}
|
||||
void DormanAlgo::do_update(const api::orderman::Update& update) {
|
||||
LOG(OrderMan, Diag, "Dorman order {} receiving update: {}",
|
||||
algo().order_key().value(), utils::pb::to_string(update));
|
||||
|
||||
if (update.has_custom()) {
|
||||
if (!update.custom().has_dynamic()) {
|
||||
algo().cancel("Update cannot change order type");
|
||||
return;
|
||||
}
|
||||
auto params = to_dorman_params(update.custom().dynamic());
|
||||
|
||||
if (auto error = std::get_if<OrderOpResult>(¶ms)) {
|
||||
algo().cancel(error->error_msg);
|
||||
}
|
||||
|
||||
me->params = std::get<AlgoParams>(params);
|
||||
auto skey = SecurityKey(algo().security().id());
|
||||
if (me->params.trade_accumulation && !me->trade_accumulator) {
|
||||
algo().subscribe_ticker(skey);
|
||||
auto& t = me->params.trade_accumulation.value();
|
||||
me->trade_accumulator = {Accumulator {t.contracts, t.seconds, algo().get_time()}};
|
||||
}
|
||||
else if (!me->params.trade_accumulation && me->trade_accumulator) {
|
||||
algo().unsubscribe_ticker(skey);
|
||||
me->trade_accumulator = std::nullopt;
|
||||
}
|
||||
else if (me->params.trade_accumulation &&
|
||||
me->trade_accumulator &&
|
||||
(me->trade_accumulator.value().threshold != me->params.trade_accumulation.value().contracts ||
|
||||
me->trade_accumulator.value().bucket_interval != std::chrono::seconds(me->params.trade_accumulation.value().seconds))
|
||||
) {
|
||||
// accumulator params have changed
|
||||
auto& t = me->params.trade_accumulation.value();
|
||||
me->trade_accumulator = {Accumulator {t.contracts, t.seconds, algo().get_time()}};
|
||||
}
|
||||
|
||||
// Check for child
|
||||
auto child = get_if<CustomOrderDefinition>(update.custom().dynamic().order_params(), "Child");
|
||||
if (child) {
|
||||
update_child_template(algo(), child.value());
|
||||
}
|
||||
}
|
||||
|
||||
if (me->fills >= update.quantity()) {
|
||||
complete();
|
||||
}
|
||||
else {
|
||||
// In case of price/qty change always update the child
|
||||
// NOTE: it might be useful to cache the available market data size
|
||||
// and update based on that if the update contains a size table change
|
||||
update_child(me->exposed_qty);
|
||||
}
|
||||
}
|
||||
|
||||
void DormanAlgo::on_child_change(OrderKey key, const ChildState& state) {
|
||||
if (key != me->child_id) return;
|
||||
|
||||
if (state.status == ChildStatus::CANCELED && !is_paused()) {
|
||||
algo().cancel("Unexpected child cancel");
|
||||
return;
|
||||
}
|
||||
|
||||
auto delta = state.total_fills.value() - me->child_state.total_fills.value();
|
||||
if (delta != 0) {
|
||||
me->fills += delta;
|
||||
algo().update_fill_state(Quantity {me->fills}, state.last_price);
|
||||
send_profit_order(state.last_price, Quantity {delta});
|
||||
}
|
||||
me->child_state = state;
|
||||
|
||||
if (me->params.scratch &&
|
||||
me->fills >= me->params.scratch.value().max_overfill) {
|
||||
do_scratch();
|
||||
}
|
||||
|
||||
if (me->fills >= algo().quantity().value() || state.status == ChildStatus::COMPLETED) {
|
||||
complete();
|
||||
}
|
||||
}
|
||||
|
||||
void DormanAlgo::on_book(SecurityKey key, const Book& book) {
|
||||
if (key.value() != me->algo->security().id()) { return; }
|
||||
auto price = me->algo->price();
|
||||
auto tick = me->algo->security().contract_spec().low_tick();
|
||||
|
||||
// Determine available size on the market
|
||||
auto& side = book.side(me->side);
|
||||
uint32_t available = 0;
|
||||
for (const auto& level : side) {
|
||||
if (securities::test_price_equality(level.price(), price.value(), tick)) {
|
||||
available += level.size();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the step_table
|
||||
auto target = Quantity {0};
|
||||
if (available > 0) {
|
||||
for (const auto& entry : me->params.steps.step_table) {
|
||||
if (std::get<0>(entry) > available) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
target = Quantity { std::get<1>(entry) };
|
||||
}
|
||||
}
|
||||
}
|
||||
target = Quantity { std::min(target.value(), me->params.steps.maximum) };
|
||||
|
||||
// First on_book
|
||||
if (!child_active()) {
|
||||
update_child(target);
|
||||
}
|
||||
// See if we need to adjust
|
||||
else {
|
||||
if (target == me->exposed_qty) {
|
||||
me->next_size = std::nullopt;
|
||||
}
|
||||
// If target < size reduce immediately
|
||||
else if (target < me->exposed_qty) {
|
||||
update_child(target);
|
||||
me->next_size = std::nullopt;
|
||||
}
|
||||
// if target > size schedule increase
|
||||
else if (target > me->exposed_qty) {
|
||||
if (me->next_size) {
|
||||
// already planning to increase, update the target
|
||||
me->next_size.value().size = target;
|
||||
}
|
||||
else {
|
||||
// no existing scheduled increase, create one now
|
||||
me->next_size = { FutureQty { target, me->algo->get_time() + std::chrono::seconds(me->params.steps.trail_seconds) } };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void DormanAlgo::on_timer() {
|
||||
// Check for pending size changes
|
||||
auto now = algo().get_time();
|
||||
if (me->pause_until &&
|
||||
now >= me->pause_until.value()) {
|
||||
|
||||
resume();
|
||||
}
|
||||
if (me->next_size &&
|
||||
now >= me->next_size.value().when) {
|
||||
auto size = me->next_size.value().size;
|
||||
update_child(size);
|
||||
me->next_size = std::nullopt;
|
||||
}
|
||||
}
|
||||
void DormanAlgo::on_ticker(SecurityKey, const TradeVector& vec) {
|
||||
if (me->trade_accumulator) {
|
||||
size_t contracts = 0;
|
||||
for (const auto& t : vec) {
|
||||
contracts += t.quantity.value();
|
||||
}
|
||||
auto& acc = me->trade_accumulator.value();
|
||||
acc.push_back(algo().get_time(), contracts);
|
||||
if (acc.exceeded()) {
|
||||
auto delay = std::chrono::seconds(me->params.trade_accumulation.value().sleep_seconds);
|
||||
pause_for(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IAlgoOrder::OnCancelResponse DormanAlgo::on_cancel() {
|
||||
// detach any remaining profit orders so they keep running
|
||||
for(const auto& key : me->profit_orders) {
|
||||
algo().detach_child(key);
|
||||
}
|
||||
return OnCancelResponse::HONOR;
|
||||
}
|
||||
|
||||
IAlgoServices& DormanAlgo::algo() const { return *me->algo; }
|
||||
|
||||
//
|
||||
// private
|
||||
|
||||
void DormanAlgo::update_child(Quantity exposed) {
|
||||
auto& splitter = *me->splitter_definition.mutable_custom_def()->mutable_splitter();
|
||||
splitter.set_max_order_qty(me->params.clip_limit);
|
||||
me->exposed_qty = exposed;
|
||||
splitter.set_exposed_qty(is_paused() ? 0 : exposed.value());
|
||||
|
||||
if (!child_active()) {
|
||||
// launch child
|
||||
me->child_id = algo().launch_child(me->splitter_definition, algo().price(), algo().quantity());
|
||||
}
|
||||
else {
|
||||
Update update;
|
||||
update.set_price(algo().price().value());
|
||||
update.set_quantity(algo().quantity().value());
|
||||
*update.mutable_custom()->mutable_splitter() = splitter;
|
||||
algo().update_child(me->child_id, update);
|
||||
}
|
||||
}
|
||||
|
||||
void DormanAlgo::update_child_template(IBeforeLaunchAlgoServices& ibl, const CustomOrderDefinition& child) {
|
||||
auto child_def = ibl.definition();
|
||||
// Insert a spliter between this and the configured child definition.
|
||||
api::orderman::SplitterOrderDefinition& splitter_def = *child_def.mutable_custom_def()->mutable_splitter();
|
||||
*splitter_def.mutable_child() = child;
|
||||
me->splitter_definition = child_def;
|
||||
}
|
||||
|
||||
Quantity DormanAlgo::exposed_qty() const { return me->exposed_qty; }
|
||||
|
||||
bool DormanAlgo::child_active() const { return me->child_id.value() != 0; }
|
||||
|
||||
void DormanAlgo::do_scratch() {
|
||||
// When the # of fills exceeds the max overfill quantity
|
||||
// We enter fills opposite fills at the fill price and terminate
|
||||
// Use a splitter to bypass clip limit if necessary
|
||||
|
||||
OrderDefinition def = me->splitter_definition;
|
||||
def.set_side(securities::other_side(me->side));
|
||||
def.mutable_custom_def()->mutable_splitter()->set_exposed_qty(me->fills);
|
||||
|
||||
auto price = me->child_state.last_price;
|
||||
auto scratch_id = algo().launch_child(def, price, Quantity { me->fills }, false);
|
||||
|
||||
algo().detach_child(scratch_id);
|
||||
complete();
|
||||
}
|
||||
|
||||
void DormanAlgo::send_profit_order(Price p, Quantity q) {
|
||||
if (me->params.profit_orders) {
|
||||
auto side = securities::other_side(me->side);
|
||||
Price offset =
|
||||
Price { algo().security().contract_spec().low_tick() * me->params.profit_orders.value().ticks_away };
|
||||
auto price =
|
||||
securities::worsen_price(side, p, offset);
|
||||
|
||||
OrderDefinition def = me->splitter_definition;
|
||||
// BTS Simulator does not currently support GTC
|
||||
// Note: This could be made a configurable order type in the algo parameters
|
||||
def.set_time_in_force(TimeInForce::TIME_IN_FORCE_DAY);
|
||||
def.set_side(side);
|
||||
def.mutable_custom_def()->mutable_limit();
|
||||
me->profit_orders.push_back(
|
||||
algo().launch_child(def, price, q, false));
|
||||
}
|
||||
}
|
||||
|
||||
void DormanAlgo::complete() {
|
||||
// Allow profit orders to keep running
|
||||
if (me->params.profit_orders) {
|
||||
for(const auto& key : me->profit_orders) {
|
||||
algo().detach_child(key);
|
||||
}
|
||||
}
|
||||
algo().complete();
|
||||
}
|
||||
|
||||
|
||||
void DormanAlgo::pause_for(Duration d) {
|
||||
if (me->params.trade_accumulation) {
|
||||
if (is_paused()) {
|
||||
me->pause_until = std::max(me->pause_until.value(), { algo().get_time() + d });
|
||||
}
|
||||
else {
|
||||
me->pause_until = { algo().get_time() + d };
|
||||
// When paused child update will always expose 0
|
||||
update_child(me->exposed_qty);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool DormanAlgo::is_paused() const {
|
||||
return (bool)(me->pause_until);
|
||||
}
|
||||
void DormanAlgo::resume() {
|
||||
if (is_paused()) {
|
||||
me->pause_until = std::nullopt;
|
||||
update_child(me->exposed_qty);
|
||||
}
|
||||
}
|
||||
|
||||
}}} //namespace
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <map>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace dorman {
|
||||
|
||||
//
|
||||
// Algo parameters
|
||||
//
|
||||
|
||||
// This defines the market quantity vs the order quantity
|
||||
struct StepParams {
|
||||
std::vector<std::tuple<uint32_t, uint32_t>> step_table;
|
||||
// This is the cap on the maximum quantity,
|
||||
// even though the step table can allow for higher quantities (depending on the
|
||||
// clip size this could be divided across multiple exchange orders)
|
||||
uint32_t maximum;
|
||||
// If the market increases don't follow until it has stayed high for trail_seconds;
|
||||
uint32_t trail_seconds;
|
||||
};
|
||||
|
||||
// If the number of accumulated fills (net of profit orders) touches or goes beyond
|
||||
// this, the fills will be scratched by entering opposite orders at the fill price
|
||||
struct ScratchParams {
|
||||
uint32_t max_overfill;
|
||||
};
|
||||
// If this is enabled, then as fills accumulate, profit GTC orders are
|
||||
// immediately sent N ticks away on the opposite side.
|
||||
struct ProfitOrderParams {
|
||||
uint32_t ticks_away;
|
||||
};
|
||||
// If N contracts trade in T seconds, then the algo is paused (ie child exchange order
|
||||
// is/are cancelled) for X seconds. Once the condition clears order is reentered.
|
||||
struct TradeAccumulationParams {
|
||||
uint32_t contracts;
|
||||
uint32_t seconds;
|
||||
uint32_t sleep_seconds;
|
||||
};
|
||||
struct AlgoParams {
|
||||
StepParams steps;
|
||||
uint32_t clip_limit;
|
||||
std::optional<ScratchParams> scratch;
|
||||
std::optional<ProfitOrderParams> profit_orders;
|
||||
std::optional<TradeAccumulationParams> trade_accumulation;
|
||||
};
|
||||
|
||||
|
||||
class DormanAlgo : public IAlgoOrder {
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> me;
|
||||
IAlgoServices& algo() const;
|
||||
|
||||
public:
|
||||
DormanAlgo();
|
||||
~DormanAlgo();
|
||||
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
void on_book(SecurityKey, const Book&) override;
|
||||
void on_timer() override;
|
||||
void on_ticker(SecurityKey, const TradeVector&) override;
|
||||
IAlgoOrder::OnCancelResponse on_cancel() override;
|
||||
|
||||
Quantity exposed_qty() const;
|
||||
|
||||
private:
|
||||
//
|
||||
// Child management
|
||||
void update_child_template(IBeforeLaunchAlgoServices&, const api::orderman::CustomOrderDefinition&);
|
||||
void update_child(Quantity exposed);
|
||||
bool child_active() const;
|
||||
|
||||
void do_scratch();
|
||||
void send_profit_order(Price, Quantity);
|
||||
void complete();
|
||||
|
||||
void pause_for(Duration);
|
||||
bool is_paused() const;
|
||||
void resume();
|
||||
|
||||
};
|
||||
|
||||
template <class OK,class ERR>
|
||||
using Result = std::variant<OK,ERR>;
|
||||
Result<AlgoParams,OrderOpResult> to_dorman_params(const api::orderman::DynamicOrderDefinition&);
|
||||
|
||||
}}} //namespace
|
||||
@@ -0,0 +1,401 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/LimitCoverAlgo.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/orderman/orders/algo/IAlgoServices.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "lib/orderman/orders/algo/utils/DynamicOrderAdapter.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverDefinition.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
|
||||
#include <map>
|
||||
#include <limits>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#define DEFAULT_LOG_LEVEL Info
|
||||
|
||||
#define SUMMARIZE_TRANSACTION_BEGIN(id, type, ...) Summary __LOG_TRANS(this, id, Formatter::fmt(__VA_ARGS__), type, DEFAULT_LOG_LEVEL)
|
||||
|
||||
#define SUMMARIZE_TRANSACTION(id, type) Summary __LOG_TRANS(this, id, "", type, DEFAULT_LOG_LEVEL)
|
||||
#define SUMMARIZE SUMMARIZE_TRANSACTION(__FUNCTION__, SummaryType::StatusChange)
|
||||
|
||||
// define 'PRINT_SUMMARIES' to print all logs to the console
|
||||
// (assuming logs are made with the MSG/MSG_LEVEL macros)
|
||||
#ifdef PRINT_SUMMARIES
|
||||
#define _PRINT_MSG(x) std::cout << x << std::endl;
|
||||
#else
|
||||
#define _PRINT_MSG(x)
|
||||
#endif
|
||||
|
||||
#define _MSG(level, msg) \
|
||||
_PRINT_MSG(msg); \
|
||||
LOG(OrderMan, level, "{}", msg)
|
||||
|
||||
#define MSG_LEVEL(level, ...) _MSG(level, (Formatter::fmt(__VA_ARGS__)))
|
||||
// these automatically prepend log_header() to the args
|
||||
#define MSG(...) MSG_LEVEL(DEFAULT_LOG_LEVEL, log_header(), " ", __VA_ARGS__)
|
||||
#define DIAG(...) MSG_LEVEL(Diag, log_header(), " ", __VA_ARGS__)
|
||||
#define INFO(...) MSG_LEVEL(Info, log_header(), " ", __VA_ARGS__)
|
||||
#define WARN(...) MSG_LEVEL(Warn, log_header(), " ", __VA_ARGS__)
|
||||
#define ERROR(...) MSG_LEVEL(Error, log_header(), " ", __VA_ARGS__)
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
using namespace api::orderman;
|
||||
using namespace bts::orderman::limit_cover::utils;
|
||||
using Adapter = bts::orderman::LimitCoverAdapter;
|
||||
|
||||
namespace limit_cover {
|
||||
namespace utils {
|
||||
void init_delete_and_cover(OrderDefinition& target, const DeleteAndCoverDefinition& def) {
|
||||
if (!target.has_custom_def())
|
||||
*target.mutable_custom_def() = CustomOrderDefinition();
|
||||
init_delete_and_cover(*target.mutable_custom_def(), def);
|
||||
}
|
||||
|
||||
void init_delete_and_cover(CustomOrderDefinition& target, const DeleteAndCoverDefinition& def) {
|
||||
if (!target.has_delete_and_cover()) {
|
||||
*target.mutable_delete_and_cover() = DeleteAndCoverDefinition(def);
|
||||
} else {
|
||||
target.mutable_delete_and_cover()->CopyFrom(def);
|
||||
}
|
||||
}
|
||||
|
||||
void init_child_update(Update& target, uint32_t qty, double price, const DeleteAndCoverDefinition& def) {
|
||||
target.set_quantity(qty);
|
||||
target.set_price(price);
|
||||
init_delete_and_cover(*target.mutable_custom(), def);
|
||||
}
|
||||
|
||||
#define INVALID_DEF(x) OrderOpResult(api::orderman::ORDER_ERROR_INVALID_ORDER_DEFINITON, x)
|
||||
|
||||
OrderOpResult validate_custom_def(const CustomOrderDefinition& custom_def) {
|
||||
if (!custom_def.has_dynamic())
|
||||
return INVALID_DEF("DynamicOrderDefinition is undefined");
|
||||
return Adapter::validate_proto(custom_def.dynamic());
|
||||
}
|
||||
OrderOpResult validate_def(const OrderDefinition& def) {
|
||||
if (def.attach_target_order_key() != 0) {
|
||||
return INVALID_DEF("Attach not supported.");
|
||||
} else if (def.has_custom_def()) {
|
||||
return validate_custom_def(def.custom_def());
|
||||
} else {
|
||||
return INVALID_DEF("CustomOrderDefinition is undefined.");
|
||||
}
|
||||
}
|
||||
|
||||
OrderOpResult validate_update(const Update& upd) {
|
||||
return upd.has_custom() ?
|
||||
validate_custom_def(upd.custom()) : OrderOpResult();
|
||||
}
|
||||
} // namespace utils
|
||||
} // namespace limit_cover
|
||||
|
||||
struct LimitCoverAlgo::PImpl {
|
||||
IAlgoServices *algo = nullptr;
|
||||
Status status = Status::WAITING_TO_DEPLOY;
|
||||
OrderKey child_key = null_order_key();
|
||||
double child_price = os::nan();
|
||||
uint32_t child_qty = 0;
|
||||
Book book;
|
||||
uint32_t limit_volume = 0;
|
||||
std::map<uint32_t, LimitCoverGate> gates;
|
||||
double settlement = os::nan();
|
||||
double price_offset_from_settle = 0;
|
||||
double limit_price = os::nan();
|
||||
};
|
||||
|
||||
struct LimitCoverAlgo::Summary {
|
||||
Summary(LimitCoverAlgo* owner, std::string transaction_id, std::string begin_tag = {}, SummaryType type = SummaryType::StatusChange, log::Level level = DEFAULT_LOG_LEVEL)
|
||||
: _owner(owner), _transaction_id(transaction_id), _original_status(owner->me->status), _type(type), _level(level) {
|
||||
if (_owner != nullptr && !_transaction_id.empty()) {
|
||||
std::stringstream str;
|
||||
str << owner->log_header() << " begin " << transaction_id << ".";
|
||||
if (!begin_tag.empty()) {
|
||||
str << " " << begin_tag << ".";
|
||||
}
|
||||
if (type == SummaryType::FullState) {
|
||||
str << " Algo State: " << _owner->fmt_state() << ".";
|
||||
}
|
||||
MSG_LEVEL(level, str.str());
|
||||
}
|
||||
}
|
||||
|
||||
~Summary() {
|
||||
std::stringstream msg_str;
|
||||
if (!_transaction_id.empty()) {
|
||||
msg_str << " end " << _transaction_id << ".";
|
||||
}
|
||||
switch (_type) {
|
||||
case SummaryType::StatusChange:
|
||||
if (_owner->me->status != _original_status)
|
||||
msg_str << " Status changed from " << _original_status << " to " << _owner->me->status << ".";
|
||||
break;
|
||||
case SummaryType::FullState:
|
||||
msg_str << " Algo State: " << _owner->fmt_state() << ".";
|
||||
break;
|
||||
}
|
||||
auto msg = msg_str.str();
|
||||
if (!msg.empty()) {
|
||||
MSG_LEVEL(_level, _owner->log_header(), msg);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
LimitCoverAlgo *_owner;
|
||||
std::string _transaction_id;
|
||||
Status _original_status;
|
||||
SummaryType _type;
|
||||
log::Level _level;
|
||||
};
|
||||
|
||||
LimitCoverAlgo::LimitCoverAlgo() : me(new PImpl()) {}
|
||||
LimitCoverAlgo::~LimitCoverAlgo() {}
|
||||
|
||||
OrderOpResult
|
||||
LimitCoverAlgo::do_before_launch(IBeforeLaunchAlgoServices &balgo) {
|
||||
SUMMARIZE;
|
||||
bts_assert(me->status == Status::WAITING_TO_DEPLOY);
|
||||
bts_assert(me->gates.empty());
|
||||
auto vd = validate_def(balgo.definition());
|
||||
if (vd.ok()) {
|
||||
auto lcdef = Adapter::from_proto(balgo.definition());
|
||||
me->price_offset_from_settle = lcdef.price_offset();
|
||||
me->gates = lcdef.normal_gates();
|
||||
}
|
||||
return vd;
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::do_launch(IAlgoServices& algo) {
|
||||
SUMMARIZE;
|
||||
bts_assert(me->status == Status::WAITING_TO_DEPLOY);
|
||||
me->algo = &algo;
|
||||
me->child_qty = algo.quantity().value();
|
||||
algo.subscribe_book(SecurityKey(algo.security().id()));
|
||||
algo.subscribe_settlement(SecurityKey(algo.security().id()));
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::do_update(Update const &upd) {
|
||||
SUMMARIZE_TRANSACTION_BEGIN(__FUNCTION__, SummaryType::StatusChange, utils::pb::to_string(upd));
|
||||
auto vup = validate_update(upd);
|
||||
if (!vup.ok()) {
|
||||
me->status = Status::DONE;
|
||||
algo().terminate_error(Formatter::fmt("Invalid update: ", vup.error_msg, "."));
|
||||
return;
|
||||
}
|
||||
if (upd.has_custom()) {
|
||||
auto lcdef = Adapter::from_proto(upd.custom());
|
||||
if (me->status == Status::WAITING_TO_DEPLOY) {
|
||||
me->price_offset_from_settle = lcdef.price_offset();
|
||||
recompute_limit_price();
|
||||
recompute_limit_volume();
|
||||
}
|
||||
me->gates = lcdef.normal_gates();
|
||||
if (me->status == Status::WORKING)
|
||||
recompute_gate();
|
||||
}
|
||||
switch (me->status) {
|
||||
case Status::WAITING_TO_DEPLOY:
|
||||
try_deploy();
|
||||
break;
|
||||
case Status::WORKING: {
|
||||
// don't change the child price; the price param is irrelevant to this algo.
|
||||
if (upd.has_custom() || me->child_qty != upd.quantity())
|
||||
update_child(upd.quantity());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::on_book(SecurityKey, Book const &b) {
|
||||
SUMMARIZE_TRANSACTION_BEGIN(__FUNCTION__, SummaryType::StatusChange, "BBO: ", Formatter::to_string(b));
|
||||
me->book = b;
|
||||
recompute_limit_volume();
|
||||
switch (me->status) {
|
||||
case Status::WAITING_TO_DEPLOY:
|
||||
try_deploy();
|
||||
break;
|
||||
case Status::WORKING: {
|
||||
if (recompute_gate())
|
||||
update_child(me->child_qty);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::update_child(uint32_t qty) {
|
||||
bts_assert(me->algo != nullptr);
|
||||
bts_assert(me->status == Status::WORKING);
|
||||
me->child_qty = qty;
|
||||
Update upd;
|
||||
init_child_update(upd, qty, me->child_price, curr_gate().definition());
|
||||
MSG("updating algo child: ", utils::pb::to_string(upd));
|
||||
me->algo->update_child(me->child_key, upd);
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::on_child_change(OrderKey key, ChildState const& state) {
|
||||
bts_assert(key == me->child_key);
|
||||
SUMMARIZE_TRANSACTION_BEGIN(__FUNCTION__, SummaryType::FullState, "Child State: ", Formatter::to_string(state));
|
||||
algo().update_fill_state(state.total_fills, state.last_price);
|
||||
me->child_price = state.price.value();
|
||||
switch (me->status) {
|
||||
case Status::WORKING:
|
||||
switch (state.status) {
|
||||
case ChildStatus::WORKING:
|
||||
break;
|
||||
case ChildStatus::CANCELED: {
|
||||
auto msg = Formatter::fmt("main child (", key, ") canceled unexpectedly.");
|
||||
MSG(msg, ". ", fmt_state());
|
||||
me->status = Status::DONE;
|
||||
algo().terminate_error(msg);
|
||||
break;
|
||||
}
|
||||
case ChildStatus::COMPLETED:
|
||||
me->status = Status::DONE;
|
||||
algo().complete();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Status::DONE:
|
||||
break;
|
||||
default:
|
||||
bts_assert_msg(false, Formatter::fmt(log_header(), " unexpected status: ", me->status));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::on_settlement(SecurityKey, Price settlement) {
|
||||
if (me->status == Status::WAITING_TO_DEPLOY) {
|
||||
MSG("updating settlement from ", me->settlement, " to ", settlement);
|
||||
me->settlement = settlement.value();
|
||||
DIAG("recomputing limit price...");
|
||||
recompute_limit_price();
|
||||
DIAG("limit price is ", me->limit_price);
|
||||
DIAG("recomputing limit volume...");
|
||||
recompute_limit_volume();
|
||||
DIAG("limit volume is ", me->limit_volume);
|
||||
try_deploy();
|
||||
} else {
|
||||
MSG("ignored settlement update to ", settlement,
|
||||
": settlement updates are only processed while the algo is ", Status::WAITING_TO_DEPLOY,
|
||||
". Settlement at time of deploy was ", me->settlement);
|
||||
}
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::try_deploy() {
|
||||
MSG("trying to deploy. ", fmt_state());
|
||||
bts_assert(me->status == Status::WAITING_TO_DEPLOY);
|
||||
bts_assert(me->algo != nullptr);
|
||||
// this is probably redundant (better safe than sorry tho).
|
||||
bts_assert(me->child_key == null_order_key());
|
||||
if (os::is_nan(me->settlement)) {
|
||||
MSG("still waiting for settle.");
|
||||
} else if (me->limit_volume < activation_threshold()) {
|
||||
MSG("still waiting for limit volume (", me->limit_volume, ") to breech activation threshold (", activation_threshold(), ").");
|
||||
} else {
|
||||
bts_assert(!os::is_nan(me->limit_price));
|
||||
recompute_gate();
|
||||
OrderDefinition child_def;
|
||||
bts_assert(!me->gates.empty());
|
||||
child_def.CopyFrom(algo().definition());
|
||||
init_delete_and_cover(child_def, curr_gate().definition());
|
||||
bts_assert(child_def.custom_def().has_delete_and_cover());
|
||||
MSG("deploying child order: ", utils::pb::to_string(child_def));
|
||||
me->child_key = algo().launch_child(child_def, Price(me->limit_price), algo().quantity());
|
||||
me->status = Status::WORKING;
|
||||
algo().set_ladder_visibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
bool did_change(T& backing, T new_value) {
|
||||
if (backing == new_value)
|
||||
return false;
|
||||
backing = new_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LimitCoverAlgo::recompute_gate() {
|
||||
if (me->gates.empty())
|
||||
return false;
|
||||
auto orig_sz = me->gates.size();
|
||||
auto it = me->gates.lower_bound(me->limit_volume);
|
||||
if (it == me->gates.end() || (it != me->gates.begin() && it->first > me->limit_volume))
|
||||
it--;
|
||||
me->gates.erase(me->gates.begin(), it);
|
||||
return me->gates.size() < orig_sz;
|
||||
}
|
||||
|
||||
// limit price
|
||||
double LimitCoverAlgo::compute_limit_price() const {
|
||||
return os::is_nan(me->settlement) ? os::nan() :
|
||||
securities::improve_price(side(), Price(me->settlement), Price(me->price_offset_from_settle))
|
||||
.value();
|
||||
}
|
||||
bool LimitCoverAlgo::recompute_limit_price() {
|
||||
return did_change(me->limit_price, compute_limit_price());
|
||||
}
|
||||
|
||||
// limit volume
|
||||
uint32_t LimitCoverAlgo::compute_limit_volume() const {
|
||||
return me->algo == nullptr ? 0 :
|
||||
BookUtils::volume_at_or_better(me->book, side(), me->limit_price);
|
||||
}
|
||||
bool LimitCoverAlgo::recompute_limit_volume() {
|
||||
return did_change(me->limit_volume, compute_limit_volume());
|
||||
}
|
||||
|
||||
// pseudo properties (const state accessors)
|
||||
IAlgoServices &LimitCoverAlgo::algo() const { return *me->algo; }
|
||||
|
||||
api::Side LimitCoverAlgo::side() const {
|
||||
return me->algo == nullptr ? api::SIDE_UNDEFINED : me->algo->definition().side();
|
||||
}
|
||||
|
||||
const LimitCoverGate& LimitCoverAlgo::curr_gate() const {
|
||||
bts_assert(!me->gates.empty());
|
||||
return me->gates.begin()->second;
|
||||
}
|
||||
|
||||
uint32_t LimitCoverAlgo::activation_threshold() const {
|
||||
return me->gates.empty() ? std::numeric_limits<uint32_t>::max() : me->gates.begin()->first;
|
||||
}
|
||||
|
||||
std::string LimitCoverAlgo::log_header() const {
|
||||
std::stringstream ostr;
|
||||
ostr << "LimitCoverAlgo";
|
||||
if (me->algo != nullptr) {
|
||||
ostr << "(" << me->algo->order_key().value() << ")";
|
||||
}
|
||||
ostr << "[" << fmt_status(me->status) << "]";
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
std::string LimitCoverAlgo::fmt_state() const {
|
||||
std::stringstream ostr;
|
||||
if (me->algo != nullptr) {
|
||||
ostr << " [" << fmt_status(me->status) << "]"
|
||||
<< Formatter::to_string(side()) << " " << me->algo->quantity().value()
|
||||
<< " @ " << me->algo->price().value()
|
||||
<< " (" << me->price_offset_from_settle
|
||||
<< " away from settle).";
|
||||
if (me->child_key != null_order_key()) {
|
||||
ostr << " Main child key: " << me->child_key << ", ";
|
||||
}
|
||||
ostr << " Settle: " << me->settlement
|
||||
<< ", Limit Price: " << me->limit_price
|
||||
<< ", BBO: " << Formatter::to_string(me->book);
|
||||
} else {
|
||||
ostr << "(initializing)";
|
||||
}
|
||||
return ostr.str();
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
#include "lib/orderman/orderman_utils/BookUtils.h"
|
||||
#include "lib/orderman/orderman_utils/Formatter.h"
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverDefinition.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverGate.h"
|
||||
#include <map>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
class LimitCoverAlgo : public IAlgoOrder {
|
||||
public:
|
||||
LimitCoverAlgo();
|
||||
~LimitCoverAlgo();
|
||||
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void on_settlement(SecurityKey, Price settlement) override;
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
|
||||
enum class Status {
|
||||
WAITING_TO_DEPLOY,
|
||||
WORKING,
|
||||
DONE
|
||||
};
|
||||
using Status = bts::orderman::LimitCoverAlgo::Status;
|
||||
|
||||
enum SummaryType {
|
||||
StatusChange,
|
||||
FullState
|
||||
};
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
struct Summary;
|
||||
std::unique_ptr<PImpl> me;
|
||||
|
||||
IAlgoServices &algo() const;
|
||||
api::Side side() const;
|
||||
std::string log_header() const;
|
||||
std::string fmt_state() const;
|
||||
// returns the first gate in gates (asserts that gates isn't empty).
|
||||
// Note: after the child is deployed, gates with lower thresholds are
|
||||
// removed when the limit volume passes the next gates' thresh.
|
||||
const LimitCoverGate& curr_gate() const;
|
||||
/// On the compute/recompute methods:
|
||||
/// compute_blah just returns the result (doesn't mutate any state),
|
||||
/// while recompute_blah assigns the result to the appropriate field,
|
||||
/// and returns a bool indicating whether or not the value changed.
|
||||
// limit price
|
||||
double compute_limit_price() const;
|
||||
bool recompute_limit_price();
|
||||
// limit volume
|
||||
uint32_t compute_limit_volume() const;
|
||||
bool recompute_limit_volume();
|
||||
// recomputes the current gate (i.e., the one with the
|
||||
// highest activation thresh that is still less than
|
||||
// the limit volume) then removes all gates with lower threshs.
|
||||
bool recompute_gate();
|
||||
uint32_t activation_threshold() const;
|
||||
//
|
||||
void try_deploy();
|
||||
void update_child(uint32_t qty);
|
||||
};
|
||||
|
||||
namespace limit_cover {
|
||||
namespace utils {
|
||||
inline std::string fmt_status(LimitCoverAlgo::Status status) {
|
||||
switch (status) {
|
||||
case LimitCoverAlgo::Status::WAITING_TO_DEPLOY: return "WAITING_TO_DEPLOY";
|
||||
case LimitCoverAlgo::Status::WORKING: return "WORKING";
|
||||
case LimitCoverAlgo::Status::DONE: return "DONE";
|
||||
default: return Formatter::fmt("STATUS_", static_cast<int>(status));
|
||||
}
|
||||
}
|
||||
void init_delete_and_cover(api::orderman::OrderDefinition& target, const api::orderman::DeleteAndCoverDefinition& def);
|
||||
void init_delete_and_cover(api::orderman::CustomOrderDefinition& target, const api::orderman::DeleteAndCoverDefinition& def);
|
||||
void init_child_update(api::orderman::Update& target, uint32_t qty, double price, const api::orderman::DeleteAndCoverDefinition& def);
|
||||
OrderOpResult validate_def(const api::orderman::OrderDefinition&);
|
||||
OrderOpResult validate_custom_def(const api::orderman::CustomOrderDefinition&);
|
||||
OrderOpResult validate_update(const api::orderman::Update&);
|
||||
} // namespace utils
|
||||
} // namespace limit_cover
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const LimitCoverAlgo::Status& status) {
|
||||
ostr << limit_cover::utils::fmt_status(status);
|
||||
return ostr;
|
||||
}
|
||||
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,584 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/PyramidAlgo.h"
|
||||
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/orderman/orders/algo/IAlgoServices.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include <stdint.h>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
using namespace api::orderman;
|
||||
|
||||
enum class PyramidAlgo::Status { WAITING_FOR_MD, WAITING_FOR_MD_ATTACH, WORKING, DONE };
|
||||
enum class PyramidAlgo::ExitPath { CANCEL, ERR, COMPLETE };
|
||||
|
||||
struct LegData{
|
||||
const size_t index;
|
||||
const SecurityKey sec_key;
|
||||
const double tick_size;
|
||||
const api::Side side; //trading side
|
||||
ChildTemplate* order_template = nullptr;
|
||||
OrderKey order_key;
|
||||
ChildState order_state;
|
||||
bool recv_book;
|
||||
Book book;
|
||||
Quantity top_delete; // this will be DONTCARE for covers
|
||||
Quantity cover_payup;// this will be DONTCARE for top
|
||||
LegData (size_t index_a,
|
||||
SecurityKey sec_key_a,
|
||||
double tick_size_a,
|
||||
api::Side side_a,
|
||||
ChildTemplate* order_template_a,
|
||||
Quantity qty)
|
||||
:
|
||||
index (index_a),
|
||||
sec_key (sec_key_a),
|
||||
tick_size(tick_size_a),
|
||||
side (side_a),
|
||||
order_template (order_template_a),
|
||||
order_key(null_order_key()),
|
||||
order_state(),
|
||||
recv_book(false),
|
||||
book(),
|
||||
top_delete(index == 0 ? qty.value() : 0),
|
||||
cover_payup(index == 0 ? 0 : qty.value())
|
||||
{
|
||||
}
|
||||
const BookSide& opp_md() const{
|
||||
return book.side(SpreaderUtils::invert_side(side));
|
||||
}
|
||||
bool is_top_spread() const{
|
||||
return index == 0;
|
||||
}
|
||||
void set_book (const Book& book_a){
|
||||
recv_book = true;
|
||||
book = book_a;
|
||||
}
|
||||
};
|
||||
|
||||
struct PyramidAlgo::PImpl {
|
||||
Status state = Status::WAITING_FOR_MD;
|
||||
IAlgoServices* algo_services = nullptr;
|
||||
IBeforeLaunchAlgoServices* bl_algo_services;
|
||||
PyramidAlgo::PyramidAlgoParams params;
|
||||
std::vector<LegData> legs;
|
||||
api::Side side;
|
||||
Price out_price;
|
||||
Quantity out_qty;
|
||||
PImpl(){
|
||||
}
|
||||
LegData* find_leg_from_sec (SecurityKey sec_key){
|
||||
for (auto& l : legs)
|
||||
if(l.sec_key == sec_key)
|
||||
return &l;
|
||||
return nullptr;
|
||||
}
|
||||
LegData* find_leg_from_ord_key (OrderKey ord_key){
|
||||
for (auto& l : legs)
|
||||
if(l.order_key == ord_key)
|
||||
return &l;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
PyramidAlgo::PyramidAlgo() : me(new PImpl()) {}
|
||||
PyramidAlgo::~PyramidAlgo() {}
|
||||
IAlgoServices& PyramidAlgo::_algo() const { return *me->algo_services; }
|
||||
|
||||
// initialization /////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
OrderOpResult PyramidAlgo::do_before_launch(IBeforeLaunchAlgoServices& serv) {
|
||||
me->bl_algo_services = &serv;
|
||||
auto& def = serv.definition();
|
||||
if (def.has_custom_def() && def.custom_def().has_dynamic()){
|
||||
auto conv_res = _get_creation_params(def.custom_def(), serv);
|
||||
if (std::get<1>(conv_res)){
|
||||
// params are good
|
||||
me->params = std::get<0>(conv_res);
|
||||
me->side = def.side();
|
||||
// top spread
|
||||
auto* t = _create_top_order_template(serv);
|
||||
double tick_size = serv.lookup_security(me->params.top_spread)->contract_spec().low_tick();
|
||||
me->legs.emplace_back(0, me->params.top_spread, tick_size, def.side(), t,
|
||||
me->params.top_delete);
|
||||
// covers
|
||||
size_t i = 0;
|
||||
for (SecurityKey key : me->params.cover_spreads){
|
||||
auto side = SpreaderUtils::invert_side(def.side());
|
||||
auto* t = _create_hedge_order_template(serv,i);
|
||||
double tick_size = serv.lookup_security(key)->contract_spec().low_tick();
|
||||
me->legs.emplace_back(i+1, key, tick_size, side, t, me->params.cover_payups[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else
|
||||
return {OrderError::ORDER_ERROR_INVALID_PARAMETER,
|
||||
std::get<2>(conv_res)};
|
||||
}
|
||||
else
|
||||
return {OrderError::ORDER_ERROR_INVALID_PARAMETER,
|
||||
std::string("Missing parameters")};
|
||||
return {};
|
||||
}
|
||||
void PyramidAlgo::do_launch(IAlgoServices& algo_services) {
|
||||
me->algo_services = &algo_services;
|
||||
me->out_price = algo_services.price();
|
||||
me->out_qty = algo_services.quantity();
|
||||
for (const LegData& l : me->legs)
|
||||
algo_services.subscribe_book(l.sec_key);
|
||||
}
|
||||
void PyramidAlgo::do_launch_attached(IAlgoServices& algo_services,
|
||||
OrderKey key,
|
||||
ChildState c) {
|
||||
me->algo_services = &algo_services;
|
||||
if(c.quantity != algo_services.quantity() ||
|
||||
!bts::securities::is_same(c.price, algo_services.price())){
|
||||
_cxl_and_end(ExitPath::ERR,
|
||||
"Pyramid price or quantity did not match the working order",
|
||||
true);
|
||||
}
|
||||
else{
|
||||
me->legs[0].order_key = key;
|
||||
me->out_price = c.price;
|
||||
me->out_qty = c.quantity;
|
||||
for (const LegData& l : me->legs)
|
||||
algo_services.subscribe_book(l.sec_key);
|
||||
_switch_state(PyramidAlgo::Status::WAITING_FOR_MD_ATTACH);
|
||||
on_child_change(key,c);
|
||||
}
|
||||
}
|
||||
// updates ////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void PyramidAlgo::do_update(const api::orderman::Update& update) {
|
||||
LOG(OrderMan, Diag, "PyramidAlgo::do_update order:{} state:{} "
|
||||
"receiving update: {}",
|
||||
_algo().order_key().value(),
|
||||
static_cast<int>(me->state),
|
||||
utils::pb::to_string(update));
|
||||
auto conv_res = _get_update_params(update, me->params);
|
||||
if (!std::get<1>(conv_res)){
|
||||
LOG(OrderMan, Warn, "PyramidAlgo::do_update order:{} bad update, ignoring",
|
||||
_algo().order_key().value());
|
||||
return;
|
||||
}
|
||||
const PyramidAlgoParams& algo_params = std::get<0>(conv_res);
|
||||
Price price = Price(update.price());
|
||||
Quantity qty = Quantity(update.quantity());
|
||||
switch(me->state){
|
||||
case PyramidAlgo::Status::WAITING_FOR_MD:
|
||||
case PyramidAlgo::Status::WAITING_FOR_MD_ATTACH:
|
||||
LOG(OrderMan, Info, "PyramidAlgo::do_update order:{} state:{} ignored",
|
||||
_algo().order_key().value(),static_cast<int>(me->state));
|
||||
break;
|
||||
case PyramidAlgo::Status::WORKING:{
|
||||
bool pq_changed = _copy_update(algo_params, price, qty);
|
||||
if(_target_reached()){
|
||||
_cxl_and_end (PyramidAlgo::ExitPath::CANCEL, "Target reached");
|
||||
return;
|
||||
}
|
||||
if (_working_state_met()){
|
||||
if(pq_changed)
|
||||
_modify_top_spread_order();
|
||||
}
|
||||
else
|
||||
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,
|
||||
"Order cancelled (Cover prices not met)");
|
||||
break;
|
||||
}
|
||||
case PyramidAlgo::Status::DONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// callbacks //////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void PyramidAlgo::on_book(SecurityKey key, const Book& book) {
|
||||
LegData* leg_data = me->find_leg_from_sec(key);
|
||||
if(leg_data != nullptr){
|
||||
leg_data->set_book(book);
|
||||
_analyze_md_and_act();
|
||||
}
|
||||
}
|
||||
// this only gets called on am explicit algo cancel
|
||||
// from the outside
|
||||
IAlgoOrder::OnCancelResponse PyramidAlgo::on_cancel(){
|
||||
// cancel top spread, let hedges work, cancel algo
|
||||
switch(me->state){
|
||||
case PyramidAlgo::Status::WAITING_FOR_MD:
|
||||
case PyramidAlgo::Status::WAITING_FOR_MD_ATTACH:
|
||||
case PyramidAlgo::Status::WORKING:
|
||||
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,"Algo cancelled");
|
||||
break;
|
||||
case PyramidAlgo::Status::DONE:
|
||||
break;
|
||||
}
|
||||
return IAlgoOrder::OnCancelResponse::IGNORE;
|
||||
}
|
||||
void PyramidAlgo::on_child_change(OrderKey key, const ChildState &st) {
|
||||
LOG(OrderMan, Info,
|
||||
"PyramidAlgo::on_child_change key:{} child_key:{} state:{} status:{} "
|
||||
"total_fills:{} last_price:{}",
|
||||
_algo().order_key(),
|
||||
key,
|
||||
static_cast<int>(me->state),
|
||||
static_cast<int>(st.status),
|
||||
st.total_fills,
|
||||
st.last_price);
|
||||
LegData* leg = me->find_leg_from_ord_key (key);
|
||||
if (me->legs[0].order_key == key){
|
||||
switch(me->state){
|
||||
case PyramidAlgo::Status::WAITING_FOR_MD:
|
||||
bts_assert_msg (false, "No child orders");
|
||||
break;
|
||||
case PyramidAlgo::Status::WAITING_FOR_MD_ATTACH:
|
||||
leg->order_state = st;
|
||||
_algo().update_fill_state(st.total_fills, st.avg_price);
|
||||
_check_for_completion();
|
||||
break;
|
||||
case PyramidAlgo::Status::WORKING:
|
||||
leg->order_state = st;
|
||||
_algo().update_fill_state(st.total_fills, st.avg_price);
|
||||
_send_hedges(st.last_fills);
|
||||
_check_for_completion();
|
||||
break;
|
||||
case PyramidAlgo::Status::DONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// stateless conversion ///////////////////////////////////////////////////////
|
||||
|
||||
PyramidAlgo::PyramidAlgoParams PyramidAlgo::_convert_params (
|
||||
const CustomOrderDefinition& cod){
|
||||
const DynamicOrderDefinition& def = cod.dynamic();
|
||||
const auto& params = def.order_params();
|
||||
PyramidAlgoParams rv;
|
||||
// top contract
|
||||
auto r = get_if<uint32_t>(params, "Top Spread");
|
||||
rv.top_spread = r.has_value() ? SecurityKey(r.value()) : SecurityKey(0);
|
||||
// top delete
|
||||
r = get_if<uint32_t>(params, "Delete");
|
||||
rv.top_delete = Quantity(r.has_value() ? r.value() : UINT32_MAX);
|
||||
for (size_t i = 0; i < 5; i++){
|
||||
auto idx = std::to_string(i+1);
|
||||
auto b = get_if<uint32_t>(params, "Spread " + idx);
|
||||
if (b.has_value() && b.value() != 0){
|
||||
rv.cover_spreads.emplace_back(b.value());
|
||||
auto pu = get_if<uint32_t>(params, "Spread " + idx + " Payup");
|
||||
rv.cover_payups.emplace_back(pu.has_value() ? pu.value() : UINT32_MAX);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
std::tuple<PyramidAlgo::PyramidAlgoParams,bool,std::string>
|
||||
PyramidAlgo::_get_creation_params(const CustomOrderDefinition& cod,
|
||||
IBeforeLaunchAlgoServices& serv){
|
||||
PyramidAlgo::PyramidAlgoParams rv = _convert_params(cod);
|
||||
// error checking
|
||||
if(rv.top_spread.value() != serv.security().id())
|
||||
return std::make_tuple(rv,false,
|
||||
"Algo can only be launched from the Top Spread");
|
||||
if(rv.cover_spreads.size() < 2)
|
||||
return std::make_tuple(rv,false,
|
||||
"Not enough cover spreads");
|
||||
if (rv.cover_spreads[0].value() == 0 ||
|
||||
rv.cover_spreads[1].value() == 0)
|
||||
return std::make_tuple(rv,false,
|
||||
"Not enough cover spreads");
|
||||
return std::make_tuple(rv,true,std::string(""));
|
||||
}
|
||||
|
||||
std::tuple<PyramidAlgo::PyramidAlgoParams,bool,std::string>
|
||||
PyramidAlgo::_get_update_params(const Update& update,
|
||||
const PyramidAlgoParams& curr_params){
|
||||
if(update.has_custom() && update.custom().has_dynamic()){
|
||||
// means there is a params change
|
||||
PyramidAlgoParams new_params = _convert_params(update.custom());
|
||||
if(new_params.top_spread != curr_params.top_spread ||
|
||||
new_params.cover_spreads.size() != curr_params.cover_spreads.size())
|
||||
return std::make_tuple(new_params,false,"Contracts can not be updated");
|
||||
for (size_t i=0; i < curr_params.cover_spreads.size(); i++)
|
||||
if(new_params.cover_spreads[i] != curr_params.cover_spreads[i])
|
||||
return std::make_tuple(new_params,false,"Contracts can not be updated");
|
||||
return std::make_tuple(new_params,true,std::string(""));
|
||||
}
|
||||
else
|
||||
return std::make_tuple(curr_params,true,std::string(""));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// order template creation/////////////////////////////////////////////////////
|
||||
|
||||
ChildTemplate* PyramidAlgo::_create_top_order_template (
|
||||
IBeforeLaunchAlgoServices& serv) const{
|
||||
OrderDefinition order_def = serv.definition();
|
||||
order_def.set_security_key (me->params.top_spread.value());
|
||||
order_def.set_side(me->side);
|
||||
order_def.mutable_custom_def()->mutable_limit()->MergeFrom (
|
||||
LimitOrderDefinition());
|
||||
return &(serv.make_child_template(order_def));
|
||||
}
|
||||
|
||||
ChildTemplate* PyramidAlgo::_create_hedge_order_template (
|
||||
IBeforeLaunchAlgoServices& serv,
|
||||
size_t hedge_idx) const{
|
||||
OrderDefinition order_def = serv.definition();
|
||||
order_def.set_attach_target_order_key(null_order_key().value());
|
||||
order_def.set_security_key (me->params.cover_spreads[hedge_idx].value());
|
||||
order_def.set_side(SpreaderUtils::invert_side(me->side));
|
||||
order_def.set_sub_type(ORDER_SUB_TYPE_HEDGING_LEG);
|
||||
LeanOrderDefinition lean;
|
||||
lean.mutable_cross_threshold()->set_absolute(me->params.cover_payups[hedge_idx].value());
|
||||
lean.set_max_chase_ticks(1);
|
||||
lean.set_trade_toward_behavior(LeanOrderDefinition::NO_CHANGE);
|
||||
lean.mutable_child()->mutable_limit()->MergeFrom (
|
||||
LimitOrderDefinition());
|
||||
order_def.mutable_custom_def()->mutable_lean()->MergeFrom (lean);
|
||||
return &(serv.make_child_template(order_def));
|
||||
}
|
||||
|
||||
void PyramidAlgo::_analyze_md_and_act(){
|
||||
switch(me->state){
|
||||
case PyramidAlgo::Status::WAITING_FOR_MD:
|
||||
if(!_md_recvd())
|
||||
return;
|
||||
// received data for the first time
|
||||
if (_working_state_met()){
|
||||
// enter order, and switch state
|
||||
LegData& top_leg = me->legs[0];
|
||||
// this is the only place top spread order is sent (once)
|
||||
top_leg.order_key = _algo().launch_child(
|
||||
*top_leg.order_template,
|
||||
me->out_price,
|
||||
me->out_qty);
|
||||
LOG(OrderMan, Info,"PyramidAlgo::_analyze_md_and_act launching order key:{} "
|
||||
"child_key:{} price:{} qty:{}",
|
||||
_algo().order_key(),
|
||||
top_leg.order_key,
|
||||
me->out_price,
|
||||
me->out_qty);
|
||||
_switch_state(PyramidAlgo::Status::WORKING);
|
||||
}
|
||||
else
|
||||
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,
|
||||
"Order cancelled (Cover prices not met)");
|
||||
break;
|
||||
case PyramidAlgo::Status::WAITING_FOR_MD_ATTACH:
|
||||
if(!_md_recvd())
|
||||
return;
|
||||
// received data for the first time
|
||||
if (_working_state_met())
|
||||
_switch_state(PyramidAlgo::Status::WORKING);
|
||||
else
|
||||
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,
|
||||
"Order detached (Cover prices not met)",
|
||||
true);
|
||||
break;
|
||||
case PyramidAlgo::Status::WORKING:
|
||||
if (!_working_state_met())
|
||||
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,
|
||||
"Order cancelled (Cover prices not met)");
|
||||
break;
|
||||
case PyramidAlgo::Status::DONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PyramidAlgo::_switch_state(Status new_state){
|
||||
switch(new_state){
|
||||
case (PyramidAlgo::Status::WAITING_FOR_MD):
|
||||
bts_assert(false);
|
||||
break;
|
||||
case (PyramidAlgo::Status::WAITING_FOR_MD_ATTACH):
|
||||
bts_assert(me->state == PyramidAlgo::Status::WAITING_FOR_MD);
|
||||
break;
|
||||
case (PyramidAlgo::Status::WORKING):
|
||||
bts_assert(me->state == PyramidAlgo::Status::WAITING_FOR_MD ||
|
||||
me->state == PyramidAlgo::Status::WAITING_FOR_MD_ATTACH);
|
||||
break;
|
||||
case (PyramidAlgo::Status::DONE):
|
||||
bts_assert(
|
||||
// when an order is entered and the criteria is not met
|
||||
me->state == PyramidAlgo::Status::WAITING_FOR_MD ||
|
||||
// when an order is attached and the market conditions are not met
|
||||
me->state == PyramidAlgo::Status::WAITING_FOR_MD_ATTACH ||
|
||||
// when the child order gets into an unexpected state
|
||||
me->state == PyramidAlgo::Status::WORKING);
|
||||
break;
|
||||
}
|
||||
LOG(OrderMan, Info,"PyramidAlgo::_switch_state key:{} {}->{} ",
|
||||
_algo().order_key(),
|
||||
static_cast<int>(me->state),
|
||||
static_cast<int>(new_state));
|
||||
me->state = new_state;
|
||||
}
|
||||
|
||||
void PyramidAlgo::_send_hedges (Quantity filled_qty){
|
||||
if (filled_qty.value() > 0)
|
||||
for (auto& l : me->legs)
|
||||
if(!l.is_top_spread()){
|
||||
const auto& inside_md = l.opp_md()[0];
|
||||
Price hedge_price = Price(inside_md.price());
|
||||
uint32_t inside_md_qty = inside_md.size();
|
||||
if (l.cover_payup.value() != 0 &&
|
||||
inside_md_qty > l.cover_payup.value())
|
||||
hedge_price = securities::worsen_price(l.side, hedge_price, Price(l.tick_size));
|
||||
// check payup
|
||||
l.order_key = _algo().launch_child(
|
||||
*l.order_template,
|
||||
hedge_price,
|
||||
filled_qty);
|
||||
_algo().detach_child(l.order_key);
|
||||
}
|
||||
}
|
||||
|
||||
void PyramidAlgo::_modify_top_spread_order(){
|
||||
Update child_update;
|
||||
child_update.set_price(me->out_price.value());
|
||||
child_update.set_quantity(me->out_qty.value());
|
||||
LOG(OrderMan, Info,"PyramidAlgo::_modify_top_spread_order modifying order key:{} "
|
||||
"child_key:{} price:{} qty:{}",
|
||||
_algo().order_key(),
|
||||
me->legs[0].order_key,
|
||||
me->out_price,
|
||||
me->out_qty);
|
||||
_algo().update_child(me->legs[0].order_key, child_update);
|
||||
}
|
||||
// copies the params, price and qty to the state
|
||||
// retuns true is the price or qty has changed
|
||||
bool PyramidAlgo::_copy_update(const PyramidAlgoParams& algo_params,
|
||||
Price price, Quantity qty){
|
||||
// copy input params (which are stored seperately)
|
||||
me->params.top_delete = algo_params.top_delete;
|
||||
size_t cov_idx = 0;
|
||||
for (Quantity payup : algo_params.cover_payups)
|
||||
me->params.cover_payups[cov_idx++] = payup;
|
||||
// copy leg data
|
||||
cov_idx = 0;
|
||||
me->legs[0].top_delete = algo_params.top_delete;
|
||||
for (Quantity pu : algo_params.cover_payups){
|
||||
LegData& cover_leg = me->legs[cov_idx+1];
|
||||
if(cover_leg.cover_payup != pu){
|
||||
cover_leg.cover_payup = pu;
|
||||
cover_leg.order_template = _create_hedge_order_template(
|
||||
*me->bl_algo_services,
|
||||
cov_idx);
|
||||
}
|
||||
cov_idx++;
|
||||
}
|
||||
// copy price and qty (returns true is they have changed)
|
||||
bool pq_changed = me->out_qty.value() != qty.value() ||
|
||||
!bts::securities::is_same(me->out_price, price);
|
||||
me->out_price = price;
|
||||
me->out_qty = qty;
|
||||
return pq_changed;
|
||||
}
|
||||
// if the top spread order is in a final state, the algo
|
||||
// algo goes to the final state
|
||||
void PyramidAlgo::_check_for_completion(){
|
||||
const ChildState& top_ord = me->legs[0].order_state;
|
||||
switch (top_ord.status){
|
||||
case (ChildStatus::WORKING):
|
||||
break;
|
||||
case (ChildStatus::COMPLETED):
|
||||
_cxl_and_end(PyramidAlgo::ExitPath::ERR,"");
|
||||
break;
|
||||
case (ChildStatus::CANCELED):
|
||||
_cxl_and_end(PyramidAlgo::ExitPath::ERR,
|
||||
"Top spread unexpectedly cancelled");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PyramidAlgo::_cxl_and_end(PyramidAlgo::ExitPath exit_path,
|
||||
const std::string& msg,
|
||||
bool detach_top){
|
||||
if(me->legs[0].order_key != null_order_key()){
|
||||
const ChildState& top_ord_state = me->legs[0].order_state;
|
||||
if(top_ord_state.status == ChildStatus::WORKING){
|
||||
if(detach_top)
|
||||
_algo().detach_child(me->legs[0].order_key);
|
||||
else
|
||||
_algo().cancel_child(me->legs[0].order_key);
|
||||
}
|
||||
}
|
||||
switch(exit_path){
|
||||
case PyramidAlgo::ExitPath::ERR:
|
||||
_algo().terminate_error(msg);
|
||||
break;
|
||||
case PyramidAlgo::ExitPath::CANCEL:
|
||||
_algo().cancel(msg);
|
||||
break;
|
||||
case PyramidAlgo::ExitPath::COMPLETE:
|
||||
_algo().complete();
|
||||
break;
|
||||
}
|
||||
_switch_state(PyramidAlgo::Status::DONE);
|
||||
}
|
||||
// true the out qty is changed it be below
|
||||
// the filled qty
|
||||
bool PyramidAlgo::_target_reached() const{
|
||||
return me->legs[0].order_state.total_fills.value() >=
|
||||
me->out_qty.value();
|
||||
}
|
||||
|
||||
bool PyramidAlgo::_md_recvd() const{
|
||||
for (const LegData& l : me->legs)
|
||||
if(!l.recv_book)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gr_than_eq(double p1, double p2){
|
||||
return securities::test_price_equality(p1,p2) || p1 > p2;
|
||||
}
|
||||
|
||||
bool less_than_eq(double p1, double p2){
|
||||
return securities::test_price_equality(p1,p2) || p1 < p2;
|
||||
}
|
||||
|
||||
bool PyramidAlgo::_working_state_met() const{
|
||||
double cover_sum = 0.0;
|
||||
size_t i = 0;
|
||||
for (const auto& l : me->legs)
|
||||
if (i++ > 0)
|
||||
cover_sum += l.opp_md()[0].price();
|
||||
bool rv = !os::is_nan(cover_sum) &&
|
||||
(
|
||||
(
|
||||
me->side == api::Side::SIDE_BUY &&
|
||||
less_than_eq(me->out_price.value(),cover_sum)
|
||||
)
|
||||
||
|
||||
(
|
||||
me->side == api::Side::SIDE_SELL &&
|
||||
gr_than_eq(me->out_price.value(),cover_sum)
|
||||
)
|
||||
);
|
||||
if (!rv && !os::is_nan(cover_sum)){
|
||||
// price condition is not met, but if the
|
||||
// cum qty at the top leg price is > threashold
|
||||
// then the condition is met ()
|
||||
const auto& top_leg = me->legs[0];
|
||||
Quantity cumm_level_qty;
|
||||
SpreaderUtils::find_qty_upto_price_level(top_leg.book,
|
||||
top_leg.side,
|
||||
Price(me->out_price),
|
||||
0.0,
|
||||
cumm_level_qty);
|
||||
rv = cumm_level_qty.value() > top_leg.top_delete.value();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class ChildTemplate;
|
||||
struct LegData;
|
||||
|
||||
class PyramidAlgo : public IAlgoOrder {
|
||||
public:
|
||||
PyramidAlgo();
|
||||
~PyramidAlgo();
|
||||
struct PyramidAlgoParams{
|
||||
// first one is the top
|
||||
SecurityKey top_spread;
|
||||
Quantity top_delete;
|
||||
std::vector<SecurityKey> cover_spreads;
|
||||
std::vector<Quantity> cover_payups;
|
||||
};
|
||||
public:
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
IAlgoOrder::OnCancelResponse on_cancel() override;
|
||||
|
||||
private:
|
||||
enum class Status;
|
||||
enum class ExitPath;
|
||||
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
private:
|
||||
IAlgoServices& _algo() const ;
|
||||
// converts parameters from the CustomOrderDefinition::dynamic
|
||||
// to PyramidAlgoParams
|
||||
static PyramidAlgoParams _convert_params (
|
||||
const bts::api::orderman::CustomOrderDefinition&);
|
||||
static std::tuple<PyramidAlgoParams,bool,std::string> _get_creation_params(
|
||||
const bts::api::orderman::CustomOrderDefinition&,
|
||||
IBeforeLaunchAlgoServices&);
|
||||
static std::tuple<PyramidAlgoParams,bool,std::string> _get_update_params(
|
||||
const api::orderman::Update&,
|
||||
const PyramidAlgoParams& curr_params);
|
||||
ChildTemplate* _create_top_order_template (IBeforeLaunchAlgoServices&) const;
|
||||
ChildTemplate* _create_hedge_order_template (IBeforeLaunchAlgoServices&,
|
||||
size_t) const;
|
||||
// acts on md, cognizant of the state
|
||||
void _analyze_md_and_act();
|
||||
void _switch_state (Status new_status);
|
||||
void _send_hedges (Quantity filled_qty);
|
||||
void _modify_top_spread_order();
|
||||
// returns true is the price, qty changes
|
||||
bool _copy_update(const PyramidAlgoParams& algo_params,
|
||||
Price price, Quantity qty);
|
||||
void _check_for_completion();
|
||||
// cxl's the working top leg order (if any), detaches
|
||||
// all hedges. if is_error then terminates algo, is
|
||||
// is_cxl then cxl's algo, otherwise completes it
|
||||
void _cxl_and_end(ExitPath exit_path,
|
||||
const std::string& err_txt,
|
||||
bool detach_top = false);
|
||||
bool _target_reached() const;
|
||||
// true is all legs have recvd md
|
||||
bool _md_recvd() const;
|
||||
bool _working_state_met() const;
|
||||
};
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,253 @@
|
||||
|
||||
#include "lib/plugin/SparkServices.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/LimitCoverAlgo.h"
|
||||
#include "lib/config/protos/toggles.pb.h"
|
||||
#include "lib/config/config.h"
|
||||
#include "DormanAlgo.h"
|
||||
#include "PyramidAlgo.h"
|
||||
#include "TASAlgo.h"
|
||||
#include "TASCounterAlgo.h"
|
||||
#include "OnSignalAlgo.h"
|
||||
|
||||
using namespace bts;
|
||||
using namespace api::orderman;
|
||||
using namespace orderman::dorman;
|
||||
using namespace orderman;
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace dorman {
|
||||
|
||||
template <class OK,class ERR>
|
||||
using Result = std::variant<OK,ERR>;
|
||||
Result<AlgoParams,OrderOpResult> bad_definition(const char* msg) { return {OrderOpResult{ORDER_ERROR_INVALID_ORDER_DEFINITON, msg}}; }
|
||||
|
||||
// Validate DynamicOrderDefinition and construct parameter object
|
||||
Result<AlgoParams,OrderOpResult> to_dorman_params(const api::orderman::DynamicOrderDefinition& def) {
|
||||
const auto& params = def.order_params();
|
||||
AlgoParams res;
|
||||
|
||||
//
|
||||
// Step Table
|
||||
for (size_t x = 0; x < 5; ++x) {
|
||||
auto xs = std::to_string(x+1);
|
||||
auto mq = get_if<uint32_t>(params, "Market Quantity " + xs).value_or(0);
|
||||
auto oq = get_if<uint32_t>(params, "Order Quantity " + xs).value_or(0);
|
||||
if (mq && oq) {
|
||||
res.steps.step_table.push_back({ mq, oq });
|
||||
}
|
||||
}
|
||||
std::sort(res.steps.step_table.begin(), res.steps.step_table.end());
|
||||
res.steps.maximum = get_if<uint32_t>(params, "Max Size").value_or(-1);
|
||||
res.steps.trail_seconds = get_if<uint32_t>(params, "Trail Seconds").value_or(0);
|
||||
|
||||
//
|
||||
// Clip limit
|
||||
res.clip_limit = get_if<uint32_t>(params, "Clip Size").value_or(0);
|
||||
|
||||
//
|
||||
// scratch
|
||||
auto enable_scratch = get_if<bool>(params, "Enable Scratch");
|
||||
if (enable_scratch && enable_scratch.value()) {
|
||||
auto overfill = get_if<uint32_t>(params, "Max Overfill");
|
||||
if (!overfill) return bad_definition("Missing 'Max Overfill'");
|
||||
res.scratch = { overfill.value() };
|
||||
}
|
||||
|
||||
//
|
||||
// profit orders
|
||||
auto enable_profit_orders = get_if<bool>(params, "Profit Orders");
|
||||
if (enable_profit_orders && enable_profit_orders.value()) {
|
||||
auto ticks = get_if<uint32_t>(params, "Ticks Away");
|
||||
if (!ticks) return bad_definition("Missing 'Ticks Away'");
|
||||
res.profit_orders = { ticks.value() };
|
||||
}
|
||||
|
||||
//
|
||||
// trade accumulation
|
||||
auto trade_acc = get_if<bool>(params, "Trade Accumulation").value_or(false);
|
||||
if (trade_acc) {
|
||||
TradeAccumulationParams tap;
|
||||
|
||||
auto contracts = get_if<uint32_t>(params, "Trade Contracts");
|
||||
if (!contracts) return bad_definition("Missing 'Trade Accumulation # Contracts'");
|
||||
tap.contracts = contracts.value();
|
||||
|
||||
auto interval = get_if<uint32_t>(params, "Trade Interval (seconds)");
|
||||
if (!interval) return bad_definition("Missing 'Trade Accumulation Interval'");
|
||||
tap.seconds = interval.value();
|
||||
|
||||
auto sleep = get_if<uint32_t>(params, "Trade Sleep (seconds)");
|
||||
if (!sleep) return bad_definition("Missing 'Trade Accumulation Sleep'");
|
||||
tap.sleep_seconds = sleep.value();
|
||||
|
||||
res.trade_accumulation = {tap};
|
||||
}
|
||||
|
||||
return {res};
|
||||
}
|
||||
|
||||
extern "C" void init_dorman_plugin(bts::SparkServices &srv) {
|
||||
DynamicOrderDescriptor join_desc;
|
||||
|
||||
join_desc.set_dynamic_order_type("Dorman");
|
||||
|
||||
*join_desc.add_param_desc() = param_array("what", param_double("").param_type());
|
||||
|
||||
|
||||
*join_desc.add_param_desc() = param_uint32("Market Quantity 1");
|
||||
*join_desc.add_param_desc() = param_uint32("Order Quantity 1");
|
||||
*join_desc.add_param_desc() = param_uint32("Market Quantity 2");
|
||||
*join_desc.add_param_desc() = param_uint32("Order Quantity 2");
|
||||
*join_desc.add_param_desc() = param_uint32("Market Quantity 3");
|
||||
*join_desc.add_param_desc() = param_uint32("Order Quantity 3");
|
||||
*join_desc.add_param_desc() = param_uint32("Market Quantity 4");
|
||||
*join_desc.add_param_desc() = param_uint32("Order Quantity 4");
|
||||
*join_desc.add_param_desc() = param_uint32("Market Quantity 5");
|
||||
*join_desc.add_param_desc() = param_uint32("Order Quantity 5");
|
||||
|
||||
*join_desc.add_param_desc() = param_uint32("Max Size");
|
||||
*join_desc.add_param_desc() = param_uint32("Trail Seconds");
|
||||
*join_desc.add_param_desc() = param_uint32("Clip Size");
|
||||
*join_desc.add_param_desc() = param_bool("Enable Scratch");
|
||||
*join_desc.add_param_desc() = param_uint32("Max Overfill");
|
||||
*join_desc.add_param_desc() = param_bool("Profit Orders");
|
||||
*join_desc.add_param_desc() = param_uint32("Ticks Away");
|
||||
|
||||
*join_desc.add_param_desc() = param_bool("Trade Accumulation");
|
||||
*join_desc.add_param_desc() = param_uint32("Trade Contracts");
|
||||
*join_desc.add_param_desc() = param_uint32("Trade Interval (seconds)");
|
||||
*join_desc.add_param_desc() = param_uint32("Trade Sleep (seconds)");
|
||||
|
||||
srv.register_dynamic_algo_order(join_desc, []() { return std::make_unique<DormanAlgo>(); });
|
||||
}
|
||||
|
||||
extern "C" void init_pyramid_plugin(bts::SparkServices& srv) {
|
||||
DynamicOrderDescriptor join_desc;
|
||||
|
||||
join_desc.set_dynamic_order_type("Pyramid");
|
||||
|
||||
*join_desc.add_param_desc() = param_security_key("Top Spread");
|
||||
*join_desc.add_param_desc() = param_uint32("Delete");
|
||||
*join_desc.add_param_desc() = param_security_key("Spread 1");
|
||||
*join_desc.add_param_desc() = param_uint32("Spread 1 Payup");
|
||||
*join_desc.add_param_desc() = param_order("Spread 1 Order");
|
||||
*join_desc.add_param_desc() = param_security_key("Spread 2");
|
||||
*join_desc.add_param_desc() = param_uint32("Spread 2 Payup");
|
||||
*join_desc.add_param_desc() = param_order("Spread 2 Order");
|
||||
*join_desc.add_param_desc() = param_security_key("Spread 3");
|
||||
*join_desc.add_param_desc() = param_uint32("Spread 3 Payup");
|
||||
*join_desc.add_param_desc() = param_order("Spread 3 Order");
|
||||
*join_desc.add_param_desc() = param_security_key("Spread 4");
|
||||
*join_desc.add_param_desc() = param_uint32("Spread 4 Payup");
|
||||
*join_desc.add_param_desc() = param_order("Spread 4 Order");
|
||||
*join_desc.add_param_desc() = param_security_key("Spread 5");
|
||||
*join_desc.add_param_desc() = param_uint32("Spread 5 Payup");
|
||||
*join_desc.add_param_desc() = param_order("Spread 5 Order");
|
||||
|
||||
srv.register_dynamic_algo_order(join_desc, []() { return std::make_unique<orderman::PyramidAlgo>(); });
|
||||
}
|
||||
|
||||
extern "C" void init_tascounter_plugin(bts::SparkServices& srv) {
|
||||
DynamicOrderDescriptor join_desc;
|
||||
|
||||
join_desc.set_dynamic_order_type("TASCounter");
|
||||
// all 6 contracts. They can theoretically be inferred from the front month
|
||||
// since T1 are calendars 1 month apart starting from the front month
|
||||
// ex Mar-May, May-Jul, Jul-Sep being T1 front, second and third respectively
|
||||
// (assuming Mar is the front month)
|
||||
*join_desc.add_param_desc() = param_security_key("T1 Front");
|
||||
*join_desc.add_param_desc() = param_security_key("T1 Second");
|
||||
*join_desc.add_param_desc() = param_security_key("T1 Third");
|
||||
*join_desc.add_param_desc() = param_security_key("T2 Front");
|
||||
*join_desc.add_param_desc() = param_security_key("T2 Second");
|
||||
*join_desc.add_param_desc() = param_security_key("T3 Front");
|
||||
// tas contracts (could have been derived)
|
||||
*join_desc.add_param_desc() = param_security_key("T1 Front TAS");
|
||||
*join_desc.add_param_desc() = param_security_key("T1 Second TAS");
|
||||
*join_desc.add_param_desc() = param_security_key("T1 Third TAS");
|
||||
// automatic orders
|
||||
*join_desc.add_param_desc() = param_uint32("T1 Front Time");
|
||||
*join_desc.add_param_desc() = param_uint32("T1 Front Qty");
|
||||
*join_desc.add_param_desc() = param_bool("T1 Front Order Enabled");
|
||||
|
||||
*join_desc.add_param_desc() = param_uint32("T1 Second Time");
|
||||
*join_desc.add_param_desc() = param_uint32("T1 Second Qty");
|
||||
*join_desc.add_param_desc() = param_bool("T1 Second Order Enabled");
|
||||
|
||||
*join_desc.add_param_desc() = param_uint32("T1 Third Time");
|
||||
*join_desc.add_param_desc() = param_uint32("T1 Third Qty");
|
||||
*join_desc.add_param_desc() = param_bool("T1 Third Order Enabled");
|
||||
// settlement time
|
||||
*join_desc.add_param_desc() = param_uint32("Settlement Start Hour");
|
||||
*join_desc.add_param_desc() = param_uint32("Settlement Start Minutes");
|
||||
*join_desc.add_param_desc() = param_uint32("Settlement Start Seconds");
|
||||
*join_desc.add_param_desc() = param_uint32("Settlement Period (seconds)");
|
||||
|
||||
srv.register_dynamic_algo_order(join_desc, []() { return std::make_unique<orderman::TASCounterAlgo>(); });
|
||||
}
|
||||
|
||||
extern "C" void init_tas_plugin(bts::SparkServices& srv) {
|
||||
DynamicOrderDescriptor tas_desc;
|
||||
|
||||
tas_desc.set_dynamic_order_type("TAS");
|
||||
|
||||
size_t n_child_ords = 8;
|
||||
for (size_t i=0; i< n_child_ords; i++){
|
||||
auto idx = std::to_string(i+1);
|
||||
*tas_desc.add_param_desc() = param_double("Order " + idx + " Price");
|
||||
*tas_desc.add_param_desc() = param_uint32("Order " + idx + " Quantity");
|
||||
*tas_desc.add_param_desc() = param_bool("Order " + idx + " Buy?");
|
||||
}
|
||||
srv.register_dynamic_algo_order(tas_desc, []() { return std::make_unique<orderman::TASAlgo>(); });
|
||||
}
|
||||
|
||||
extern "C" void init_limit_cover(bts::SparkServices & srv) {
|
||||
DynamicOrderDescriptor lc_desc;
|
||||
lc_desc.set_dynamic_order_type("LimitCover");
|
||||
LimitCoverAdapter::define_proto(lc_desc);
|
||||
srv.register_dynamic_algo_order(lc_desc, []() { return std::make_unique<orderman::LimitCoverAlgo>(); });
|
||||
}
|
||||
|
||||
extern "C" void init_on_signal_plugin(bts::SparkServices& srv) {
|
||||
DynamicOrderDescriptor on_signal;
|
||||
on_signal.set_dynamic_order_type("OnSignal");
|
||||
*on_signal.add_param_desc() = param_uint32("Time Hours");
|
||||
*on_signal.add_param_desc() = param_uint32("Time Minutes");
|
||||
*on_signal.add_param_desc() = param_uint32("Time Seconds");
|
||||
*on_signal.add_param_desc() = param_bool("Preopen");
|
||||
*on_signal.add_param_desc() = param_bool("Open");
|
||||
// contracts who's signals have to be received to send the algo
|
||||
*on_signal.add_param_desc() = param_security_key("Signal Contract 1");
|
||||
size_t n_child_ords = 10;
|
||||
for (size_t i=0; i< n_child_ords; i++){
|
||||
auto idx = std::to_string(i+1);
|
||||
*on_signal.add_param_desc() = param_security_key("Order " + idx + " Contract");
|
||||
*on_signal.add_param_desc() = param_order("Order " + idx);
|
||||
*on_signal.add_param_desc() = param_double("Order " + idx + " Price");
|
||||
*on_signal.add_param_desc() = param_int32("Order " + idx + " Quantity");
|
||||
}
|
||||
srv.register_dynamic_algo_order(on_signal, []() { return std::make_unique<orderman::OnSignalAlgo>(); });
|
||||
}
|
||||
|
||||
extern "C" void init_spark_plugin(bts::SparkServices &srv) {
|
||||
auto& dyn_algo_config = config::get<config::toggles::DynamicAlgos>();
|
||||
if(dyn_algo_config.enable_dorman())
|
||||
init_dorman_plugin(srv);
|
||||
if(dyn_algo_config.enable_pyramid())
|
||||
init_pyramid_plugin(srv);
|
||||
if(dyn_algo_config.enable_tas())
|
||||
init_tas_plugin(srv);
|
||||
if(dyn_algo_config.enable_tascounter())
|
||||
init_tascounter_plugin(srv);
|
||||
if (dyn_algo_config.enable_limit_cover())
|
||||
init_limit_cover(srv);
|
||||
if (dyn_algo_config.enable_onsignal())
|
||||
init_on_signal_plugin(srv);
|
||||
}
|
||||
|
||||
}}}
|
||||
@@ -0,0 +1,222 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/TASAlgo.h"
|
||||
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/orderman/orders/algo/IAlgoServices.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <stdint.h>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
using namespace api::orderman;
|
||||
|
||||
enum class TASAlgo::Status { WAITING_FOR_SIGNAL, WORKING, DONE };
|
||||
|
||||
struct TASLegData{
|
||||
api::Side side; //trading side
|
||||
Price price;
|
||||
Quantity quantity;
|
||||
ChildTemplate* order_template = nullptr;
|
||||
OrderKey order_key;
|
||||
ChildState order_state;
|
||||
TASLegData (api::Side side,
|
||||
Price price,
|
||||
Quantity quantity,
|
||||
ChildTemplate* order_template)
|
||||
:
|
||||
side (side),
|
||||
price(price),
|
||||
quantity(quantity),
|
||||
order_template (order_template),
|
||||
order_key(null_order_key()),
|
||||
order_state(){
|
||||
order_state.status = ChildStatus::WORKING;
|
||||
}
|
||||
};
|
||||
|
||||
struct TASAlgo::PImpl {
|
||||
Status state = Status::WAITING_FOR_SIGNAL;
|
||||
IAlgoServices* algo_services = nullptr;
|
||||
SecurityKey sec_key = SecurityKey(0);
|
||||
TASAlgo::TASAlgoParams algo_params;
|
||||
std::vector<TASLegData> legs;
|
||||
bts::datetime::PTime start_time = bts::datetime::local_now();
|
||||
PImpl(){
|
||||
}
|
||||
TASLegData* find_leg_from_ord_key (OrderKey ord_key){
|
||||
for (auto& l : legs)
|
||||
if(l.order_key == ord_key)
|
||||
return &l;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
TASAlgo::TASAlgo() : me(new PImpl()) {}
|
||||
TASAlgo::~TASAlgo() {}
|
||||
// initialization /////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
OrderOpResult TASAlgo::do_before_launch(IBeforeLaunchAlgoServices& serv) {
|
||||
auto& def = serv.definition();
|
||||
if (def.has_custom_def() && def.custom_def().has_dynamic()){
|
||||
const auto& sec = serv.security();
|
||||
me->sec_key = SecurityKey(sec.id());
|
||||
me->algo_params = _convert_params(def.custom_def());
|
||||
if (me->algo_params.child_orders.size() > 0){
|
||||
for (const auto& t : me->algo_params.child_orders){
|
||||
Price price = std::get<0>(t);
|
||||
Quantity quantity = std::get<1>(t);
|
||||
api::Side side = std::get<2>(t);
|
||||
auto* ordt = _create_order_template(
|
||||
serv,
|
||||
SecurityKey(sec.id()),
|
||||
side);
|
||||
me->legs.emplace_back(side,
|
||||
price,
|
||||
quantity,
|
||||
ordt);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {OrderError::ORDER_ERROR_INVALID_PARAMETER,
|
||||
std::string("Parameters are not well formed")};
|
||||
}
|
||||
void TASAlgo::do_launch(IAlgoServices& algo_services) {
|
||||
me->algo_services = &algo_services;
|
||||
_algo().subscribe_status(me->sec_key);
|
||||
//_algo().subscribe_timer();
|
||||
}
|
||||
void TASAlgo::do_launch_attached(IAlgoServices& algo_services,
|
||||
OrderKey,
|
||||
ChildState) {
|
||||
me->algo_services = &algo_services;
|
||||
_cxl_and_end(true, "TAS Algo can not be attached to working orders");
|
||||
}
|
||||
// updates ////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void TASAlgo::do_update(const api::orderman::Update&) {
|
||||
LOG(OrderMan, Warn, "TASAlgo::do_update order:{} state:{} update not allowed ",
|
||||
_algo().order_key().value(),
|
||||
static_cast<int>(me->state));
|
||||
}
|
||||
// callbacks //////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void TASAlgo::on_book(SecurityKey, const Book&) {
|
||||
}
|
||||
void TASAlgo::on_child_change(OrderKey key,
|
||||
ChildState const &st){
|
||||
LOG(OrderMan, Info,
|
||||
"TASAlgo::on_child_change key:{} child_key:{} state:{} status:{} "
|
||||
"total_fills:{} last_price:{}",
|
||||
_algo().order_key(),
|
||||
key,
|
||||
static_cast<int>(me->state),
|
||||
static_cast<int>(st.status),
|
||||
st.total_fills,
|
||||
st.last_price);
|
||||
TASLegData* ld_ptr = me->find_leg_from_ord_key(key);
|
||||
if (ld_ptr != nullptr)
|
||||
ld_ptr->order_state = st;
|
||||
for (const auto& l : me->legs)
|
||||
if(l.order_state.status == ChildStatus::WORKING)
|
||||
return;
|
||||
_cxl_and_end(false,"");
|
||||
}
|
||||
void TASAlgo::on_status(SecurityKey key, co2::TradingStatus status){
|
||||
if(key == me->sec_key &&
|
||||
status == co2::TradingStatus::TRADINGSTATUS_PRE_OPEN){
|
||||
switch (me->state){
|
||||
case TASAlgo::Status::WAITING_FOR_SIGNAL:
|
||||
for (auto& l : me->legs){
|
||||
l.order_key = _algo().launch_child(
|
||||
*l.order_template,
|
||||
l.price,
|
||||
l.quantity);
|
||||
LOG(OrderMan, Info,"TASAlgo::on_status launching order key:{} "
|
||||
"child_key:{} price:{} qty:{}",
|
||||
_algo().order_key(),
|
||||
l.order_key,
|
||||
l.price,
|
||||
l.quantity);
|
||||
}
|
||||
_switch_state(TASAlgo::Status::WORKING);
|
||||
break;
|
||||
case TASAlgo::Status::WORKING:
|
||||
case TASAlgo::Status::DONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void TASAlgo::on_timer(){
|
||||
if (bts::datetime::msecs_since(me->start_time) > 10000)
|
||||
on_status(me->sec_key,co2::TradingStatus::TRADINGSTATUS_PRE_OPEN);
|
||||
}
|
||||
IAlgoServices& TASAlgo::_algo() const { return *me->algo_services; }
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// stateless conversion ///////////////////////////////////////////////////////
|
||||
TASAlgo::TASAlgoParams TASAlgo::_convert_params (
|
||||
const bts::api::orderman::CustomOrderDefinition& cod){
|
||||
const DynamicOrderDefinition& def = cod.dynamic();
|
||||
const auto& params = def.order_params();
|
||||
TASAlgo::TASAlgoParams rv;
|
||||
for (size_t i = 0; i < 1024; i++){
|
||||
auto idx = std::to_string(i+1);
|
||||
auto price = get_if<double>(params, "Order " + idx + " Price");
|
||||
auto qty = get_if<uint32_t>(params, "Order " + idx + " Quantity");
|
||||
auto is_buy = get_if<bool>(params, "Order " + idx + " Buy?");
|
||||
if(price.has_value() && qty.has_value() && qty.value() != 0){
|
||||
api::Side side = is_buy.has_value() ?
|
||||
(is_buy.value() ? api::Side::SIDE_BUY : api::Side::SIDE_SELL) :
|
||||
api::Side::SIDE_SELL;
|
||||
rv.child_orders.emplace_back(Price(price.value()), Quantity(qty.value()), side);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
ChildTemplate* TASAlgo::_create_order_template (
|
||||
IBeforeLaunchAlgoServices& serv,
|
||||
SecurityKey key,
|
||||
api::Side side){
|
||||
OrderDefinition order_def = serv.definition();
|
||||
order_def.set_security_key (key.value());
|
||||
order_def.set_side(side);
|
||||
order_def.mutable_custom_def()->mutable_limit()->MergeFrom (
|
||||
api::orderman::LimitOrderDefinition());
|
||||
return &(serv.make_child_template(order_def));
|
||||
}
|
||||
void TASAlgo::_switch_state(TASAlgo::Status new_state){
|
||||
switch (new_state){
|
||||
case (TASAlgo::Status::WAITING_FOR_SIGNAL):
|
||||
bts_assert(false);
|
||||
break;
|
||||
case (TASAlgo::Status::WORKING):
|
||||
bts_assert(me->state == TASAlgo::Status::WAITING_FOR_SIGNAL);
|
||||
break;
|
||||
case (TASAlgo::Status::DONE):
|
||||
bts_assert(me->state != TASAlgo::Status::DONE);
|
||||
break;
|
||||
}
|
||||
LOG(OrderMan, Info,"TASAlgo::_switch_state key:{} {}->{} ",
|
||||
_algo().order_key(),
|
||||
static_cast<int>(me->state),
|
||||
static_cast<int>(new_state));
|
||||
me->state = new_state;
|
||||
}
|
||||
|
||||
void TASAlgo::_cxl_and_end (bool is_error, const std::string& err_msg){
|
||||
if(is_error)
|
||||
_algo().terminate_error(err_msg);
|
||||
else
|
||||
_algo().complete();
|
||||
_switch_state(TASAlgo::Status::DONE);
|
||||
}
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class ChildTemplate;
|
||||
struct LegData;
|
||||
|
||||
class TASAlgo : public IAlgoOrder {
|
||||
public:
|
||||
TASAlgo();
|
||||
~TASAlgo();
|
||||
struct TASAlgoParams{
|
||||
std::vector<std::tuple<Price, Quantity, api::Side>> child_orders;
|
||||
};
|
||||
public:
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
void on_status(SecurityKey, co2::TradingStatus) override;
|
||||
void on_timer() override;
|
||||
|
||||
private:
|
||||
enum class Status;
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
private:
|
||||
IAlgoServices& _algo() const ;
|
||||
static TASAlgoParams _convert_params (
|
||||
const bts::api::orderman::CustomOrderDefinition&);
|
||||
static ChildTemplate* _create_order_template (IBeforeLaunchAlgoServices&,
|
||||
SecurityKey,
|
||||
api::Side);
|
||||
// acts on md, cognizant of the state
|
||||
void _switch_state (Status new_status);
|
||||
void _cxl_and_end (bool is_error, const std::string& err_msg);
|
||||
};
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,490 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/TASCounterAlgo.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/orderman/orders/algo/IAlgoServices.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <google/protobuf/timestamp.pb.h>
|
||||
#include "lib/orderman/actors/SODPosition.h"
|
||||
#include <google/protobuf/util/time_util.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
using namespace api::orderman;
|
||||
using namespace google::protobuf;
|
||||
|
||||
enum class TASCounterAlgo::Status { WORKING, DONE };
|
||||
|
||||
// book and trade data for each of the 6 contracts as well as the TAS contracts
|
||||
// (the vwap of the TAS contracts is not used, but for uniformity is kept in the
|
||||
// the same structure)
|
||||
struct ContractMarketData{
|
||||
uint32_t tot_vol = 0;
|
||||
double running_sum = 0;
|
||||
Book book;
|
||||
double estimated_settle = 0.0;
|
||||
void on_trade (const TickerUpdateEvent::Trade& t){
|
||||
tot_vol += t.quantity.value();
|
||||
running_sum += t.price.value()*t.quantity.value();
|
||||
}
|
||||
const double vwap(){
|
||||
return tot_vol == 0 ? os::nan() : running_sum/tot_vol;
|
||||
}
|
||||
};
|
||||
// the input for the t1 front, second and third orders, as well as the template
|
||||
// and working order key are kept here
|
||||
struct ContractAutoOrder{
|
||||
// inputs
|
||||
uint32_t n_secs = 0;
|
||||
uint32_t qty = 0;
|
||||
bool enabled = false;
|
||||
// dont know yet which side will be sent
|
||||
ChildTemplate* buy_ord = nullptr;
|
||||
ChildTemplate* sell_ord = nullptr;
|
||||
// computed input
|
||||
// the is AlgoData::settle_stop - n_secs
|
||||
Timestamp algo_start;
|
||||
// output
|
||||
std::optional<OrderKey> tas_order = std::nullopt;
|
||||
};
|
||||
|
||||
struct AlgoData{
|
||||
double tick_size;
|
||||
Timestamp settle_start;
|
||||
Timestamp settle_stop;
|
||||
ContractMarketData t1_front,
|
||||
t1_second,
|
||||
t1_third,
|
||||
t2_front,
|
||||
t2_second,
|
||||
t3_front;
|
||||
ContractMarketData t1_front_tas,
|
||||
t1_second_tas,
|
||||
t1_third_tas;
|
||||
ContractAutoOrder t1_front_order,
|
||||
t1_second_order,
|
||||
t1_third_order;
|
||||
DynamicOrderState out_state;
|
||||
};
|
||||
|
||||
struct TASCounterAlgo::PImpl {
|
||||
// reporting
|
||||
uint16_t reporting_period_msecs = 500;
|
||||
bts::datetime::PTime last_report_time = bts::datetime::now();
|
||||
// immutable state
|
||||
IAlgoServices* algo_services = nullptr;
|
||||
// mutable state
|
||||
AlgoData data;
|
||||
// to avoid lookups on md and ticker callbacks
|
||||
std::map<SecurityKey,ContractMarketData*> md_map;
|
||||
Status state = Status::WORKING;
|
||||
};
|
||||
|
||||
//fwd decls
|
||||
void _copy_output(AlgoData&);
|
||||
bool _copy_settle_times (AlgoData&,const Map<std::string,DynamicOrderParam>&);
|
||||
void _copy_order_data (AlgoData&,const Map<std::string,DynamicOrderParam>&);
|
||||
bool _setup_data(IBeforeLaunchAlgoServices&, AlgoData&,
|
||||
std::map<SecurityKey,ContractMarketData*>&);
|
||||
double round_to_nearest(double price, double tick_size);
|
||||
long long t1_minus_t2_usecs (const Timestamp&, const Timestamp&);
|
||||
long long t_minus_now_usecs (const google::protobuf::Timestamp&);
|
||||
bool trade_within_time_win (const Timestamp&, const Timestamp&,
|
||||
const TickerUpdateEvent::Trade& t);
|
||||
bool now_within_time_win (const Timestamp&, const Timestamp&);
|
||||
|
||||
|
||||
TASCounterAlgo::TASCounterAlgo() : me(new PImpl()) {}
|
||||
TASCounterAlgo::~TASCounterAlgo() {}
|
||||
// initialization /////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
OrderOpResult TASCounterAlgo::do_before_launch(IBeforeLaunchAlgoServices& serv) {
|
||||
if(_setup_data(serv,me->data,me->md_map))
|
||||
return {};
|
||||
return {OrderError::ORDER_ERROR_INVALID_PARAMETER,
|
||||
std::string("Parameters are not well formed")};
|
||||
}
|
||||
void TASCounterAlgo::do_launch(IAlgoServices& algo_services) {
|
||||
me->algo_services = &algo_services;
|
||||
algo_services.subscribe_timer();
|
||||
for (const auto& kp : me->md_map){
|
||||
algo_services.subscribe_book(kp.first);
|
||||
algo_services.subscribe_ticker(kp.first);
|
||||
}
|
||||
}
|
||||
void TASCounterAlgo::do_launch_attached(
|
||||
IAlgoServices& algo_services,
|
||||
OrderKey,
|
||||
ChildState) {
|
||||
// not allowed
|
||||
me->algo_services = &algo_services;
|
||||
}
|
||||
// updates ////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void TASCounterAlgo::do_update(const api::orderman::Update& u) {
|
||||
if(me->state == Status::DONE)
|
||||
return;
|
||||
const auto& p = u.custom().dynamic().order_params();
|
||||
// only order data change is allowed
|
||||
_copy_order_data(me->data,p);
|
||||
_check_and_send_order();
|
||||
}
|
||||
// callbacks //////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void TASCounterAlgo::on_book(SecurityKey k, const Book& b) {
|
||||
if(me->state == Status::DONE)
|
||||
return;
|
||||
const auto& it = me->md_map.find(k);
|
||||
if(it != me->md_map.end()){
|
||||
it->second->book = b;
|
||||
_check_and_send_order();
|
||||
}
|
||||
}
|
||||
void TASCounterAlgo::on_ticker(SecurityKey k, const TradeVector& v){
|
||||
if(me->state == Status::DONE)
|
||||
return;
|
||||
const auto& it = me->md_map.find(k);
|
||||
if(it != me->md_map.end()){
|
||||
auto contract_md = it->second;
|
||||
for (const auto& t : v)
|
||||
if (_within_settle()){
|
||||
contract_md->on_trade(t);
|
||||
}
|
||||
_check_and_send_order();
|
||||
}
|
||||
}
|
||||
|
||||
// this only gets called on an explicit algo cancel
|
||||
// from the outside
|
||||
TASCounterAlgo::OnCancelResponse TASCounterAlgo::on_cancel(){
|
||||
return IAlgoOrder::OnCancelResponse::HONOR;
|
||||
}
|
||||
|
||||
void TASCounterAlgo::on_timer(){
|
||||
if(me->state == Status::DONE)
|
||||
return;
|
||||
_check_and_send_order();
|
||||
if(bts::datetime::msecs_since(me->last_report_time) >=
|
||||
me->reporting_period_msecs){
|
||||
me->last_report_time = bts::datetime::now();
|
||||
_report_state();
|
||||
}
|
||||
// check for end and exit
|
||||
if (t1_minus_t2_usecs(util::TimeUtil::GetCurrentTime(),
|
||||
me->data.settle_stop) > 0){
|
||||
std::string msg = "session over";
|
||||
_cxl_and_end(false,msg);
|
||||
}
|
||||
}
|
||||
|
||||
IAlgoServices& TASCounterAlgo::_algo() const { return *me->algo_services; }
|
||||
|
||||
void TASCounterAlgo::_switch_state(TASCounterAlgo::Status new_state){
|
||||
switch (new_state){
|
||||
case (TASCounterAlgo::Status::WORKING):
|
||||
bts_assert(false);
|
||||
break;
|
||||
case (TASCounterAlgo::Status::DONE):
|
||||
bts_assert(me->state == TASCounterAlgo::Status::WORKING);
|
||||
break;
|
||||
}
|
||||
LOG(OrderMan, Info,"TASCounterAlgo::_switch_state key:{} {}->{} ",
|
||||
_algo().order_key(),
|
||||
static_cast<int>(me->state),
|
||||
static_cast<int>(new_state));
|
||||
me->state = new_state;
|
||||
}
|
||||
|
||||
void TASCounterAlgo::_cxl_and_end (bool is_error, const std::string& err_msg){
|
||||
if(me->state == Status::DONE)
|
||||
return;
|
||||
if(is_error)
|
||||
_algo().terminate_error(err_msg);
|
||||
else
|
||||
_algo().complete();
|
||||
_switch_state(TASCounterAlgo::Status::DONE);
|
||||
}
|
||||
|
||||
void TASCounterAlgo::_compute_settles(){
|
||||
AlgoData& d = me->data;
|
||||
d.t1_front.estimated_settle = d.t1_front.vwap();
|
||||
d.t1_second.estimated_settle = d.t2_front.vwap() - d.t1_front.vwap();
|
||||
d.t1_third.estimated_settle = d.t3_front.vwap() - d.t1_front.vwap() -
|
||||
d.t1_second.vwap();
|
||||
// round to closest value
|
||||
d.t1_front.estimated_settle = round_to_nearest(
|
||||
d.t1_front.estimated_settle,me->data.tick_size);
|
||||
d.t1_second.estimated_settle = round_to_nearest(
|
||||
d.t1_second.estimated_settle,me->data.tick_size);
|
||||
d.t1_third.estimated_settle = round_to_nearest(
|
||||
d.t1_third.estimated_settle,me->data.tick_size);
|
||||
}
|
||||
|
||||
// the decision to send an order can be caused by
|
||||
// 1. change in settings (=> time window, enabling orders etc)
|
||||
// 2. book update (=> TAS book can result in order)
|
||||
// 3. ticker (=> new estimated settlement)
|
||||
// 4. time passing (=> in the window now)
|
||||
void TASCounterAlgo::_check_and_send_order(){
|
||||
_compute_settles();
|
||||
auto should_send_tas_order =
|
||||
[](const ContractMarketData& c,
|
||||
const ContractAutoOrder& o,
|
||||
const ContractMarketData& tas_c,
|
||||
bool is_buy,
|
||||
double& out_price,
|
||||
uint32_t& out_qty) -> bool {
|
||||
// buying the tas at the offer or selling it at the bid
|
||||
const auto& tas_opp_top = is_buy ? tas_c.book.ask()[0] : tas_c.book.bid()[0];
|
||||
const auto& c_same_top = is_buy ? c.book.bid()[0] : c.book.ask()[0];
|
||||
if(tas_opp_top.size() > 0 && c_same_top.size() > 0){
|
||||
// delivery price as defined by where the TAS order is traded
|
||||
double tas_delv_price = c.estimated_settle + tas_opp_top.price();
|
||||
// if the delv price for the buy order is <= contract bid or
|
||||
// or the delv price for the sell order is >= contract offer
|
||||
// then we are buying at the bid or selling at the offer
|
||||
double c_price = c_same_top.price();
|
||||
if((is_buy && tas_delv_price <= c_price) ||
|
||||
(!is_buy && tas_delv_price >= c_price)){
|
||||
out_qty = std::min(o.qty,tas_opp_top.size());
|
||||
out_price = tas_opp_top.price();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto _send_order_tn = [&](
|
||||
const ContractMarketData& c,
|
||||
ContractAutoOrder& o,
|
||||
const ContractMarketData& tas_c) -> void {
|
||||
if(o.enabled && o.n_secs > 0 && o.qty > 0 &&
|
||||
!o.tas_order.has_value() &&
|
||||
now_within_time_win(o.algo_start,me->data.settle_stop)){
|
||||
double price;
|
||||
uint32_t qty;
|
||||
if(should_send_tas_order(c,o,tas_c,true,price,qty)){
|
||||
o.tas_order = _algo().launch_child(*o.buy_ord,
|
||||
Price(price),
|
||||
Quantity(qty));
|
||||
}
|
||||
else if(should_send_tas_order(c,o,tas_c,false,price,qty)){
|
||||
o.tas_order = _algo().launch_child(*o.sell_ord,
|
||||
Price(price),
|
||||
Quantity(qty));
|
||||
}
|
||||
}
|
||||
};
|
||||
_send_order_tn(me->data.t1_front,me->data.t1_front_order,me->data.t1_front_tas);
|
||||
_send_order_tn(me->data.t1_second,me->data.t1_second_order,me->data.t1_second_tas);
|
||||
_send_order_tn(me->data.t1_third,me->data.t1_third_order,me->data.t1_third_tas);
|
||||
}
|
||||
|
||||
void TASCounterAlgo::_report_state(){
|
||||
_compute_settles();
|
||||
_copy_output(me->data);
|
||||
_algo().update_custom_state(me->data.out_state);
|
||||
}
|
||||
|
||||
bool TASCounterAlgo::_within_settle() const{
|
||||
return now_within_time_win(me->data.settle_start,me->data.settle_stop);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// stateless conversion ///////////////////////////////////////////////////////
|
||||
|
||||
// stateless helpers
|
||||
ChildTemplate* _create_order_template (IBeforeLaunchAlgoServices& serv,
|
||||
SecurityKey key,
|
||||
api::Side side){
|
||||
OrderDefinition order_def = serv.definition();
|
||||
order_def.set_security_key (key.value());
|
||||
order_def.set_side(side);
|
||||
order_def.mutable_custom_def()->mutable_limit()->MergeFrom (
|
||||
api::orderman::LimitOrderDefinition());
|
||||
return &(serv.make_child_template(order_def));
|
||||
}
|
||||
// copies output into the dynamic state for publishing
|
||||
void _copy_output(AlgoData& d){
|
||||
auto& m = *(d.out_state.mutable_custom_state());
|
||||
// we can safely assume its within int32
|
||||
auto secs_to_settle = static_cast<int>(t_minus_now_usecs(d.settle_start)/1000000);
|
||||
// if its in settle (-ve value), its set to 0
|
||||
secs_to_settle = std::max(secs_to_settle,0);
|
||||
m["Seconds To Settle"].set_as_int32(secs_to_settle);
|
||||
m["VWAP T1-Front"].set_as_double(d.t1_front.vwap());
|
||||
m["VWAP T1-Second"].set_as_double(d.t1_second.vwap());
|
||||
m["VWAP T1-Third"].set_as_double(d.t1_third.vwap());
|
||||
m["VWAP T2-Front"].set_as_double(d.t2_front.vwap());
|
||||
m["VWAP T2-Second"].set_as_double(d.t2_second.vwap());
|
||||
m["VWAP T3-Front"].set_as_double(d.t3_front.vwap());
|
||||
m["T1-Front Projected Settle"].set_as_double(d.t1_front.estimated_settle);
|
||||
m["T1-Second Projected Settle"].set_as_double(d.t1_second.estimated_settle);
|
||||
m["T1-Third Projected Settle"].set_as_double(d.t1_third.estimated_settle);
|
||||
}
|
||||
// copies the settle time/period part of the input data
|
||||
bool _copy_settle_times (AlgoData& d,
|
||||
const Map<std::string,DynamicOrderParam>& params){
|
||||
auto hh = get_if<uint32_t>(params, "Settlement Start Hour");
|
||||
auto mm = get_if<uint32_t>(params, "Settlement Start Minutes");
|
||||
auto ss = get_if<uint32_t>(params, "Settlement Start Seconds");
|
||||
auto settle_period_secs = get_if<uint32_t>(params, "Settlement Period (seconds)");
|
||||
if (hh.has_value() && mm.has_value() &&
|
||||
ss.has_value() && settle_period_secs.has_value() &&
|
||||
hh.value() <= 23 && mm.value() <= 59 && ss.value() <= 59){
|
||||
d.settle_start = SODPosition::today_local_time_to_utc_timestamp(
|
||||
hh.value(),
|
||||
mm.value(),
|
||||
ss.value(),
|
||||
true,
|
||||
false);
|
||||
d.settle_stop = SODPosition::add_seconds(d.settle_start,
|
||||
settle_period_secs.value());
|
||||
auto settle_start_txt = util::TimeUtil::ToString(d.settle_start);
|
||||
auto settle_stop_txt = util::TimeUtil::ToString(d.settle_stop);
|
||||
LOG(OrderMan, Info, "TASCounterAlgo::_copy_settle_times "
|
||||
"settle_start:{} settle_stop:{} ",
|
||||
settle_start_txt,
|
||||
settle_stop_txt);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// copies the order execution part of the input data
|
||||
void _copy_order_data (AlgoData& out_d,
|
||||
const Map<std::string,DynamicOrderParam>& params){
|
||||
auto copy_o = [&](ContractAutoOrder& out_order,
|
||||
std::string time_name,
|
||||
std::string qty_name,
|
||||
std::string enabled_name
|
||||
) -> bool {
|
||||
out_order.enabled = false;
|
||||
auto time = get_if<uint32_t>(params, time_name);
|
||||
auto qty = get_if<uint32_t>(params, qty_name);
|
||||
auto enabled = get_if<bool>(params, enabled_name);
|
||||
if(time.has_value() && qty.has_value() && enabled.has_value()){
|
||||
out_order.n_secs = time.value();
|
||||
out_order.qty = qty.value();
|
||||
out_order.enabled = enabled.value();
|
||||
out_order.algo_start = SODPosition::add_seconds(out_d.settle_stop,
|
||||
-1 * out_order.n_secs);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
copy_o(out_d.t1_front_order,
|
||||
"T1 Front Time",
|
||||
"T1 Front Qty",
|
||||
"T1 Front Order Enabled");
|
||||
copy_o(out_d.t1_second_order,
|
||||
"T1 Second Time",
|
||||
"T1 Second Qty",
|
||||
"T1 Second Order Enabled");
|
||||
copy_o(out_d.t1_third_order,
|
||||
"T1 Third Time",
|
||||
"T1 Third Qty",
|
||||
"T1 Third Order Enabled");
|
||||
}
|
||||
// copies all of the input params (in the algo) into proper structures
|
||||
bool _setup_data(IBeforeLaunchAlgoServices& serv,
|
||||
AlgoData& out_d,
|
||||
std::map<SecurityKey,ContractMarketData*>& out_md_map){
|
||||
auto& def = serv.definition();
|
||||
// [ captures ] (parameters) -> returnTypesDeclaration { lambdaStatements; }
|
||||
auto init_contract_bk = [&out_md_map](auto& out_contract_md,
|
||||
const auto& params,
|
||||
auto param_name) -> void {
|
||||
auto k = get_if<uint32_t>(params, param_name);
|
||||
if(k.has_value()){
|
||||
out_contract_md.book = Book(SecurityKey(k.value()));
|
||||
out_md_map[out_contract_md.book.security_key()] = &out_contract_md;
|
||||
}
|
||||
};
|
||||
if (def.has_custom_def() && def.custom_def().has_dynamic()){
|
||||
out_d.tick_size = serv.security().contract_spec().low_tick();
|
||||
const auto& p = def.custom_def().dynamic().order_params();
|
||||
// settlement time
|
||||
if(!_copy_settle_times(out_d,p))
|
||||
return false;
|
||||
// main contracts
|
||||
init_contract_bk(out_d.t1_front, p,"T1 Front");
|
||||
init_contract_bk(out_d.t1_second, p,"T1 Second");
|
||||
init_contract_bk(out_d.t1_third, p,"T1 Third");
|
||||
init_contract_bk(out_d.t2_front, p,"T2 Front");
|
||||
init_contract_bk(out_d.t2_second, p,"T2 Second");
|
||||
init_contract_bk(out_d.t3_front, p,"T3 Front");
|
||||
// TAS contracts
|
||||
init_contract_bk(out_d.t1_front_tas, p,"T1 Front TAS");
|
||||
init_contract_bk(out_d.t1_second_tas, p,"T1 Second TAS");
|
||||
init_contract_bk(out_d.t1_third_tas, p,"T1 Third TAS");
|
||||
// auto orders
|
||||
_copy_order_data(out_d,p);
|
||||
// order template
|
||||
out_d.t1_front_order.buy_ord = _create_order_template(serv,
|
||||
out_d.t1_front_tas.book.security_key(),api::Side::SIDE_BUY);
|
||||
out_d.t1_front_order.sell_ord = _create_order_template(serv,
|
||||
out_d.t1_front_tas.book.security_key(),api::Side::SIDE_SELL);
|
||||
out_d.t1_second_order.buy_ord = _create_order_template(serv,
|
||||
out_d.t1_second_tas.book.security_key(),api::Side::SIDE_BUY);
|
||||
out_d.t1_second_order.sell_ord = _create_order_template(serv,
|
||||
out_d.t1_second_tas.book.security_key(),api::Side::SIDE_SELL);
|
||||
out_d.t1_third_order.buy_ord = _create_order_template(serv,
|
||||
out_d.t1_third_tas.book.security_key(),api::Side::SIDE_BUY);
|
||||
out_d.t1_third_order.sell_ord = _create_order_template(serv,
|
||||
out_d.t1_third_tas.book.security_key(),api::Side::SIDE_SELL);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
double round_to_nearest(double price, double tick_size){
|
||||
if(os::is_nan(price))
|
||||
return price;
|
||||
auto buy_price = bts::securities::raw_round_to_quote_price(
|
||||
price,
|
||||
tick_size,
|
||||
api::Side::SIDE_BUY);
|
||||
auto sell_price = buy_price + tick_size;
|
||||
return std::abs(price-buy_price) < std::abs(sell_price-price) ? buy_price :
|
||||
sell_price;
|
||||
}
|
||||
// time helpers
|
||||
// converts std::chrono::system_clock::time_point to google timestamp
|
||||
google::protobuf::Timestamp to_pf_ts (const bts::TimeStamp& ts){
|
||||
auto ts_us = std::chrono::time_point_cast<std::chrono::microseconds>(ts);
|
||||
auto ts_us_val = ts_us.time_since_epoch();
|
||||
auto usecs = ts_us_val.count();
|
||||
return google::protobuf::util::TimeUtil::MicrosecondsToTimestamp(usecs);
|
||||
}
|
||||
long long t1_minus_t2_usecs (const Timestamp& t1,const Timestamp& t2){
|
||||
return util::TimeUtil::TimestampToMicroseconds(t1) -
|
||||
util::TimeUtil::TimestampToMicroseconds(t2);
|
||||
}
|
||||
long long t_minus_now_usecs (const Timestamp& t){
|
||||
auto now = util::TimeUtil::GetCurrentTime();
|
||||
return t1_minus_t2_usecs(t,now);
|
||||
}
|
||||
bool within_time_win (const Timestamp& beg, const Timestamp& end,
|
||||
const Timestamp& ts){
|
||||
return t1_minus_t2_usecs(ts,beg)>=0 &&
|
||||
t1_minus_t2_usecs(ts,end)<=0;
|
||||
}
|
||||
// if current time is within time window
|
||||
bool now_within_time_win (const Timestamp& beg,
|
||||
const Timestamp& end){
|
||||
return within_time_win(beg,end,util::TimeUtil::GetCurrentTime());
|
||||
}
|
||||
bool trade_within_time_win (const Timestamp& beg, const Timestamp& end,
|
||||
const TickerUpdateEvent::Trade& t){
|
||||
auto ts = to_pf_ts(t.ts);
|
||||
return within_time_win(beg,end,ts);
|
||||
}
|
||||
|
||||
} // namespace orderman
|
||||
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class ChildTemplate;
|
||||
|
||||
class TASCounterAlgo : public IAlgoOrder {
|
||||
public:
|
||||
TASCounterAlgo();
|
||||
~TASCounterAlgo();
|
||||
struct TASCounterAlgoParams{
|
||||
};
|
||||
public:
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void on_ticker(SecurityKey, TradeVector const&) override;
|
||||
IAlgoOrder::OnCancelResponse on_cancel() override;
|
||||
void on_timer() override;
|
||||
private:
|
||||
enum class Status;
|
||||
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
private:
|
||||
IAlgoServices& _algo() const ;
|
||||
void _switch_state (Status new_status);
|
||||
void _cxl_and_end (bool is_error, const std::string& err_msg);
|
||||
void _check_and_send_order();
|
||||
void _compute_settles ();
|
||||
void _report_state();
|
||||
bool _within_settle() const;
|
||||
};
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,152 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
|
||||
#include "lib/orderman/orders/OrderOpResult.h"
|
||||
#include "lib/orderman/orders/algo/utils/DynamicOrderAdapter.h"
|
||||
#include <utility>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
template<typename T>
|
||||
using Param = bts::orderman::dynamic_adapter::ParamKey<bts::orderman::LimitCoverDefinition, T>;
|
||||
using DynamicOrderEnumerated = bts::api::orderman::DynamicOrderEnumerated;
|
||||
using OnToward = bts::api::orderman::LeanOnToward;
|
||||
using LimitCoverDefinition = bts::orderman::LimitCoverDefinition;
|
||||
using namespace bts::api::orderman;
|
||||
#define ON_TOWARD_NAME_PAIR(x) std::make_pair(x, #x)
|
||||
|
||||
template<uint32_t gate_num>
|
||||
std::string gate_param_name(std::string name) {
|
||||
return Formatter::fmt("Gate ", gate_num, " ", name);
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string gate_param_name<0>(std::string name) {
|
||||
return Formatter::fmt("Initial ", name);
|
||||
}
|
||||
|
||||
struct PriceOffset : public Param<double> {
|
||||
PriceOffset() : Param<double>(std::string("Limit"),
|
||||
[](const LimitCoverDefinition& def) { return def.price_offset(); },
|
||||
[](LimitCoverDefinition& def, double v) { def.price_offset(v); }) {}
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct ActivationThreshold : public Param<uint32_t> {
|
||||
ActivationThreshold() : Param<uint32_t>(gate_param_name<gate_num>("Activation Threshold"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).activation_threshold(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).activation_threshold(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct CoverThreshold : public Param<uint32_t> {
|
||||
CoverThreshold() : Param<uint32_t>(gate_param_name<gate_num>("Cover Threshold"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).cover_threshold(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).cover_threshold(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct DeleteThreshold : public Param<uint32_t> {
|
||||
DeleteThreshold() : Param<uint32_t>(gate_param_name<gate_num>("Delete Threshold"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).delete_threshold(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).delete_threshold(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct HedgeTicks : public Param<uint32_t> {
|
||||
HedgeTicks() : Param<uint32_t>(gate_param_name<gate_num>("Hedge Ticks"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).hedge_ticks(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).hedge_ticks(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct TopTicksOff : public Param<uint32_t> {
|
||||
TopTicksOff() : Param<uint32_t>(gate_param_name<gate_num>("Top Ticks Offset"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).top_ticks_offset(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).top_ticks_offset(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct MaxChaseTicks : public Param<uint32_t> {
|
||||
MaxChaseTicks() : Param<uint32_t>(gate_param_name<gate_num>("Max Chase Ticks"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).max_chase_ticks(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).max_chase_ticks(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct TradeTowardBehavior : public dynamic_adapter::EnumParamKey<LimitCoverDefinition, OnToward> {
|
||||
TradeTowardBehavior() : EnumParamKey<LimitCoverDefinition, OnToward>(
|
||||
gate_param_name<gate_num>("Trade Toward Behavior"), {
|
||||
ON_TOWARD_NAME_PAIR(NO_CHANGE),
|
||||
ON_TOWARD_NAME_PAIR(CANCEL),
|
||||
ON_TOWARD_NAME_PAIR(DETACH),
|
||||
ON_TOWARD_NAME_PAIR(REPRICE)
|
||||
},
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).on_toward(); },
|
||||
[](LimitCoverDefinition& def, OnToward v) { def.mgate(gate_num).on_toward(v); })
|
||||
{}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ActivationThreshold<0> : public Param<uint32_t> {
|
||||
ActivationThreshold() : Param<uint32_t>(std::string("Entry Threshold"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(0).activation_threshold(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) {
|
||||
auto& g = def.mgate(0);
|
||||
g.activation_threshold(v);
|
||||
g.cover_threshold(v);
|
||||
g.delete_threshold(v);
|
||||
}) {}
|
||||
};
|
||||
|
||||
using Adapter = dynamic_adapter::DynamicOrderAdapter<LimitCoverDefinition,
|
||||
PriceOffset,
|
||||
// [gate 0]
|
||||
ActivationThreshold<0>,
|
||||
HedgeTicks<0>,
|
||||
TopTicksOff<0>,
|
||||
MaxChaseTicks<0>,
|
||||
TradeTowardBehavior<0>,
|
||||
// gate 1
|
||||
ActivationThreshold<1>,
|
||||
CoverThreshold<1>,
|
||||
DeleteThreshold<1>,
|
||||
HedgeTicks<1>,
|
||||
TopTicksOff<1>,
|
||||
MaxChaseTicks<1>,
|
||||
TradeTowardBehavior<1>,
|
||||
// gate 2
|
||||
ActivationThreshold<2>,
|
||||
CoverThreshold<2>,
|
||||
DeleteThreshold<2>,
|
||||
HedgeTicks<2>,
|
||||
TopTicksOff<2>,
|
||||
MaxChaseTicks<2>,
|
||||
TradeTowardBehavior<2>>;
|
||||
|
||||
LimitCoverDefinition LimitCoverAdapter::from_proto(const api::orderman::OrderDefinition& order) {
|
||||
if (order.has_custom_def())
|
||||
return from_proto(order.custom_def());
|
||||
return LimitCoverDefinition();
|
||||
}
|
||||
LimitCoverDefinition LimitCoverAdapter::from_proto(const api::orderman::CustomOrderDefinition& custom_def) {
|
||||
LimitCoverDefinition rv;
|
||||
if (custom_def.has_dynamic())
|
||||
Adapter().from_proto(custom_def.dynamic(), rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
#define INVALID_DEF(x) OrderOpResult(api::orderman::ORDER_ERROR_INVALID_ORDER_DEFINITON, x)
|
||||
|
||||
OrderOpResult LimitCoverAdapter::validate_proto(const api::orderman::DynamicOrderDefinition& dyn) {
|
||||
return Adapter().validate_proto(dyn) ?
|
||||
OrderOpResult() :
|
||||
INVALID_DEF("One or more dynamic parameters are missing or invalid.");
|
||||
}
|
||||
void LimitCoverAdapter::init_proto(const LimitCoverDefinition& def, api::orderman::DynamicOrderDefinition& dyn) {
|
||||
Adapter().to_proto(dyn, def);
|
||||
}
|
||||
|
||||
void LimitCoverAdapter::define_proto(api::orderman::DynamicOrderDescriptor& desc) {
|
||||
Adapter().define_proto(desc);
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverDefinition.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
struct LimitCoverAdapter {
|
||||
static LimitCoverDefinition from_proto(const api::orderman::OrderDefinition& order);
|
||||
static LimitCoverDefinition from_proto(const api::orderman::CustomOrderDefinition& custom_def);
|
||||
static OrderOpResult validate_proto(const api::orderman::DynamicOrderDefinition& order);
|
||||
static void init_proto(const LimitCoverDefinition& def, api::orderman::DynamicOrderDefinition& dyn);
|
||||
static void define_proto(api::orderman::DynamicOrderDescriptor& desc);
|
||||
};
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverGate.h"
|
||||
#include "lib/orderman/orderman_utils/Formatter.h"
|
||||
#include <ostream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace limit_cover {
|
||||
// The exclusive maximum number of gates.
|
||||
// Note: the limit cover spec called for 3 gates (2 gates explicitly,
|
||||
// plus an initial activation threshold, which is treated as another gate).
|
||||
const uint32_t max_num_gates = 4;
|
||||
}
|
||||
struct LimitCoverDefinition {
|
||||
// price offset
|
||||
double price_offset() const {
|
||||
return _price_offset;
|
||||
}
|
||||
void price_offset(double value) {
|
||||
_price_offset = value;
|
||||
}
|
||||
|
||||
LimitCoverGate gate(uint32_t index) const {
|
||||
return _gate_or_default(index);
|
||||
}
|
||||
LimitCoverGate& mgate(uint32_t index) {
|
||||
return _ensure_gate(index);
|
||||
}
|
||||
void gate(uint32_t index, LimitCoverGate value) {
|
||||
_ensure_gate(index) = value;
|
||||
}
|
||||
|
||||
// The original LimitAlgo spec called for an initial activation thresh, and 2 gates
|
||||
// (fyi a gate has 3 members: activation thresh, cover thresh, and delete thresh).
|
||||
// The initial activation thresh serves as the initial cover, and delete threshes as well.
|
||||
// In this sense, the activation threshold can be considered as an 'initial gate' itself.
|
||||
// 'normal_gates' does just that: returns a map of both gates plus the 'initial gate.'
|
||||
std::map<uint32_t, LimitCoverGate> normal_gates() const {
|
||||
std::map<uint32_t, LimitCoverGate> m;
|
||||
for (auto g : _gates)
|
||||
m.insert(std::make_pair(g.activation_threshold(), g));
|
||||
return m;
|
||||
}
|
||||
private:
|
||||
double _price_offset;
|
||||
std::vector<LimitCoverGate> _gates;
|
||||
|
||||
LimitCoverGate& _ensure_gate(uint32_t index) {
|
||||
bts_assert(index < limit_cover::max_num_gates);
|
||||
while (_gates.size() <= index)
|
||||
_gates.push_back(LimitCoverGate());
|
||||
return _gates[index];
|
||||
}
|
||||
|
||||
LimitCoverGate _gate_or_default(uint32_t index) const {
|
||||
return _gates.size() > index ? _gates[index] : LimitCoverGate();
|
||||
}
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const LimitCoverDefinition& lcd) {
|
||||
auto ngates = lcd.normal_gates();
|
||||
ostr << "Price Offset: " << lcd.price_offset()
|
||||
<< ", Activation Thresh: " << lcd.gate(0).activation_threshold()
|
||||
<< ", Gates: " << Formatter::fmt_values(ngates);
|
||||
return ostr;
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include <ostream>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
struct LimitCoverGate {
|
||||
LimitCoverGate() : LimitCoverGate(0, 0, 0) {}
|
||||
|
||||
LimitCoverGate(const LimitCoverGate& other)
|
||||
: _activation_threshold(other._activation_threshold), _definition(other._definition) {}
|
||||
|
||||
LimitCoverGate(uint32_t activation_thresh, uint32_t cover_thresh, uint32_t delete_thresh)
|
||||
: _activation_threshold(activation_thresh), _definition() {
|
||||
cover_threshold(cover_thresh);
|
||||
delete_threshold(delete_thresh);
|
||||
}
|
||||
|
||||
// activation threshold
|
||||
uint32_t activation_threshold() const { return _activation_threshold; }
|
||||
void activation_threshold(uint32_t value) { _activation_threshold = value; }
|
||||
|
||||
// cover threshold
|
||||
uint32_t cover_threshold() const {
|
||||
return _definition.has_cross_threshold() ? _get_absolute(_definition.cross_threshold()) : 0;
|
||||
}
|
||||
void cover_threshold(uint32_t value) {
|
||||
if (!_definition.has_cross_threshold()) {
|
||||
*_definition.mutable_cross_threshold() = bts::spark::QuantityMeasure();
|
||||
}
|
||||
_definition.mutable_cross_threshold()->set_absolute(value);
|
||||
}
|
||||
|
||||
// delete threshold
|
||||
uint32_t delete_threshold() const {
|
||||
return _definition.has_top_threshold() ? _get_absolute(_definition.top_threshold()) : 0;
|
||||
}
|
||||
void delete_threshold(uint32_t value) {
|
||||
if (!_definition.has_top_threshold()) {
|
||||
*_definition.mutable_top_threshold() = bts::spark::QuantityMeasure();
|
||||
}
|
||||
_definition.mutable_top_threshold()->set_absolute(value);
|
||||
}
|
||||
|
||||
// hedge ticks
|
||||
uint32_t hedge_ticks() const { return _definition.hedge_ticks(); }
|
||||
void hedge_ticks(uint32_t value) { _definition.set_hedge_ticks(value); }
|
||||
// top ticks offset
|
||||
uint32_t top_ticks_offset() const { return _definition.ticks_off(); }
|
||||
void top_ticks_offset(uint32_t value) { _definition.set_ticks_off(value); }
|
||||
// max chase ticks
|
||||
uint32_t max_chase_ticks() const { return _definition.max_chase_ticks(); }
|
||||
void max_chase_ticks(uint32_t value) { _definition.set_max_chase_ticks(value); }
|
||||
// on toward
|
||||
api::orderman::LeanOnToward on_toward() const { return _definition.trade_toward_behavior(); }
|
||||
void on_toward(api::orderman::LeanOnToward value) { _definition.set_trade_toward_behavior(value); }
|
||||
// definition
|
||||
const api::orderman::DeleteAndCoverDefinition& definition() const { return _definition; }
|
||||
api::orderman::DeleteAndCoverDefinition& mdefinition() { return _definition; }
|
||||
void definition(api::orderman::DeleteAndCoverDefinition value) { _definition = value; }
|
||||
|
||||
api::orderman::CustomOrderDefinition custom_def() const {
|
||||
api::orderman::CustomOrderDefinition cod;
|
||||
*(cod.mutable_delete_and_cover()) = definition();
|
||||
return cod;
|
||||
}
|
||||
void custom_def(const api::orderman::CustomOrderDefinition &cod) {
|
||||
if (cod.has_delete_and_cover())
|
||||
definition(cod.delete_and_cover());
|
||||
}
|
||||
private:
|
||||
uint32_t _activation_threshold;
|
||||
api::orderman::DeleteAndCoverDefinition _definition;
|
||||
|
||||
static inline uint32_t _get_absolute(spark::QuantityMeasure qty_measure, uint32_t fallback_value = 0) {
|
||||
return qty_measure.quantity_case() == spark::QuantityMeasure::kAbsolute ? qty_measure.absolute() : fallback_value;
|
||||
}
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const LimitCoverGate& g) {
|
||||
ostr << "Activation Thresh: " << g.activation_threshold() << ", Definition: " << utils::pb::to_string(g.custom_def());
|
||||
return ostr;
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/LimitCoverAlgo.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/AlgoTestDriver.h"
|
||||
#include "catch/catch.hpp"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orderman_utils/BookUtils.h"
|
||||
#include "lib/orderman/orderman_utils/Formatter.h"
|
||||
#include "lib/orderman/orderman_utils/PriceComp.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverState.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverTestUtils.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/TestSteps.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <array>
|
||||
#include <random>
|
||||
#include <variant>
|
||||
#include <cmath>
|
||||
|
||||
// for some reason, intellisense poops its pants without 'using namespace std.'
|
||||
// std::optional<T>, and std::unordered_map<T>, in particular, break intellisense for the whole document.
|
||||
using namespace std;
|
||||
using namespace bts::api::orderman;
|
||||
using namespace bts::orderman::limit_cover::utils;
|
||||
//#define PRINT_SUMMARIES
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
// Glossary:
|
||||
// * limit-price:
|
||||
// The price at which the main child is deployed (computed as a tick offset from the settlement).
|
||||
// * limit-volume:
|
||||
// The same-side market volume at or better than the limit price.
|
||||
// This is recomputed whenever the book changes (obviously).
|
||||
// * volume-gate:
|
||||
// * volume-gate: a simple struct with 3 fields:
|
||||
// 1) activation_threshold (uint)
|
||||
// 2) cover_threshold (uint) must be <= activation_thresh
|
||||
// 3) delete_threshold (uint) must be <= activation_thresh
|
||||
// * current-volume-gate:
|
||||
// The volume gate with the highest activation threshold that is still
|
||||
// <= the limit-volume. This is recomputed whenever the book changes
|
||||
// (obviously).
|
||||
// * volume-gates:
|
||||
// A series of volume gates, sorted by 'activation_threshold.'
|
||||
// Initially, the current-volume-gate is one with the lowest activation threshold.
|
||||
// The main child is not deployed until the limit-volume reaches that threshold.
|
||||
// After that, the algo continues to observe the limit-volume, removing gates with lower
|
||||
// thresholds when the volume breeches the next gate's thresh.
|
||||
|
||||
// Test Assertions:
|
||||
// * child is not launched until the limit-volume reaches the initial activation threshold.
|
||||
// * limit-volume is always correct (verify whenever the book changes).
|
||||
// * current-volume-gate is always correct (verify whenever the book changes).
|
||||
|
||||
// Test Design:
|
||||
// * expectations of the limit cover algo's state are stored in an instance of 'LimitCoverState.'
|
||||
// The actual LimitCoverAlgo does not use this class; the tests just use it as a means
|
||||
// to validate the actual LimitCoverAlgo's state, and to wrap calls to the LimitCoverAlgo
|
||||
// with validation checks.
|
||||
|
||||
// * each test implementation should be executed 6 times:
|
||||
// 2 x 3 = test both sides, with each of the following termination steps:
|
||||
// 1) Fill to completion
|
||||
// 2) Cancel the algo
|
||||
// 3) Cancel the child
|
||||
|
||||
// * Restrictions on the parameters:
|
||||
// - the spec explicitly called for 3 volume gates, and specified that the 3 thresholds
|
||||
// (activation, cover, and delete) be the same for the first gate
|
||||
// (technically, the spec described 2 gates and an 'entry threshold').
|
||||
// - the algo should immediately cancel itself if these parameters are not the case.
|
||||
// while it would most likely not cause a problem, these assumptions allow the tests to focus
|
||||
// on a narrow range of inputs (and of course, such restrictions naturally decrease the
|
||||
// chance of a bug slipping through).
|
||||
|
||||
LimitCoverState init_state(
|
||||
OrderDefinition& mdef,
|
||||
Side side,
|
||||
uint32_t quantity,
|
||||
double price,
|
||||
double price_offset,
|
||||
std::vector<LimitCoverGate> gates,
|
||||
bool cancel_hedges_on_cancel = true,
|
||||
bool cancel_on_external_hedge_cancel = false) {
|
||||
//
|
||||
REQUIRE(!gates.empty());
|
||||
for (auto& g : gates) {
|
||||
g.mdefinition().set_cancel_hedges_on_cancel(cancel_hedges_on_cancel);
|
||||
g.mdefinition().set_cancel_on_external_hedge_cancel(cancel_on_external_hedge_cancel);
|
||||
}
|
||||
LimitCoverDefinition lc_def;
|
||||
lc_def.price_offset(price_offset);
|
||||
uint32_t gate_pos = 0;
|
||||
for (auto g : gates) {
|
||||
lc_def.gate(gate_pos++, g);
|
||||
}
|
||||
DynamicOrderDefinition dyn_def;
|
||||
LimitCoverAdapter::init_proto(lc_def, dyn_def);
|
||||
*(mdef.mutable_custom_def()->mutable_dynamic()) = dyn_def;
|
||||
mdef.set_side(side);
|
||||
return LimitCoverState(mdef, quantity, price);
|
||||
}
|
||||
|
||||
template<typename... Steps>
|
||||
inline void test_impl(Side side, uint32_t qty, double price_offset, double settle, std::vector<LimitCoverGate> gates, Steps... steps) {
|
||||
std::cout << std::endl << std::endl;
|
||||
std::cout << "*************************************************************************************************************************" << std::endl;
|
||||
std::cout << "=========================================================================================================================" << std::endl;
|
||||
int step_count = 1;
|
||||
std::cout << "Test " << "[" << Formatter::to_string(side) << " Order]" << std::endl << "\t1. "
|
||||
<< Formatter::join([&]() {
|
||||
return Formatter::fmt("\n\t", ++step_count, ". ");
|
||||
}, steps...)
|
||||
<< std::endl;
|
||||
std::cout << "=========================================================================================================================" << std::endl;
|
||||
std::cout << "*************************************************************************************************************************" << std::endl;
|
||||
std::cout << std::endl;
|
||||
OrderDefinition def;
|
||||
// price is irrelevant
|
||||
auto state = init_state(def, side, qty, -99999, price_offset, gates);
|
||||
state.settlement(settle);
|
||||
(steps.execute(state), ...);
|
||||
}
|
||||
|
||||
template<typename... Steps>
|
||||
inline void test_permutations(
|
||||
uint32_t qty,
|
||||
double settle,
|
||||
std::vector<LimitCoverGate> gates,
|
||||
Steps... steps) {
|
||||
std::vector<double> price_offsets { 0.0, 0.75, 1.25, -0.75, -1.25 };
|
||||
for (auto po : price_offsets) {
|
||||
// complete
|
||||
test_impl(api::SIDE_BUY, qty, po, settle, gates, steps..., test_steps::Complete());
|
||||
test_impl(api::SIDE_SELL, qty, po, settle, gates, steps..., test_steps::Complete());
|
||||
// cancel child
|
||||
test_impl(api::SIDE_BUY, qty, po, settle, gates, steps..., test_steps::CancelChild());
|
||||
test_impl(api::SIDE_SELL, qty, po, settle, gates, steps..., test_steps::CancelChild());
|
||||
// cancel algo
|
||||
test_impl(api::SIDE_BUY, qty, po, settle, gates, steps..., test_steps::CancelAlgo());
|
||||
test_impl(api::SIDE_SELL, qty, po, settle, gates, steps..., test_steps::CancelAlgo());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("test_activation_thresholds.", "[algo][limit_cover]") {
|
||||
SECTION("Activate 1st gate.") {
|
||||
test_permutations(25, 100, {
|
||||
LimitCoverGate(4, 4, 4),
|
||||
LimitCoverGate(10, 7, 5),
|
||||
LimitCoverGate(15, 12, 11),
|
||||
},
|
||||
// test steps
|
||||
test_steps::BeginLaunch(),
|
||||
test_steps::Launch(),
|
||||
test_steps::BreechActivationThresh());
|
||||
}
|
||||
SECTION("Activate 1st then 2nd gate.") {
|
||||
test_permutations(25, 100, {
|
||||
LimitCoverGate(4, 4, 4),
|
||||
LimitCoverGate(10, 7, 5),
|
||||
LimitCoverGate(15, 12, 11),
|
||||
},
|
||||
// test steps
|
||||
test_steps::BeginLaunch(),
|
||||
test_steps::Launch(),
|
||||
test_steps::BreechActivationThresh(),
|
||||
test_steps::Fill([](const auto& state) { return test_params::fills::child(state, 8); }),
|
||||
test_steps::BreechActivationThresh(),
|
||||
test_steps::Fill([](const auto& state) { return test_params::fills::child(state, 2); }),
|
||||
test_steps::BreechActivationThresh());
|
||||
}
|
||||
SECTION("Activate 2nd gate.") {
|
||||
test_permutations(25, 100, {
|
||||
LimitCoverGate(4, 4, 4),
|
||||
LimitCoverGate(10, 7, 5),
|
||||
LimitCoverGate(15, 12, 11),
|
||||
},
|
||||
// test steps
|
||||
test_steps::BeginLaunch(),
|
||||
test_steps::Launch(),
|
||||
test_steps::BreechActivationThresh(1, 1),
|
||||
test_steps::Fill([](const auto& state) { return test_params::fills::child(state, 8); }));
|
||||
}
|
||||
}
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,743 @@
|
||||
#include "catch/catch.hpp"
|
||||
#include "../DormanAlgo.h"
|
||||
#include "AlgoTestDriver.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <random>
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::orderman::dorman;
|
||||
using namespace bts::api::orderman;
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
|
||||
/* STATE
|
||||
|
||||
struct OrderState {
|
||||
Price desired_price;
|
||||
Quantity exposed_qty;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
/* TEST ASPECTS
|
||||
|
||||
order_qty vs market quantity
|
||||
order_qty reduce when market changes
|
||||
order_qty increase (w/ delay) when market changes
|
||||
order_qty maximum
|
||||
|
||||
overfill [under/over] max with auto-scratch [on/off]
|
||||
Profit orders
|
||||
- test that orders are sent
|
||||
- test that away order are at the right price
|
||||
|
||||
Trade Accumulation
|
||||
- accumulator goes [on/off]
|
||||
- Dorman goes to sleep when it goes 'on' and wakes up when it goes 'off'
|
||||
|
||||
Market Moves
|
||||
- Available size increases
|
||||
- Available size decreases
|
||||
|
||||
clip size pseudo-order
|
||||
- change clip size
|
||||
- immidiate effect?
|
||||
- don't have to cancel if clip size goes down?
|
||||
- change order size [up/down]
|
||||
- suspend/resume
|
||||
- cancels all
|
||||
|
||||
|
||||
*/
|
||||
|
||||
api::orderman::DynamicOrderDefinition to_dynamic_order_params(const AlgoParams& input) {
|
||||
api::orderman::DynamicOrderDefinition result;
|
||||
result.set_dynamic_order_type("Dorman");
|
||||
auto& map = *result.mutable_order_params();
|
||||
|
||||
for (size_t x = 0; x < input.steps.step_table.size(); ++x) {
|
||||
std::string xs = std::to_string(x+1);
|
||||
map["Market Quantity " + xs] = dynamic_parameter( std::get<0>(input.steps.step_table[x]));
|
||||
map["Order Quantity " + xs] = dynamic_parameter(std::get<1>(input.steps.step_table[x]));
|
||||
}
|
||||
|
||||
map["Max Size"] = dynamic_parameter(input.steps.maximum);
|
||||
map["Trail Seconds"] = dynamic_parameter(input.steps.trail_seconds);
|
||||
map["Clip Size"] = dynamic_parameter(input.clip_limit);
|
||||
|
||||
// scratch
|
||||
map["Enable Scratch"] = dynamic_parameter((bool)(input.scratch));
|
||||
if (input.scratch) {
|
||||
map["Max Overfill"] = dynamic_parameter(input.scratch.value().max_overfill);
|
||||
}
|
||||
//
|
||||
// profit orders
|
||||
map["Profit Orders"] = dynamic_parameter((bool)input.profit_orders);
|
||||
if (input.profit_orders) {
|
||||
map["Ticks Away"] = dynamic_parameter(input.profit_orders.value().ticks_away);
|
||||
}
|
||||
|
||||
//
|
||||
// trade accumulator
|
||||
map["Trade Accumulation"] = dynamic_parameter((bool)input.trade_accumulation);
|
||||
if (input.trade_accumulation) {
|
||||
auto& a = input.trade_accumulation.value();
|
||||
map[("Trade Contracts")] = dynamic_parameter(a.contracts);
|
||||
map["Trade Interval (seconds)"] = dynamic_parameter(a.seconds);
|
||||
map["Trade Sleep (seconds)"] = dynamic_parameter(a.sleep_seconds);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void check_round_trip(const AlgoParams& input) {
|
||||
auto d1 = to_dynamic_order_params(input);
|
||||
auto ap1 = to_dorman_params(d1);
|
||||
if (std::holds_alternative<OrderOpResult>(ap1)) {
|
||||
std::cout << std::get<OrderOpResult>(ap1).error_msg;
|
||||
}
|
||||
REQUIRE(std::holds_alternative<AlgoParams>(ap1));
|
||||
auto d2 = to_dynamic_order_params(std::get<AlgoParams>(ap1));
|
||||
|
||||
REQUIRE(d2.DebugString() == d1.DebugString());
|
||||
}
|
||||
|
||||
TEST_CASE("dorman_round_trip_params", "[algo][dorman]") {
|
||||
|
||||
SECTION("a") {
|
||||
AlgoParams input;
|
||||
input.steps = {
|
||||
{ {500, 250},
|
||||
{1000, 400} },
|
||||
1024, 5
|
||||
};
|
||||
check_round_trip(input);
|
||||
}
|
||||
|
||||
SECTION("b") {
|
||||
AlgoParams input;
|
||||
input.steps = {
|
||||
{ {500, 250},
|
||||
{1000, 400}
|
||||
},
|
||||
1024, 5
|
||||
};
|
||||
input.scratch = { 500 };
|
||||
input.profit_orders = { 42 };
|
||||
input.clip_limit = 220;
|
||||
check_round_trip(input);
|
||||
}
|
||||
|
||||
SECTION("c") {
|
||||
AlgoParams input;
|
||||
input.steps = {
|
||||
{ {500, 250},
|
||||
{1000, 400}
|
||||
},
|
||||
1024, 5
|
||||
};
|
||||
input.profit_orders = { 42 };
|
||||
input.trade_accumulation = {
|
||||
100, 200, 300
|
||||
};
|
||||
check_round_trip(input);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct ExpectedSize {
|
||||
uint32_t input;
|
||||
uint32_t expected;
|
||||
};
|
||||
constexpr auto some_price = md::IntPrice { 128000, -3 };
|
||||
|
||||
|
||||
template <class CB>
|
||||
size_t count_x(const std::vector<ChildOrder>& child_orders, CB fn) {
|
||||
return std::accumulate(
|
||||
child_orders.begin(),
|
||||
child_orders.end(), 0,
|
||||
[&](uint32_t acc, const auto& child) {
|
||||
return acc + fn(child);
|
||||
});
|
||||
}
|
||||
|
||||
size_t count_exposed(const std::vector<ChildOrder>& child_orders) {
|
||||
return count_x(child_orders, [](const ChildOrder& child) {
|
||||
REQUIRE(child.def);
|
||||
if (child.cstate.status == ChildStatus::CANCELED) return 0u;
|
||||
else return child.def.value().custom_def().splitter().exposed_qty() + child.cstate.total_fills.value();
|
||||
});
|
||||
}
|
||||
|
||||
size_t count_fills(const std::vector<ChildOrder>& child_orders) {
|
||||
return count_x(child_orders, [](const ChildOrder& child) {
|
||||
return child.def.value().custom_def().splitter().exposed_qty() + child.cstate.total_fills.value();
|
||||
});
|
||||
}
|
||||
size_t count_status(const std::vector<ChildOrder>& child_orders, ChildStatus status) {
|
||||
size_t match = 0;
|
||||
for (const auto& child : child_orders) {
|
||||
if (child.cstate.status == status) match += 1;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
TEST_CASE("dorman_order_size", "[algo][dorman]") {
|
||||
// create a table of various sizes
|
||||
// send market state to the algo
|
||||
AlgoParams input;
|
||||
input.steps.step_table = {
|
||||
{500, 250},
|
||||
{1000, 400},
|
||||
{2000, 600},
|
||||
{3500, 750},
|
||||
{10000, 999}
|
||||
};
|
||||
input.steps.maximum = 800;
|
||||
input.steps.trail_seconds = 5;
|
||||
input.clip_limit = 100;
|
||||
|
||||
api::orderman::OrderDefinition def;
|
||||
def.set_side(api::SIDE_BUY);
|
||||
def.mutable_custom_def()->mutable_dynamic()->CopyFrom(to_dynamic_order_params(input));
|
||||
|
||||
SECTION("initial size vs market") {
|
||||
std::vector<ExpectedSize> test_sizes {
|
||||
{ 10, 0 },
|
||||
{ 200, 0 },
|
||||
{ 250, 0 },
|
||||
{ 500, 250 },
|
||||
{ 999, 250 },
|
||||
{ 2500, 600 },
|
||||
{ 9999, 750 },
|
||||
{ 20002, 800 },
|
||||
};
|
||||
|
||||
for (auto& s : test_sizes)
|
||||
{
|
||||
AlgoTestDriver<DormanAlgo> state(def, some_price, Quantity {10});
|
||||
auto secid = SecurityKey(state.security().id());
|
||||
Book book(secid);
|
||||
|
||||
// order size is driven by the available market size
|
||||
book.bid()[0] = { some_price, s.input };
|
||||
state.update_book(book);
|
||||
|
||||
REQUIRE(state.before_launch().ok());
|
||||
state.launch();
|
||||
REQUIRE(state.algo().exposed_qty().value() == s.expected);
|
||||
REQUIRE(s.expected == count_exposed(state.child_orders()));
|
||||
}
|
||||
}
|
||||
SECTION("Reduce qty on market shrink (immidiate)") {
|
||||
AlgoTestDriver<DormanAlgo> state(def, some_price, Quantity {10});
|
||||
auto secid = SecurityKey(state.security().id());
|
||||
Book book(secid);
|
||||
book.bid()[0] = { some_price, 505 };
|
||||
state.update_book(book);
|
||||
REQUIRE(state.before_launch().ok());
|
||||
state.launch();
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(250));
|
||||
|
||||
// Check that orders were created
|
||||
REQUIRE(250 == count_exposed(state.child_orders()));
|
||||
for(const auto& ch : state.child_orders()) {
|
||||
REQUIRE(ch.cstate.quantity == state.quantity());
|
||||
}
|
||||
|
||||
// Reduce market
|
||||
book.bid()[0] = { some_price, 405 };
|
||||
state.update_book(book);
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(0));
|
||||
REQUIRE(
|
||||
0 == count_exposed(state.child_orders()));
|
||||
}
|
||||
|
||||
SECTION("Increase qty w/ delay when market grows") {
|
||||
AlgoTestDriver<DormanAlgo> state(def, some_price, Quantity {10});
|
||||
auto secid = SecurityKey(state.security().id());
|
||||
Book book(secid);
|
||||
book.bid()[0] = { some_price, 505 };
|
||||
state.update_book(book);
|
||||
REQUIRE(state.before_launch().ok());
|
||||
state.launch();
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(250));
|
||||
|
||||
// Check that orders were created
|
||||
REQUIRE(
|
||||
250 == count_exposed(state.child_orders()));
|
||||
|
||||
// INCREASE market
|
||||
book.bid()[0] = { some_price, 1001 };
|
||||
state.update_book(book);
|
||||
// NOTE: that the orders do not follow the market instantly
|
||||
// so this is still 250
|
||||
REQUIRE(
|
||||
250 == count_exposed(state.child_orders()));
|
||||
|
||||
// Still 250 after the timer ticks but not enough time has passed
|
||||
state.advance_time(std::chrono::seconds());
|
||||
REQUIRE(
|
||||
250 == count_exposed(state.child_orders()));
|
||||
|
||||
state.advance_time(std::chrono::seconds(input.steps.trail_seconds));
|
||||
REQUIRE(
|
||||
400 == count_exposed(state.child_orders()));
|
||||
|
||||
}
|
||||
SECTION("Respect maximum") {
|
||||
AlgoTestDriver<DormanAlgo> state(def, some_price, Quantity {10});
|
||||
auto secid = SecurityKey(state.security().id());
|
||||
Book book(secid);
|
||||
book.bid()[0] = { some_price, 10505 };
|
||||
state.update_book(book);
|
||||
REQUIRE(state.before_launch().ok());
|
||||
state.launch();
|
||||
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(input.steps.maximum));
|
||||
|
||||
// Check that orders were created
|
||||
REQUIRE(
|
||||
input.steps.maximum == count_exposed(state.child_orders()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Dorman fills", "[algo][dorman]") {
|
||||
AlgoParams input;
|
||||
input.steps.step_table = {
|
||||
{500, 250},
|
||||
{1000, 450},
|
||||
{2000, 650},
|
||||
{3500, 750},
|
||||
};
|
||||
input.steps.maximum = 750;
|
||||
input.steps.trail_seconds = 2;
|
||||
input.clip_limit = 100;
|
||||
api::orderman::OrderDefinition def;
|
||||
def.set_side(api::SIDE_BUY);
|
||||
def.mutable_custom_def()->mutable_dynamic()->CopyFrom(to_dynamic_order_params(input));
|
||||
auto price = Price { some_price.price() };
|
||||
|
||||
SECTION("complete when filled") {
|
||||
// Send out some orders
|
||||
// Fill them
|
||||
// Expect order state to become finished
|
||||
AlgoTestDriver<DormanAlgo> state(def, some_price, Quantity {10});
|
||||
auto secid = SecurityKey(state.security().id());
|
||||
Book book(secid);
|
||||
book.bid()[0] = { some_price, 501 };
|
||||
state.update_book(book);
|
||||
REQUIRE(state.before_launch().ok());
|
||||
state.launch();
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(250));
|
||||
|
||||
for (const auto& order : state.child_orders()) {
|
||||
state.fill(order.key, price, order.cstate.quantity);
|
||||
}
|
||||
|
||||
REQUIRE(state.state() == AlgoState::COMPLETED);
|
||||
}
|
||||
|
||||
// Test cases for transitions w/ fills
|
||||
//
|
||||
|
||||
SECTION("Complete by update") {
|
||||
// target = 500, exposed = 650
|
||||
// Fill 400
|
||||
// Change target to 350
|
||||
// expect completion
|
||||
AlgoTestDriver<DormanAlgo> state(def, some_price, Quantity {500});
|
||||
auto secid = SecurityKey(state.security().id());
|
||||
Book book(secid);
|
||||
book.bid()[0] = { some_price, 1999 };
|
||||
state.update_book(book);
|
||||
REQUIRE(state.before_launch().ok());
|
||||
state.launch();
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(450));
|
||||
REQUIRE(state.quantity() == Quantity {500});
|
||||
|
||||
// Fill 400
|
||||
state.fill(price, Quantity { 400 });
|
||||
Update update;
|
||||
update.set_quantity(350);
|
||||
state.do_update(update);
|
||||
REQUIRE(state.state() == AlgoState::COMPLETED);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Dorman overfill", "[algo][dorman]") {
|
||||
AlgoParams input;
|
||||
input.steps.step_table = {
|
||||
{500, 250},
|
||||
{1000, 450},
|
||||
{2000, 650},
|
||||
{3500, 750},
|
||||
};
|
||||
input.steps.maximum = 750;
|
||||
input.steps.trail_seconds = 2;
|
||||
input.clip_limit = 100;
|
||||
|
||||
SECTION("auto-scratch ON") {
|
||||
input.scratch = {{ 500 }};
|
||||
}
|
||||
SECTION("auto-scratch OFF") {
|
||||
// both sections will run
|
||||
input.scratch = std::nullopt;
|
||||
}
|
||||
|
||||
api::orderman::OrderDefinition def;
|
||||
def.set_side(api::SIDE_BUY);
|
||||
def.mutable_custom_def()->mutable_dynamic()->CopyFrom(to_dynamic_order_params(input));
|
||||
auto price = Price { some_price.price() };
|
||||
|
||||
AlgoTestDriver<DormanAlgo> state(def, some_price, Quantity {100});
|
||||
state.allow_overfill();
|
||||
auto secid = SecurityKey(state.security().id());
|
||||
Book book(secid);
|
||||
book.bid()[0] = { some_price, 1999 };
|
||||
state.update_book(book);
|
||||
REQUIRE(state.before_launch().ok());
|
||||
state.launch();
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(450));
|
||||
REQUIRE(state.quantity() == Quantity {100});
|
||||
REQUIRE(state.child_orders().size() == 1);
|
||||
|
||||
state.fill(state.child_orders().begin()->key, price, Quantity {500});
|
||||
REQUIRE(state.state() == AlgoState::COMPLETED);
|
||||
|
||||
if (input.scratch) {
|
||||
// check that we scratched
|
||||
REQUIRE(state.detached_orders().size() > 0);
|
||||
}
|
||||
else {
|
||||
// check that we *did not* scratch
|
||||
REQUIRE(state.detached_orders().size() == 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Dorman profit-orders", "[algo][dorman]") {
|
||||
AlgoParams input;
|
||||
input.steps.step_table = {
|
||||
{500, 250},
|
||||
{1000, 450},
|
||||
{2000, 650},
|
||||
{3500, 750},
|
||||
};
|
||||
input.steps.maximum = 750;
|
||||
input.steps.trail_seconds = 2;
|
||||
input.clip_limit = 100;
|
||||
|
||||
bool test_cancel = true;
|
||||
|
||||
SECTION("Profit Orders ON - Cancel") {
|
||||
// Send profit orders 3 ticks away
|
||||
input.profit_orders = {{ 3 }};
|
||||
}
|
||||
SECTION("Profit Orders ON - Complete") {
|
||||
// Send profit orders 3 ticks away
|
||||
input.profit_orders = {{ 3 }};
|
||||
test_cancel = false;
|
||||
}
|
||||
SECTION("Profit Orders OFF") {
|
||||
input.profit_orders = std::nullopt;
|
||||
}
|
||||
SECTION("Profit Orders OFF - complete") {
|
||||
input.profit_orders = std::nullopt;
|
||||
test_cancel = false;
|
||||
}
|
||||
|
||||
api::orderman::OrderDefinition def;
|
||||
def.set_side(api::SIDE_BUY);
|
||||
def.mutable_custom_def()->mutable_dynamic()->CopyFrom(to_dynamic_order_params(input));
|
||||
auto price = Price { some_price.price() };
|
||||
|
||||
AlgoTestDriver<DormanAlgo> state(def, some_price, Quantity {100});
|
||||
auto secid = SecurityKey(state.security().id());
|
||||
auto tick = state.security().contract_spec().low_tick();
|
||||
Book book(secid);
|
||||
book.bid()[0] = { some_price, 1999 };
|
||||
state.update_book(book);
|
||||
REQUIRE(state.before_launch().ok());
|
||||
state.launch();
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(450));
|
||||
REQUIRE(state.quantity() == Quantity {100});
|
||||
REQUIRE(state.child_orders().size() == 1);
|
||||
|
||||
state.fill(state.child_orders().begin()->key, price, Quantity {20});
|
||||
|
||||
if (input.profit_orders) {
|
||||
// We're testing the bid side so we expect 3 ticks up
|
||||
auto expected_price = Price {
|
||||
price.value() + (tick * input.profit_orders.value().ticks_away) };
|
||||
REQUIRE(state.child_orders().size() > 1);
|
||||
REQUIRE(state.detached_orders().empty());
|
||||
size_t profit_order_count = 0;
|
||||
for (const auto& order : state.child_orders()) {
|
||||
if (order.def.value().side() == api::SIDE_SELL) {
|
||||
profit_order_count += 1;
|
||||
REQUIRE(order.cstate.quantity.value() == 20);
|
||||
REQUIRE(securities::is_same(order.cstate.price, expected_price));
|
||||
}
|
||||
}
|
||||
REQUIRE(profit_order_count == 1);
|
||||
}
|
||||
else {
|
||||
REQUIRE(state.child_orders().size() == 1);
|
||||
REQUIRE(state.detached_orders().empty());
|
||||
}
|
||||
|
||||
state.fill(state.child_orders().begin()->key, price, Quantity {42});
|
||||
|
||||
if (input.profit_orders) {
|
||||
auto expected_price = Price {
|
||||
price.value() + (tick * input.profit_orders.value().ticks_away) };
|
||||
REQUIRE(state.child_orders().size() > 1);
|
||||
REQUIRE(state.detached_orders().empty());
|
||||
bool got_20 = false;
|
||||
bool got_42 = false;
|
||||
for (const auto& order : state.child_orders()) {
|
||||
if (order.def.value().side() == api::SIDE_SELL) {
|
||||
if (order.cstate.quantity.value() == 20) got_20 = true;
|
||||
if (order.cstate.quantity.value() == 42) got_42 = true;
|
||||
REQUIRE(securities::is_same(order.cstate.price, expected_price));
|
||||
}
|
||||
}
|
||||
REQUIRE(got_42);
|
||||
REQUIRE(got_20);
|
||||
}
|
||||
else {
|
||||
REQUIRE(state.child_orders().size() == 1);
|
||||
REQUIRE(state.detached_orders().empty());
|
||||
}
|
||||
|
||||
if (test_cancel) {
|
||||
// Profit orders should detach on cancel
|
||||
state.cancel();
|
||||
REQUIRE(state.state() == AlgoState::CANCELED);
|
||||
if (input.profit_orders) {
|
||||
REQUIRE(state.detached_orders().size() > 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
state.fill(state.child_orders().begin()->key, price, Quantity {38});
|
||||
REQUIRE(state.state() == AlgoState::COMPLETED);
|
||||
if (input.profit_orders) {
|
||||
REQUIRE(state.detached_orders().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE("Dorman detaches profit orders when completed by scratch", "[algo][dorman][!hide]") {
|
||||
// Idea for future test
|
||||
// }
|
||||
|
||||
TickerUpdateEvent::Trade mkTrade(Price p, Quantity q) {
|
||||
return {
|
||||
Timer::now(),
|
||||
p,
|
||||
q,
|
||||
Quantity { q.value() + 9999 },
|
||||
co2::AGGRESSOR_SIDE_NONE
|
||||
};
|
||||
}
|
||||
|
||||
TradeVector mkTrades(Price p, Quantity total) {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
|
||||
|
||||
uint32_t remaining = total.value();
|
||||
TradeVector result;
|
||||
while(remaining > 0) {
|
||||
std::uniform_real_distribution<> dis(1, remaining);
|
||||
auto qty = (uint32_t)dis(gen);
|
||||
remaining -= qty;
|
||||
result.push_back(mkTrade(p, Quantity {qty}));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST_CASE("Dorman trade accumulation", "[algo][dorman]") {
|
||||
// Start algo with trade accumulation enabled
|
||||
// If 100 trades in 5 seconds pause for 6 seconds
|
||||
// Exceed Limits
|
||||
// Check that child was canceled and re-created
|
||||
// Disable accumulator
|
||||
// attempt to exceed limits
|
||||
// Check that algo does not turn off
|
||||
// re-enable accumulator
|
||||
// Check that algo turns off
|
||||
// Change parameters
|
||||
// Check that the algo turns off based on the *new* params
|
||||
|
||||
AlgoParams input;
|
||||
input.steps.step_table = {
|
||||
{225, 32},
|
||||
{362, 64},
|
||||
{589, 128},
|
||||
{697, 5000},
|
||||
};
|
||||
input.steps.maximum = 9999;
|
||||
input.clip_limit = 8;
|
||||
// If 100 trades in 5 seconds pause for 6 seconds
|
||||
input.trade_accumulation = {TradeAccumulationParams { 100, 5, 6 } };
|
||||
|
||||
api::orderman::OrderDefinition def;
|
||||
def.set_side(api::SIDE_BUY);
|
||||
def.mutable_custom_def()->mutable_dynamic()->CopyFrom(to_dynamic_order_params(input));
|
||||
auto price = Price { some_price.price() };
|
||||
|
||||
AlgoTestDriver<DormanAlgo> state(def, some_price, Quantity {2500});
|
||||
auto secid = SecurityKey(state.security().id());
|
||||
Book book(secid);
|
||||
book.bid()[0] = { some_price, 1999 };
|
||||
state.update_book(book);
|
||||
REQUIRE(state.before_launch().ok());
|
||||
state.launch();
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(5000));
|
||||
REQUIRE(state.child_orders().size() == 1);
|
||||
|
||||
state.advance_time(std::chrono::seconds(1));
|
||||
|
||||
// Need 100 trades in 5 seconds,
|
||||
// The algorithm we're using has a margin of error so
|
||||
// lets do 25 trades per second for 5 seconds which should be comfortably over
|
||||
// send_trades(state, Quantity { 120 }, std::chrono::seconds(5));
|
||||
for (auto t = 0; t < 5; ++t) {
|
||||
state.update_ticker(secid, mkTrades(price, Quantity { 25 }));
|
||||
state.advance_time(std::chrono::milliseconds(1001), 3);
|
||||
}
|
||||
REQUIRE(count_exposed(state.child_orders()) == 0);
|
||||
|
||||
state.advance_time(std::chrono::seconds(4));
|
||||
// Check that it hasn't jumped the gun
|
||||
REQUIRE(count_exposed(state.child_orders()) == 0);
|
||||
state.advance_time(std::chrono::seconds(3));
|
||||
|
||||
// Sleep time has expired so we should re-join the market
|
||||
REQUIRE(count_exposed(state.child_orders()) == 5000);
|
||||
|
||||
|
||||
// Disable the accumulator
|
||||
Update update;
|
||||
input.trade_accumulation = std::nullopt;
|
||||
update.mutable_custom()->mutable_dynamic()->CopyFrom(to_dynamic_order_params(input));
|
||||
update.set_quantity(5000);
|
||||
state.do_update(update);
|
||||
|
||||
// Attempt to exceed again
|
||||
for (auto t = 0; t < 5; ++t) {
|
||||
state.update_ticker(secid, mkTrades(price, Quantity { 25 }));
|
||||
state.advance_time(std::chrono::milliseconds(1001), 9);
|
||||
}
|
||||
// Still working
|
||||
REQUIRE(count_exposed(state.child_orders()) == 5000);
|
||||
|
||||
// re-enable accumulator
|
||||
input.trade_accumulation = {TradeAccumulationParams { 100, 5, 6 } };
|
||||
update.mutable_custom()->mutable_dynamic()->CopyFrom(to_dynamic_order_params(input));
|
||||
state.do_update(update);
|
||||
|
||||
for (auto t = 0; t < 5; ++t) {
|
||||
state.update_ticker(secid, mkTrades(price, Quantity { 25 }));
|
||||
state.advance_time(std::chrono::milliseconds(1001), 4);
|
||||
}
|
||||
// turned off?
|
||||
REQUIRE(count_exposed(state.child_orders()) == 0);
|
||||
state.advance_time(std::chrono::seconds(10));
|
||||
// back on!
|
||||
REQUIRE(count_exposed(state.child_orders()) == 5000);
|
||||
|
||||
// Change the accumulator params without disabling it
|
||||
input.trade_accumulation = {TradeAccumulationParams { 1000, 5, 6 } };
|
||||
update.mutable_custom()->mutable_dynamic()->CopyFrom(to_dynamic_order_params(input));
|
||||
state.do_update(update);
|
||||
|
||||
// Attempt to trigger based on the old settings.
|
||||
for (auto t = 0; t < 5; ++t) {
|
||||
state.update_ticker(secid, mkTrades(price, Quantity { 35 }));
|
||||
state.advance_time(std::chrono::milliseconds(1001), 7);
|
||||
}
|
||||
// Still working?
|
||||
REQUIRE(count_exposed(state.child_orders()) == 5000);
|
||||
|
||||
// Attempt to trigger based on new settings
|
||||
for (auto t = 0; t < 5; ++t) {
|
||||
state.update_ticker(secid, mkTrades(price, Quantity { 500 }));
|
||||
state.advance_time(std::chrono::milliseconds(1001), 5);
|
||||
}
|
||||
REQUIRE(count_exposed(state.child_orders()) == 0);
|
||||
|
||||
// complete while paused (fills can arrive after requested cancel)
|
||||
ChildOrder latest_order = {};
|
||||
for (auto& order : state.child_orders()) {
|
||||
if (order.key > latest_order.key) latest_order = order;
|
||||
}
|
||||
state.fill(latest_order.key, price, latest_order.cstate.quantity);
|
||||
REQUIRE(state.state() == AlgoState::COMPLETED);
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Dorman updates child", "[algo][dorman]") {
|
||||
// When updating the Dorman price the price of the child should also update
|
||||
}
|
||||
|
||||
TEST_CASE("Dorman market move up then back down", "[algo][dorman]") {
|
||||
// Start dorman algo
|
||||
// Move market up so we plan to increase the exposed qty after trail_seconds
|
||||
// Move market back down before trail_seconds elapses
|
||||
// check that algo does *not* increase exposure
|
||||
|
||||
AlgoParams input;
|
||||
input.steps.step_table = {
|
||||
{500, 250},
|
||||
{1000, 450},
|
||||
{2000, 650},
|
||||
{3500, 750},
|
||||
};
|
||||
input.steps.maximum = 750;
|
||||
input.steps.trail_seconds = 30;
|
||||
input.clip_limit = 100;
|
||||
api::orderman::OrderDefinition def;
|
||||
def.set_side(api::SIDE_BUY);
|
||||
def.mutable_custom_def()->mutable_dynamic()->CopyFrom(to_dynamic_order_params(input));
|
||||
// auto price = Price { some_price.price() };
|
||||
|
||||
AlgoTestDriver<DormanAlgo> state(def, some_price, Quantity {1337});
|
||||
auto secid = SecurityKey(state.security().id());
|
||||
|
||||
Book book(secid);
|
||||
book.bid()[0] = { some_price, 1350 };
|
||||
state.update_book(book);
|
||||
REQUIRE(state.before_launch().ok());
|
||||
state.launch();
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(450));
|
||||
REQUIRE(state.child_orders().size() == 1);
|
||||
state.advance_time(std::chrono::seconds(1));
|
||||
|
||||
// Update book sets future quantity = 650
|
||||
book.bid()[0] = { some_price, 2219 };
|
||||
state.update_book(book);
|
||||
state.advance_time(std::chrono::seconds(10));
|
||||
// Algo does not increase exposure after 10 second
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(450));
|
||||
|
||||
book.bid()[0] = { some_price, 1425 };
|
||||
state.update_book(book);
|
||||
state.advance_time(std::chrono::seconds(40));
|
||||
// Algo does not increase exposure after trail_seconds
|
||||
REQUIRE(state.algo().exposed_qty() == Quantity(450));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}}}//namespace
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverState.h"
|
||||
#include "catch/catch.hpp"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/md/IntPrice.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orderman_utils/BookUtils.h"
|
||||
#include "lib/orderman/orderman_utils/Formatter.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverTestUtils.h"
|
||||
|
||||
#define VALIDATE Validation __VALIDATION(this)
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
using namespace std;
|
||||
using namespace bts::api::orderman;
|
||||
using namespace bts::orderman::limit_cover::utils;
|
||||
using Size = bts::md::Size;
|
||||
using Side = bts::api::Side;
|
||||
|
||||
LimitCoverState::LimitCoverState(
|
||||
const OrderDefinition& def,
|
||||
uint32_t quantity,
|
||||
double price)
|
||||
: driver(def, int_price(price), Quantity(quantity)), _definition(def), _gates(), _book(SecurityKey(security().id())) {
|
||||
auto ngs = limit_cover().normal_gates();
|
||||
_gates.insert(ngs.begin(), ngs.end());
|
||||
// attach callbacks
|
||||
driver.on_child_launching([&](OrderKey key, Quantity child_qty, Price child_price) {
|
||||
// should never deploy twice
|
||||
REQUIRE(_child == null_order_key());
|
||||
REQUIRE(key != null_order_key());
|
||||
_child = key;
|
||||
// qty should match the algo's, but price must be the limit price
|
||||
// (the algo's price is irrelevant)
|
||||
REQUIRE(child_qty.value() == driver.quantity().value());
|
||||
REQUIRE(child_price.value() == limit_price());
|
||||
// todo: verify delete and cover def matches the current gate
|
||||
//REQUIRE(child_def.custom_def().delete_and_cover())
|
||||
REQUIRE(limit_volume() >= activation_threshold());
|
||||
});
|
||||
driver.on_child_changed([&](const ChildOrder&, const ChildOrder& new_state) {
|
||||
REQUIRE(new_state.key == _child);
|
||||
REQUIRE(new_state.def.has_value());
|
||||
REQUIRE(new_state.def.value().has_custom_def());
|
||||
REQUIRE(new_state.def.value().custom_def().has_delete_and_cover());
|
||||
});
|
||||
driver.after_child_changed([&](const ChildOrder&, const ChildOrder& new_state) {
|
||||
REQUIRE(driver.total_fills().value() == new_state.cstate.total_fills.value());
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LimitCoverState::quantity() const {
|
||||
return driver.quantity().value();
|
||||
}
|
||||
double LimitCoverState::price() const {
|
||||
return driver.price().value();
|
||||
}
|
||||
const api::Security& LimitCoverState::security() const {
|
||||
return driver.security();
|
||||
}
|
||||
const bts::api::orderman::OrderDefinition& LimitCoverState::def() const {
|
||||
return _definition;
|
||||
}
|
||||
LimitCoverDefinition LimitCoverState::limit_cover() const {
|
||||
return LimitCoverAdapter::from_proto(def());
|
||||
}
|
||||
uint32_t LimitCoverState::limit_volume() const {
|
||||
return _limit_volume;
|
||||
}
|
||||
double LimitCoverState::limit_price() const {
|
||||
return _limit_price;
|
||||
}
|
||||
OrderKey LimitCoverState::child() const {
|
||||
return _child;
|
||||
}
|
||||
optional<ChildOrder> LimitCoverState::child_state() const {
|
||||
if (has_been_deployed()) {
|
||||
return ChildOrder(*driver.child_orders().begin());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
void LimitCoverState::update_child(const Update& u) {
|
||||
driver.update_child(child(), u);
|
||||
}
|
||||
bool LimitCoverState::has_been_deployed() const {
|
||||
return _child != null_order_key();
|
||||
}
|
||||
bool LimitCoverState::is_terminated() const {
|
||||
switch (driver.state()) {
|
||||
case AlgoState::CANCELED:
|
||||
case AlgoState::TERMINATED:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const std::map<uint32_t, LimitCoverGate>& LimitCoverState::gates() const {
|
||||
return _gates;
|
||||
}
|
||||
const LimitCoverGate& LimitCoverState::curr_gate() const {
|
||||
REQUIRE(!gates().empty());
|
||||
return gates().begin()->second;
|
||||
}
|
||||
uint32_t LimitCoverState::activation_threshold() const {
|
||||
return gates().empty() ? std::numeric_limits<uint32_t>::max() : gates().begin()->first;
|
||||
}
|
||||
// settlement
|
||||
double LimitCoverState::settlement() const { return _settlement; }
|
||||
void LimitCoverState::settlement(double value) {
|
||||
VALIDATE;
|
||||
// the algo is not supposed to disregard settlement changes
|
||||
// after the child has been deployed
|
||||
if (!has_been_deployed()) {
|
||||
_settlement = value;
|
||||
recompute_all();
|
||||
}
|
||||
driver.update_settlement(SecurityKey(security().id()), Price(value));
|
||||
}
|
||||
|
||||
// book
|
||||
const Book& LimitCoverState::book() const { return _book; }
|
||||
void LimitCoverState::book(Book value) {
|
||||
VALIDATE;
|
||||
_book = value;
|
||||
recompute_all();
|
||||
driver.update_book(value);
|
||||
}
|
||||
|
||||
OrderOpResult LimitCoverState::before_launch() {
|
||||
_gates.clear();
|
||||
auto ngs = limit_cover().normal_gates();
|
||||
_gates.insert(ngs.begin(), ngs.end());
|
||||
return driver.before_launch();
|
||||
}
|
||||
void LimitCoverState::launch() {
|
||||
VALIDATE;
|
||||
_is_initialized = true;
|
||||
recompute_all();
|
||||
driver.launch();
|
||||
}
|
||||
void LimitCoverState::do_update(const Update& upd) {
|
||||
REQUIRE(!is_terminated());
|
||||
// updates should not affect the child's price (after it has been launched).
|
||||
// this is bc the literal 'price' parameter is irrelevant to this algo,
|
||||
// for all intents and purposes, its price is the tick offset from settlement.
|
||||
auto upd_valid = validate_update(upd);
|
||||
// keep definition in sync with update
|
||||
if (upd.has_custom() && upd_valid.ok()) {
|
||||
*(_definition.mutable_custom_def()) = upd.custom();
|
||||
}
|
||||
driver.do_update(upd);
|
||||
if (upd_valid.ok()) {
|
||||
recompute_all();
|
||||
} else {
|
||||
REQUIRE(driver.state() == AlgoState::TERMINATED);
|
||||
}
|
||||
}
|
||||
|
||||
void LimitCoverState::recompute_limit_price() {
|
||||
_limit_price = os::is_nan(settlement()) ? os::nan() :
|
||||
securities::improve_price(side(), Price(settlement()), Price(limit_cover().price_offset()))
|
||||
.value();
|
||||
}
|
||||
|
||||
void LimitCoverState::recompute_limit_volume() {
|
||||
_limit_volume = os::is_nan(settlement()) ? os::nan() :
|
||||
BookUtils::volume_at_or_better(_book, side(), limit_price());
|
||||
}
|
||||
|
||||
void LimitCoverState::recompute_all() {
|
||||
recompute_limit_price();
|
||||
recompute_limit_volume();
|
||||
if (_is_initialized)
|
||||
recompute_gate();
|
||||
}
|
||||
|
||||
void LimitCoverState::recompute_gate() {
|
||||
if (gates().empty())
|
||||
return;
|
||||
auto it = _gates.lower_bound(limit_volume());
|
||||
if (it->first > limit_volume() && it != _gates.begin())
|
||||
it--;
|
||||
_gates.erase(_gates.begin(), it);
|
||||
}
|
||||
|
||||
LimitCoverState::ValidateDeployment::ValidateDeployment(LimitCoverState* state) :
|
||||
_state(state), _had_been_deployed(state->has_been_deployed()) { }
|
||||
|
||||
LimitCoverState::ValidateDeployment::~ValidateDeployment() {
|
||||
if (!_had_been_deployed && _state->limit_volume() >= _state->activation_threshold())
|
||||
REQUIRE(_state->has_been_deployed());
|
||||
}
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/LimitCoverAlgo.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverDefinition.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/AlgoTestDriver.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
struct LimitCoverState {
|
||||
AlgoTestDriver<LimitCoverAlgo> driver;
|
||||
|
||||
LimitCoverState(
|
||||
const api::orderman::OrderDefinition& def,
|
||||
uint32_t quantity,
|
||||
double price);
|
||||
|
||||
const api::orderman::OrderDefinition& def() const;
|
||||
uint32_t quantity() const;
|
||||
double price() const;
|
||||
const api::Security& security() const;
|
||||
LimitCoverDefinition limit_cover() const;
|
||||
api::Side side() const { return def().side(); }
|
||||
uint32_t activation_threshold() const;
|
||||
// main child
|
||||
OrderKey child() const;
|
||||
std::optional<ChildOrder> child_state() const;
|
||||
// gates/curr gate
|
||||
const std::map<uint32_t, LimitCoverGate>& gates() const;
|
||||
const LimitCoverGate& curr_gate() const;
|
||||
// book
|
||||
const Book& book() const;
|
||||
void book(Book new_book);
|
||||
//
|
||||
bool has_been_deployed() const;
|
||||
// settle
|
||||
double settlement() const;
|
||||
void settlement(double value);
|
||||
// limit price
|
||||
double limit_price() const;
|
||||
// limit volume
|
||||
uint32_t limit_volume() const;
|
||||
bool is_terminated() const;
|
||||
OrderOpResult before_launch();
|
||||
void launch();
|
||||
void do_update(const api::orderman::Update&);
|
||||
void update_child(const api::orderman::Update&);
|
||||
|
||||
private:
|
||||
api::orderman::OrderDefinition _definition;
|
||||
uint32_t _limit_volume = 0;
|
||||
OrderKey _child;
|
||||
double _limit_price = os::nan();
|
||||
double _settlement = os::nan();
|
||||
bool _is_initialized = false;
|
||||
std::map<uint32_t, LimitCoverGate> _gates;
|
||||
Book _book;
|
||||
|
||||
void recompute_limit_price();
|
||||
void recompute_limit_volume();
|
||||
void recompute_all();
|
||||
void recompute_gate();
|
||||
|
||||
struct ValidateDeployment {
|
||||
ValidateDeployment(LimitCoverState* state);
|
||||
~ValidateDeployment();
|
||||
|
||||
private:
|
||||
LimitCoverState* _state;
|
||||
bool _had_been_deployed;
|
||||
};
|
||||
|
||||
struct Validation : public ValidateDeployment {
|
||||
Validation(LimitCoverState* state)
|
||||
: ValidateDeployment(state) {}
|
||||
|
||||
~Validation() {}
|
||||
};
|
||||
};
|
||||
|
||||
// custom formatting uses 1 line (unlike pb::to_string).
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const bts::spark::QuantityMeasure& qm) {
|
||||
switch (qm.quantity_case()) {
|
||||
case bts::spark::QuantityMeasure::kAbsolute:
|
||||
ostr << "[absolute] " << qm.absolute();
|
||||
break;
|
||||
case bts::spark::QuantityMeasure::kRatio:
|
||||
ostr << "[ratio] " << qm.ratio();
|
||||
break;
|
||||
default:
|
||||
ostr << "[quantity_case_" << static_cast<int>(qm.quantity_case()) << "]";
|
||||
break;
|
||||
}
|
||||
return ostr;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const LimitCoverState& lcs) {
|
||||
ostr << "qty: " << lcs.quantity() << std::endl;
|
||||
ostr << "limit price: " << lcs.limit_price() << std::endl;
|
||||
ostr << "limit volume: " << lcs.limit_volume() << std::endl;
|
||||
ostr << "activation/curr gate thresh: " << lcs.activation_threshold() << std::endl;
|
||||
ostr << "settlement: " << lcs.settlement() << std::endl;
|
||||
ostr << "bbo: " << Formatter::to_string(lcs.book()) << std::endl;
|
||||
ostr << "has been deployed: " << lcs.has_been_deployed() << std::endl;
|
||||
ostr << "status: " << lcs.driver.state() << std::endl;
|
||||
ostr << "child: " << lcs.child() << std::endl;
|
||||
ostr << "child qty: " << (lcs.child_state().has_value() ? lcs.child_state().value().cstate.quantity.value() : 0) << std::endl;
|
||||
ostr << "filled qty: " << lcs.driver.total_fills().value() << std::endl;
|
||||
return ostr;
|
||||
}
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/md/IntPrice.h"
|
||||
#include "lib/orderman/orderman_utils/Formatter.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include <iostream>
|
||||
|
||||
#define SUMMARIZE(...) Summary __summary__ (Formatter::fmt(__VA_ARGS__))
|
||||
|
||||
using Side = bts::api::Side;
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
using namespace std;
|
||||
inline bts::md::IntPrice int_price(double value) {
|
||||
return bts::md::IntPrice_from_double<8>(value);
|
||||
}
|
||||
inline bts::md::PriceSize price_size(double price, uint32_t size) {
|
||||
return bts::md::PriceSize(int_price(price), size);
|
||||
}
|
||||
inline bts::spark::QuantityMeasure qty_measure(uint32_t abs) {
|
||||
bts::spark::QuantityMeasure qm;
|
||||
qm.set_absolute(abs);
|
||||
return qm;
|
||||
}
|
||||
|
||||
struct Summary {
|
||||
Summary(std::string header)
|
||||
: _header(header) {
|
||||
std::cout << "Begin " << header << std::endl;
|
||||
}
|
||||
~Summary() {
|
||||
std::cout << "End " << _header << std::endl;
|
||||
}
|
||||
private:
|
||||
std::string _header;
|
||||
};
|
||||
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,221 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/TestSteps.h"
|
||||
#include "catch/catch.hpp"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverTestUtils.h"
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
|
||||
std::string ITestStep::name() const { return "step with no name"; }
|
||||
void ITestStep::execute(LimitCoverState &state) const {
|
||||
SUMMARIZE(name());
|
||||
assert_pre_conditions(state);
|
||||
_execute(state);
|
||||
assert_post_conditions(state);
|
||||
}
|
||||
void ITestStep::assert_pre_conditions_core(const LimitCoverState &) const {}
|
||||
void ITestStep::execute_core(LimitCoverState &) const { bts_assert(false); }
|
||||
void ITestStep::assert_post_conditions_core(const LimitCoverState &) const {}
|
||||
void ITestStep::assert_pre_conditions(const LimitCoverState &state) const {
|
||||
// SUMMARIZE("Asserting Pre-conditions (", name(), ")...");
|
||||
assert_pre_conditions_core(state);
|
||||
}
|
||||
void ITestStep::assert_post_conditions(const LimitCoverState &state) const {
|
||||
// SUMMARIZE("Asserting Post-conditions (", name(), ")...");
|
||||
assert_post_conditions_core(state);
|
||||
}
|
||||
void ITestStep::_execute(LimitCoverState &state) const {
|
||||
// SUMMARIZE("Executing (", name(), ")...");
|
||||
execute_core(state);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& ostr, const ITestStep& ts) {
|
||||
ostr << ts.name();
|
||||
return ostr;
|
||||
}
|
||||
|
||||
LimitCoverState& operator<<(LimitCoverState& state, const ITestStep& ts) {
|
||||
SUMMARIZE(ts.name());
|
||||
ts.execute(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// Test Params
|
||||
/////////////////////
|
||||
namespace test_params {
|
||||
namespace fills {
|
||||
Fill child(const LimitCoverState& state, std::optional<uint32_t> qty, std::optional<double> price) {
|
||||
if (!qty.has_value()) {
|
||||
REQUIRE(state.child_state().value().active_qty() > 0);
|
||||
}
|
||||
if (!price.has_value()) {
|
||||
REQUIRE(state.child_state().has_value());
|
||||
}
|
||||
return test_params::Fill(
|
||||
qty.has_value() ? qty.value() : state.child_state().value().active_qty(),
|
||||
price.has_value() ? price.value() : state.child_state().value().cstate.price.value());
|
||||
}
|
||||
} // namespace fills
|
||||
} // namespace test_params
|
||||
|
||||
/////////////////////
|
||||
// Order Selectors
|
||||
/////////////////////
|
||||
namespace order_selectors {
|
||||
std::vector<OrderKey> child(const LimitCoverState& state) {
|
||||
std::vector<OrderKey> rv;
|
||||
if (state.has_been_deployed()) {
|
||||
rv.push_back(state.child());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
std::vector<OrderKey> self(const LimitCoverState&) {
|
||||
std::vector<OrderKey> rv;
|
||||
rv.push_back(null_order_key());
|
||||
return rv;
|
||||
}
|
||||
} // namespace order_selectors
|
||||
|
||||
/////////////////////////
|
||||
// Order Ops (step impls)
|
||||
/////////////////////////
|
||||
namespace ops {
|
||||
void begin_launch(LimitCoverState& state) {
|
||||
REQUIRE(state.driver.before_launch().ok());
|
||||
}
|
||||
|
||||
void launch(LimitCoverState& state) {
|
||||
state.driver.launch();
|
||||
}
|
||||
|
||||
void breech_activation_thresh(LimitCoverState& state, uint32_t thresh_count, int offset) {
|
||||
auto book = state.book();
|
||||
REQUIRE(!os::is_nan(state.limit_price()));
|
||||
auto gate_it = state.gates().upper_bound(state.limit_volume());
|
||||
REQUIRE(gate_it != state.gates().end());
|
||||
while (thresh_count-- > 0 && gate_it != state.gates().end())
|
||||
gate_it++;
|
||||
auto thresh = gate_it->first + offset;
|
||||
int gi = 1;
|
||||
auto& bs = book.side(state.side());
|
||||
if (bs[0].size() == 0) {
|
||||
bs[0] = price_size(state.limit_price(), thresh);
|
||||
} else if (BookUtils::price_comp(state.side())(state.limit_price(), bs[0].price())) {
|
||||
// shift existing levels down 1 index,
|
||||
// put the limit volume at the top
|
||||
for (int i = bs.size() - 1; i > 0; i--) {
|
||||
bs[i] = bs[i - 1];
|
||||
}
|
||||
bs[0] = price_size(state.limit_price(), thresh);
|
||||
} else {
|
||||
// add the difference to the qty for the best price
|
||||
bs[0] = price_size(bs[0].price(), bs[0].size() + thresh - state.limit_volume());
|
||||
}
|
||||
state.book(book);
|
||||
}
|
||||
|
||||
void fill_child(LimitCoverState& state, uint32_t qty, double price, bool allow_overfill) {
|
||||
SUMMARIZE("filling child: ", Formatter::fmt_order(state.side(), qty, price));
|
||||
bts_assert(os::is_finite(price));
|
||||
bts_assert(qty > 0);
|
||||
REQUIRE(state.has_been_deployed());
|
||||
REQUIRE(!state.is_terminated());
|
||||
uint32_t orig_filled_qty = state.driver.total_fills().value();
|
||||
if (state.child_state().has_value()) {
|
||||
auto cs = state.child_state().value();
|
||||
REQUIRE(((cs.cstate.total_fills.value() - cs.cstate.fills_at_attach_time.value()) == orig_filled_qty));
|
||||
if (!allow_overfill) {
|
||||
REQUIRE(cs.active_qty() >= qty);
|
||||
}
|
||||
} else {
|
||||
REQUIRE(orig_filled_qty == 0);
|
||||
}
|
||||
state.driver.fill(state.child(), Price(price), Quantity(qty));
|
||||
}
|
||||
|
||||
void fill_child(LimitCoverState& lcs, uint32_t qty, bool allow_overfill) {
|
||||
fill_child(lcs, qty, lcs.limit_price(), allow_overfill);
|
||||
}
|
||||
|
||||
void cancel_child(LimitCoverState& state) {
|
||||
REQUIRE(state.has_been_deployed());
|
||||
REQUIRE(!state.is_terminated());
|
||||
state.driver.cancel_child(state.child());
|
||||
REQUIRE(state.driver.state() == AlgoState::TERMINATED);
|
||||
}
|
||||
|
||||
void complete(LimitCoverState& state) {
|
||||
REQUIRE(state.has_been_deployed());
|
||||
REQUIRE(!state.is_terminated());
|
||||
REQUIRE(state.driver.quantity().value() > state.driver.total_fills().value());
|
||||
fill_child(state, state.driver.quantity().value() - state.driver.total_fills().value());
|
||||
REQUIRE(state.driver.state() == AlgoState::COMPLETED);
|
||||
}
|
||||
} // namespace ops
|
||||
|
||||
/////////////////////
|
||||
// Test Steps
|
||||
/////////////////////
|
||||
namespace test_steps {
|
||||
// Begin Launch
|
||||
std::string BeginLaunch::name() const { return "Begin Launch"; }
|
||||
void BeginLaunch::execute_core(LimitCoverState &state) const { ops::begin_launch(state); }
|
||||
|
||||
// Launch
|
||||
std::string Launch::name() const { return "Launch"; }
|
||||
void Launch::execute_core(LimitCoverState &state) const { ops::launch(state); }
|
||||
|
||||
// Breech Activation Threshold
|
||||
std::string BreechActivationThresh::name() const {
|
||||
std::stringstream title;
|
||||
title << "Breech Activation Threshold " << (thresh_count + 1);
|
||||
if (offset > 0) {
|
||||
title << " (+" << offset << ")";
|
||||
} else if (offset < 0) {
|
||||
title << " (" << offset << ")";
|
||||
}
|
||||
return title.str();
|
||||
}
|
||||
void BreechActivationThresh::execute_core(LimitCoverState &state) const {
|
||||
ops::breech_activation_thresh(state, thresh_count, offset);
|
||||
}
|
||||
|
||||
// Fill
|
||||
std::string Fill::name() const { return "Fill"; }
|
||||
void Fill::execute_core(LimitCoverState &state) const {
|
||||
auto p = get_params(state);
|
||||
ops::fill_child(state, p.quantity, p.price);
|
||||
}
|
||||
|
||||
// Complete
|
||||
std::string Complete::name() const { return "Complete"; }
|
||||
void Complete::execute_core(LimitCoverState &state) const {
|
||||
ops::complete(state);
|
||||
}
|
||||
|
||||
// Cancel Child
|
||||
std::string CancelChild::name() const { return "Cancel Child"; }
|
||||
void CancelChild::execute_core(LimitCoverState& state) const {
|
||||
ops::cancel_child(state);
|
||||
}
|
||||
|
||||
// Cancel Algo
|
||||
std::string CancelAlgo::name() const { return "Cancel Algo"; }
|
||||
void CancelAlgo::execute_core(LimitCoverState& state) const {
|
||||
state.driver.cancel();
|
||||
REQUIRE(state.driver.state() == AlgoState::CANCELED);
|
||||
}
|
||||
} // namespace test_steps
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
#include "catch/catch.hpp"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverState.h"
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
// In Visual Studio, intellisense and syntax highlighting don't work (at all)
|
||||
// when <optional> is included, unless 'using namespace std;' is specified.
|
||||
using namespace std;
|
||||
|
||||
struct ITestStep {
|
||||
virtual std::string name() const;
|
||||
void execute(LimitCoverState& state) const;
|
||||
protected:
|
||||
virtual void assert_pre_conditions_core(const LimitCoverState&) const;
|
||||
virtual void execute_core(LimitCoverState&) const;
|
||||
virtual void assert_post_conditions_core(const LimitCoverState&) const;
|
||||
private:
|
||||
void assert_pre_conditions(const LimitCoverState& state) const;
|
||||
void assert_post_conditions(const LimitCoverState& state) const;
|
||||
void _execute(LimitCoverState& state) const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& ostr, const ITestStep& ts);
|
||||
LimitCoverState& operator<<(LimitCoverState& state, const ITestStep& ts);
|
||||
|
||||
namespace order_selectors {
|
||||
std::vector<OrderKey> child(const LimitCoverState &lcs);
|
||||
std::vector<OrderKey> self(const LimitCoverState &);
|
||||
} // namespace order_selectors
|
||||
|
||||
namespace ops {
|
||||
void begin_launch(LimitCoverState& lcs);
|
||||
void launch(LimitCoverState& lcs);
|
||||
void breech_activation_thresh(LimitCoverState& lcs, uint32_t thresh_count = 0, int offset = 0);
|
||||
|
||||
void fill_child(LimitCoverState& lcs, uint32_t qty, double price, bool allow_overfill = false);
|
||||
void fill_child(LimitCoverState& lcs, uint32_t qty, bool allow_overfill = false);
|
||||
|
||||
void cancel_child(LimitCoverState& state);
|
||||
void complete(LimitCoverState& lcs);
|
||||
}
|
||||
|
||||
namespace test_params {
|
||||
struct Fill {
|
||||
uint32_t quantity;
|
||||
double price;
|
||||
|
||||
Fill(uint32_t quantity, double price)
|
||||
: quantity(quantity), price(price) {}
|
||||
};
|
||||
|
||||
namespace fills {
|
||||
Fill child(const LimitCoverState& state, optional<uint32_t> qty = {}, optional<double> price = {});
|
||||
} // namespace fills
|
||||
} // namespace test_params
|
||||
namespace test_steps {
|
||||
struct BeginLaunch : public ITestStep {
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct Launch : public ITestStep {
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct BreechActivationThresh : public ITestStep {
|
||||
uint32_t thresh_count;
|
||||
int offset;
|
||||
|
||||
BreechActivationThresh(uint32_t thresh_count = 0, int offset = 0)
|
||||
: thresh_count(thresh_count), offset(offset) { }
|
||||
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct Fill : public ITestStep {
|
||||
std::function<test_params::Fill(const LimitCoverState&)> get_params;
|
||||
|
||||
Fill(std::function<test_params::Fill(const LimitCoverState&)> get_params)
|
||||
: get_params(get_params) {}
|
||||
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct Complete : public ITestStep {
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct CancelChild : public ITestStep {
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct CancelAlgo : public ITestStep {
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct Custom : public ITestStep {
|
||||
Custom(std::string name, std::function<void(LimitCoverState&)> impl)
|
||||
: _name(name), _impl(impl) { }
|
||||
|
||||
std::string name() const override { return _name; }
|
||||
void execute_core(LimitCoverState& state) const override { _impl(state); }
|
||||
private:
|
||||
std::string _name;
|
||||
std::function<void(LimitCoverState&)> _impl;
|
||||
};
|
||||
} // namespace test_steps
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <boost/dll.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
extern "C" void init_spark_plugin(bts::SparkServices &serv);
|
||||
|
||||
namespace bts {
|
||||
namespace plugin {
|
||||
|
||||
@@ -29,6 +31,9 @@ void init_plugins(SparkServices &services) {
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize the plugins in libspark.so
|
||||
init_spark_plugin(services);
|
||||
|
||||
boost::filesystem::directory_iterator end;
|
||||
for (auto iter = boost::filesystem::directory_iterator(plugin_path);
|
||||
iter != end; ++iter) {
|
||||
|
||||
@@ -19,21 +19,17 @@ target_include_directories( sparkd PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY
|
||||
set_source_files_properties(exchange_sim/ExchangeSim.cpp PROPERTIES COMPILE_FLAGS --std=c++14)
|
||||
set_source_files_properties(exchange_sim/ExchangeSimApplication.cpp PROPERTIES COMPILE_FLAGS --std=c++14)
|
||||
|
||||
if (${USE_ONIX})
|
||||
set(ONIX_LIBS, "onixs-cmemdh-all-4.8.0 OnixS.IceImpactMulticastPriceFeedHandlerCpp ACE")
|
||||
else()
|
||||
set(ONIX_LIBS, "")
|
||||
endif()
|
||||
|
||||
# linking
|
||||
target_link_libraries( sparkd
|
||||
libspark
|
||||
ssl
|
||||
cryptopp
|
||||
tbb
|
||||
onixs-cmemdh-all-4.8.0
|
||||
OnixS.IceImpactMulticastPriceFeedHandlerCpp
|
||||
ACE
|
||||
quickfix
|
||||
benchmark
|
||||
${ONIX_LIBS}
|
||||
lttng-ust
|
||||
-static-libgcc
|
||||
-static-libstdc++
|
||||
|
||||
@@ -1,423 +1,455 @@
|
||||
#ifdef USE_ONIX
|
||||
#include "ICEListener.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <vector>
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
using namespace bts::md::ice;
|
||||
|
||||
namespace bts {
|
||||
namespace md {
|
||||
namespace ice {
|
||||
|
||||
template <>
|
||||
double double_of_price<onixs_ice::Rational>(const onixs_ice::Rational& price, int8_t denominator)
|
||||
{
|
||||
UNUSED(denominator);
|
||||
double dbl = price.convertTo<double>();
|
||||
return dbl;
|
||||
}
|
||||
|
||||
template<>
|
||||
void ICEListener::set_security_specific<onixs_ice::FuturesProductDefinition>(const onixs_ice::FuturesProductDefinition& def,
|
||||
FixedSecurity& fsec)
|
||||
{
|
||||
//fsec.cash_per_point = double_of_price(def.tickValue, fsec.ice_price_denominator);
|
||||
//fsec.cash_per_point = def.tickValue;
|
||||
fsec.cash_per_point = def.unitQuantity;
|
||||
}
|
||||
|
||||
ICEListener::ICEListener(ICESecurityIndex& index, MDProducer& producer, const std::vector<MDProducerChannel> channels) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_symbol_filter(),
|
||||
_channels(channels),
|
||||
_concurrency_monitor(0)
|
||||
{
|
||||
}
|
||||
|
||||
ICEListener::~ICEListener() {}
|
||||
|
||||
std::string ICEListener::parse_contract_symbol(const std::string& sym)
|
||||
{
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
//
|
||||
//State change
|
||||
void ICEListener::onHandlerStateChange(const onixs_ice::HandlerStateChange&)
|
||||
{
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , const onixs_ice::MessageInfo&)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Warnings/Errors
|
||||
void ICEListener::onError(const onixs_ice::Error& error)
|
||||
{
|
||||
std::string error_desc(error.description());
|
||||
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
|
||||
}
|
||||
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
|
||||
}
|
||||
void ICEListener::onWarning(const onixs_ice::Warning& warning)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
|
||||
}
|
||||
|
||||
//
|
||||
// Security discovery
|
||||
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
if(msg.isSpread)
|
||||
on_future_strip(msg, is_interested);
|
||||
else
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& def, bool& is_interested)
|
||||
{
|
||||
on_uds_future_spread(def, is_interested);
|
||||
}
|
||||
//This is only for UDS
|
||||
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onOptionsProductDefinition(const onixs_ice::OptionsProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsMarketDefinition(const onixs_ice::NewOptionsMarketDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
void ICEListener::onOptionsStrategyDefinition(const onixs_ice::OptionsStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsStrategyDefinition(const onixs_ice::NewOptionsStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void ICEListener::onMarketSnapshot(const onixs_ice::MarketSnapshot& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
if (msg.settlePriceDateTime > 0) {
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(msg.settlePriceDateTime) );
|
||||
}
|
||||
|
||||
LOG(md,Diag,"OnixSICE Got Market Snap : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), datetime::today() );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
|
||||
data->handle.set_last_trade( ts, pricesize_of_price_size(msg.lastTradePrice, deal_denominator, msg.lastTradeQuantity),
|
||||
ll_of_timestamp(msg.lastTradeDateTime), msg.volume );
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStatistics(const onixs_ice::MarketStatistics& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
data->handle.set_total_volume(msg.volume);
|
||||
}
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotOrder(const onixs_ice::MarketSnapshotOrder& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPriceLevel& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
// Book
|
||||
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
|
||||
|
||||
auto data = _index.get_security(onixs_book.marketId());
|
||||
if (data == nullptr)
|
||||
return;
|
||||
bts_assert(data->id == onixs_book.marketId());
|
||||
auto denominator = data->handle.security().ice_price_denominator;
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = onixs_book.bids().size();
|
||||
size_t asks_size = onixs_book.offers().size();
|
||||
if (!data->handle.has_depth())
|
||||
{
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dbids; dbids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> ibids; ibids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dasks; dasks.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> iasks; iasks.fill(NO_MARKET);
|
||||
for (size_t x = 0; x < bids_size; ++x)
|
||||
{
|
||||
auto& bid = onixs_book.bids()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dbids[x] = pricesize_of_price_size(bid.price, denominator, bid.quantity - bid.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
ibids[x] = pricesize_of_price_size(bid.price, denominator, bid.impliedQuantity);
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x)
|
||||
{
|
||||
auto& ask = onixs_book.offers()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dasks[x] = pricesize_of_price_size(ask.price, denominator, ask.quantity - ask.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
iasks[x] = pricesize_of_price_size(ask.price, denominator, ask.impliedQuantity);
|
||||
}
|
||||
data->handle.update_books(TimeStamp::now(), dbids, dasks, ibids, iasks, (uint32_t) msg_info.sequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Trades
|
||||
AggressorSide map_aggressor(onixs_ice::AggressorSide::Enum agside)
|
||||
{
|
||||
switch(agside) {
|
||||
default:
|
||||
case onixs_ice::AggressorSide::NoAggressor:
|
||||
return AGGRESSOR_SIDE_NONE;
|
||||
case onixs_ice::AggressorSide::Buy:
|
||||
return AGGRESSOR_SIDE_BUY;
|
||||
case onixs_ice::AggressorSide::Sell:
|
||||
return AGGRESSOR_SIDE_SELL;
|
||||
}
|
||||
}
|
||||
|
||||
void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size,
|
||||
onixs_ice::AggressorSide::Enum agside, const onixs_ice::DateTime& onixs_date, bool from_spread)
|
||||
{
|
||||
auto ts = utils::TimeStamp::now();
|
||||
PriceSize ps = pricesize_of_price_size(price, data->handle.security().ice_deal_denominator, size);
|
||||
auto date = ll_of_timestamp(onixs_date);
|
||||
data->handle.add_trade(ts, ps, date, map_aggressor(agside), 0u, from_spread);
|
||||
}
|
||||
|
||||
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != NULL)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto size = msg.quantity;
|
||||
auto price = msg.price;
|
||||
onixs_ice::AggressorSide::Enum agside = msg.aggressorSide;
|
||||
on_trade(data, price, size, agside, msg.transactDateTime, msg.isSystemPricedLeg);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// RFQ
|
||||
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
co2::QuoteRequestMessage message;
|
||||
message.set_key(data->handle.key());
|
||||
|
||||
auto time = ll_of_timestamp(msg.messageTimestamp);
|
||||
message.set_time(time);
|
||||
|
||||
long long rfqid = msg.rfqSystemId;
|
||||
message.set_request_id(std::to_string(rfqid));
|
||||
|
||||
//https://ref.onixs.biz/cpp-ice-impact-handler-guide/struct_onix_s_1_1_i_c_e_1_1i_mpact_1_1_market_data_1_1_side.html#a8150b7776c2a1749101acf22e868d091
|
||||
switch(msg.side)
|
||||
{
|
||||
case ' ':
|
||||
message.set_side(co2::QuoteRequestMessage::UNDEFINED);
|
||||
break;
|
||||
case '1':
|
||||
message.set_side(co2::QuoteRequestMessage::BUY);
|
||||
break;
|
||||
case '2':
|
||||
message.set_side(co2::QuoteRequestMessage::SELL);
|
||||
break;
|
||||
}
|
||||
message.set_size(msg.quantity);
|
||||
data->handle.add_rfq(ts, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto price = msg.settlementPrice;
|
||||
auto time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_settle_denominator;
|
||||
LOG(md,Diag,"OnixSICE Got Settlement: {} - {}", std::to_string(data->id), std::to_string(price));
|
||||
data->handle.update_settlement(ts, convert_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs_ice::MessageInfo &msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.openPrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.closePrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_closing( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(time) );
|
||||
|
||||
LOG(md,Warn,"OnixSICE Got EOD Market : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), date_of_timestamp(time) );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Feed
|
||||
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
UNUSED(feedId);
|
||||
}
|
||||
|
||||
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
|
||||
{
|
||||
UNUSED(feedId);
|
||||
UNUSED(marketIds);
|
||||
}
|
||||
|
||||
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
|
||||
{}
|
||||
|
||||
void ICEListener::onFeedStopped(onixs_ice::FeedId)
|
||||
{}
|
||||
|
||||
void ICEListener::onSessionNumberChange(onixs_ice::FeedId , short , short )
|
||||
{}
|
||||
|
||||
|
||||
//
|
||||
// Non-onixs calls
|
||||
void ICEListener::add_filter_symbol(const std::string& symbol)
|
||||
{
|
||||
_symbol_filter.insert(symbol);
|
||||
}
|
||||
|
||||
void ICEListener::heartbeat()
|
||||
{
|
||||
for (auto& c : _channels) { c.heartbeat(); }
|
||||
}
|
||||
|
||||
//
|
||||
// Private functions
|
||||
void ICEListener::add_security(const FixedSecurity& security)
|
||||
{
|
||||
// Set up the shared memory for this security
|
||||
auto handle = _producer.add_security(security);
|
||||
bts_assert(security.numeric_id != 0);
|
||||
// Add it to the index so it can receive messages
|
||||
_index.add_security(security.numeric_id, handle);
|
||||
}
|
||||
|
||||
void ICEListener::update_all_status(CoconutSecurityStatus status)
|
||||
{
|
||||
_index.walk_security_list([=](ICESecurityData& data)
|
||||
{
|
||||
data.handle.set_status(status);
|
||||
});
|
||||
}
|
||||
|
||||
}}} //namespace
|
||||
|
||||
|
||||
#endif
|
||||
#include "ICEListener.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <vector>
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
using namespace bts::md::ice;
|
||||
|
||||
namespace bts {
|
||||
namespace md {
|
||||
namespace ice {
|
||||
|
||||
template <>
|
||||
double double_of_price<onixs_ice::Rational>(const onixs_ice::Rational& price, int8_t denominator)
|
||||
{
|
||||
UNUSED(denominator);
|
||||
double dbl = price.convertTo<double>();
|
||||
return dbl;
|
||||
}
|
||||
|
||||
template<>
|
||||
void ICEListener::set_security_specific<onixs_ice::FuturesProductDefinition>(const onixs_ice::FuturesProductDefinition& def,
|
||||
FixedSecurity& fsec)
|
||||
{
|
||||
//fsec.cash_per_point = double_of_price(def.tickValue, fsec.ice_price_denominator);
|
||||
//fsec.cash_per_point = def.tickValue;
|
||||
fsec.cash_per_point = def.unitQuantity;
|
||||
}
|
||||
|
||||
ICEListener::ICEListener(ICESecurityIndex& index, MDProducer& producer, const std::vector<MDProducerChannel> channels) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_symbol_filter(),
|
||||
_channels(channels),
|
||||
_concurrency_monitor(0)
|
||||
{
|
||||
}
|
||||
|
||||
ICEListener::~ICEListener() {}
|
||||
|
||||
std::string ICEListener::parse_contract_symbol(const std::string& sym)
|
||||
{
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
//
|
||||
//State change
|
||||
void ICEListener::onHandlerStateChange(const onixs_ice::HandlerStateChange&)
|
||||
{
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , const onixs_ice::MessageInfo&)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Warnings/Errors
|
||||
void ICEListener::onError(const onixs_ice::Error& error)
|
||||
{
|
||||
std::string error_desc(error.description());
|
||||
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
|
||||
}
|
||||
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
|
||||
}
|
||||
void ICEListener::onWarning(const onixs_ice::Warning& warning)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
|
||||
}
|
||||
|
||||
//
|
||||
// Security discovery
|
||||
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
if(msg.isSpread)
|
||||
on_future_strip(msg, is_interested);
|
||||
else
|
||||
on_future(msg, is_interested);
|
||||
LOG(md,Diag,"ICE_Future,{},{},{},{},{},{},{},{},{},{},{},{},{}",
|
||||
is_interested,
|
||||
msg.productName, // NG Swing GDD Futures,
|
||||
msg.hubAlias, // Henry
|
||||
msg.marketDesc, // NG Swing GDD Futures - Henry - 10 Jul 20,
|
||||
msg.stripName, // 10 Jul 20
|
||||
msg.contractSymbol, // CGS SDN1020.N1020,
|
||||
msg.productId,
|
||||
msg.marketId,
|
||||
msg.requestMarketType,
|
||||
msg.securitySubType,
|
||||
msg.contractSymbolExtra,
|
||||
msg.clearedAlias,
|
||||
msg.isSpread);
|
||||
}
|
||||
|
||||
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
LOG(md,Diag,"ICE_Strategy,{},{},{},{},{},{},{},{},{},{},{},{},{},{}",
|
||||
is_interested,
|
||||
msg.productName,
|
||||
msg.hubAlias,
|
||||
msg.marketDesc,
|
||||
msg.stripName,
|
||||
msg.contractSymbol,
|
||||
msg.productId,
|
||||
msg.marketId,
|
||||
msg.requestMarketType,
|
||||
msg.securitySubType,
|
||||
log::CharPtrWrapper {""},
|
||||
msg.clearedAlias,
|
||||
msg.productName,
|
||||
0);
|
||||
}
|
||||
|
||||
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onOptionsProductDefinition(const onixs_ice::OptionsProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsMarketDefinition(const onixs_ice::NewOptionsMarketDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
void ICEListener::onOptionsStrategyDefinition(const onixs_ice::OptionsStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsStrategyDefinition(const onixs_ice::NewOptionsStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void ICEListener::onMarketSnapshot(const onixs_ice::MarketSnapshot& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
if (msg.settlePriceDateTime > 0) {
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(msg.settlePriceDateTime) );
|
||||
}
|
||||
|
||||
LOG(md,Diag,"OnixSICE Got Market Snap : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), datetime::today() );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
|
||||
data->handle.set_last_trade( ts, pricesize_of_price_size(msg.lastTradePrice, deal_denominator, msg.lastTradeQuantity),
|
||||
ll_of_timestamp(msg.lastTradeDateTime), msg.volume );
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStatistics(const onixs_ice::MarketStatistics& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.set_total_volume(msg.volume);
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
}
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotOrder(const onixs_ice::MarketSnapshotOrder& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPriceLevel& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
// Book
|
||||
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
|
||||
|
||||
auto data = _index.get_security(onixs_book.marketId());
|
||||
if (data == nullptr)
|
||||
return;
|
||||
bts_assert(data->id == onixs_book.marketId());
|
||||
auto denominator = data->handle.security().ice_price_denominator;
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = onixs_book.bids().size();
|
||||
size_t asks_size = onixs_book.offers().size();
|
||||
if (!data->handle.has_depth())
|
||||
{
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dbids; dbids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> ibids; ibids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dasks; dasks.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> iasks; iasks.fill(NO_MARKET);
|
||||
for (size_t x = 0; x < bids_size; ++x)
|
||||
{
|
||||
auto& bid = onixs_book.bids()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dbids[x] = pricesize_of_price_size(bid.price, denominator, bid.quantity - bid.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
ibids[x] = pricesize_of_price_size(bid.price, denominator, bid.impliedQuantity);
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x)
|
||||
{
|
||||
auto& ask = onixs_book.offers()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dasks[x] = pricesize_of_price_size(ask.price, denominator, ask.quantity - ask.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
iasks[x] = pricesize_of_price_size(ask.price, denominator, ask.impliedQuantity);
|
||||
}
|
||||
data->handle.update_books(TimeStamp::now(), dbids, dasks, ibids, iasks, (uint32_t) msg_info.sequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Trades
|
||||
AggressorSide map_aggressor(onixs_ice::AggressorSide::Enum agside)
|
||||
{
|
||||
switch(agside) {
|
||||
default:
|
||||
case onixs_ice::AggressorSide::NoAggressor:
|
||||
return AGGRESSOR_SIDE_NONE;
|
||||
case onixs_ice::AggressorSide::Buy:
|
||||
return AGGRESSOR_SIDE_BUY;
|
||||
case onixs_ice::AggressorSide::Sell:
|
||||
return AGGRESSOR_SIDE_SELL;
|
||||
}
|
||||
}
|
||||
|
||||
void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size,
|
||||
onixs_ice::AggressorSide::Enum agside, const onixs_ice::DateTime& onixs_date, bool from_spread)
|
||||
{
|
||||
auto ts = utils::TimeStamp::now();
|
||||
PriceSize ps = pricesize_of_price_size(price, data->handle.security().ice_deal_denominator, size);
|
||||
auto date = ll_of_timestamp(onixs_date);
|
||||
data->handle.add_trade(ts, ps, date, map_aggressor(agside), 0u, from_spread);
|
||||
}
|
||||
|
||||
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != NULL)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto size = msg.quantity;
|
||||
auto price = msg.price;
|
||||
onixs_ice::AggressorSide::Enum agside = msg.aggressorSide;
|
||||
on_trade(data, price, size, agside, msg.transactDateTime, msg.isSystemPricedLeg);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// RFQ
|
||||
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
co2::QuoteRequestMessage message;
|
||||
message.set_key(data->handle.key());
|
||||
|
||||
auto time = ll_of_timestamp(msg.messageTimestamp);
|
||||
message.set_time(time);
|
||||
|
||||
long long rfqid = msg.rfqSystemId;
|
||||
message.set_request_id(std::to_string(rfqid));
|
||||
|
||||
//https://ref.onixs.biz/cpp-ice-impact-handler-guide/struct_onix_s_1_1_i_c_e_1_1i_mpact_1_1_market_data_1_1_side.html#a8150b7776c2a1749101acf22e868d091
|
||||
switch(msg.side)
|
||||
{
|
||||
case ' ':
|
||||
message.set_side(co2::QuoteRequestMessage::UNDEFINED);
|
||||
break;
|
||||
case '1':
|
||||
message.set_side(co2::QuoteRequestMessage::BUY);
|
||||
break;
|
||||
case '2':
|
||||
message.set_side(co2::QuoteRequestMessage::SELL);
|
||||
break;
|
||||
}
|
||||
message.set_size(msg.quantity);
|
||||
data->handle.add_rfq(ts, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto price = msg.settlementPrice;
|
||||
auto time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_settle_denominator;
|
||||
LOG(md,Info,"OnixSICE Got Settlement: {} - {}", std::to_string(data->id), std::to_string(price));
|
||||
data->handle.update_settlement(ts, convert_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs_ice::MessageInfo &msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.openPrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.closePrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_closing( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(time) );
|
||||
|
||||
LOG(md,Info,"OnixSICE Got EOD Market : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), date_of_timestamp(time) );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Feed
|
||||
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
UNUSED(feedId);
|
||||
}
|
||||
|
||||
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
|
||||
{
|
||||
UNUSED(feedId);
|
||||
UNUSED(marketIds);
|
||||
}
|
||||
|
||||
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
|
||||
{}
|
||||
|
||||
void ICEListener::onFeedStopped(onixs_ice::FeedId)
|
||||
{}
|
||||
|
||||
void ICEListener::onSessionNumberChange(onixs_ice::FeedId , short , short )
|
||||
{}
|
||||
|
||||
|
||||
//
|
||||
// Non-onixs calls
|
||||
void ICEListener::add_filter_symbol(const std::string& symbol)
|
||||
{
|
||||
LOG(md, Info, "ICEListener::add_filter_symbol: adding {}", symbol);
|
||||
_symbol_filter.insert(symbol);
|
||||
}
|
||||
|
||||
void ICEListener::heartbeat()
|
||||
{
|
||||
for (auto& c : _channels) { c.heartbeat(); }
|
||||
}
|
||||
|
||||
//
|
||||
// Private functions
|
||||
void ICEListener::add_security(const FixedSecurity& security)
|
||||
{
|
||||
// Set up the shared memory for this security
|
||||
auto handle = _producer.add_security(security);
|
||||
bts_assert(security.numeric_id != 0);
|
||||
// Add it to the index so it can receive messages
|
||||
_index.add_security(security.numeric_id, handle);
|
||||
}
|
||||
|
||||
void ICEListener::update_all_status(CoconutSecurityStatus status)
|
||||
{
|
||||
_index.walk_security_list([=](ICESecurityData& data)
|
||||
{
|
||||
data.handle.set_status(status);
|
||||
});
|
||||
}
|
||||
|
||||
}}} //namespace
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#ifdef USE_ONIX
|
||||
|
||||
#include "ICEMain.h"
|
||||
|
||||
#include "lib/md/ice/ICEUtils.h"
|
||||
@@ -249,4 +247,3 @@ unique_ptr<ICEListener> ICEMain::prepare_listener(onixs_ice::Handler& handler, c
|
||||
|
||||
}}} //namespace
|
||||
|
||||
#endif
|
||||
@@ -1,4 +1,3 @@
|
||||
#ifdef USE_ONIX
|
||||
#include "MDP3Listener.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <vector>
|
||||
@@ -303,4 +302,3 @@ md::MDP3Listener::BookData::BookData(MDProducerHandle & handle) : handle(handle)
|
||||
}}//namespace
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,10 +1,7 @@
|
||||
#ifdef USE_ONIX
|
||||
#include "MDP3Main.h"
|
||||
|
||||
#include "lib/md/cme/MDP3Utils.h"
|
||||
|
||||
#include "lib/md/vendor/OnixsUtils.h"
|
||||
|
||||
#include "lib/md/MBOProcessor.h"
|
||||
#include "lib/config/config.h"
|
||||
#include "lib/md/coconut2.h"
|
||||
@@ -368,4 +365,3 @@ unique_ptr<MDP3Listener> MDP3Main::prepare_listener(onixs::Handler& handler, con
|
||||
|
||||
}}//namespace
|
||||
|
||||
#endif
|
||||
@@ -10,16 +10,14 @@
|
||||
#include "lib/md/Diag/ShmDumper.h"
|
||||
#include "lib/md/Diag/MDPeriodicDumper.h"
|
||||
#include "lib/md/cme/MDP3Utils.h"
|
||||
#include "platforms/mdp3_onixs/MDP3Main.h"
|
||||
#include "platforms/ice_onixs/ICEMain.h"
|
||||
#include "platforms/IMDPlatform.h"
|
||||
#include "lib/md/SecurityDB.h"
|
||||
#include "server/sparkd/exchange_sim/ExchangeSim.h"
|
||||
#include "lib/utils/thread_manager.h"
|
||||
#include "lib/utils/parse_enum.h"
|
||||
|
||||
#ifdef USE_ONIX
|
||||
#include "platforms/mdp3_onixs/MDP3Main.h"
|
||||
#include "platforms/ice_onixs/ICEMain.h"
|
||||
#endif
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <iostream>
|
||||
@@ -73,9 +71,6 @@ void run_replay() {
|
||||
}
|
||||
|
||||
void run_onixs(MBOWriter* mbo) {
|
||||
(void) mbo;
|
||||
|
||||
#ifdef USE_ONIX
|
||||
std::vector<std::shared_ptr<IMDPlatform>> platforms;
|
||||
// get all glbx symbols
|
||||
std::vector<UserSymbol> cme_symbols;
|
||||
@@ -109,7 +104,6 @@ void run_onixs(MBOWriter* mbo) {
|
||||
heartbeat_timer.start();
|
||||
|
||||
disp.run();
|
||||
#endif
|
||||
}
|
||||
|
||||
void run_cleanup() {
|
||||
|
||||
@@ -40,7 +40,7 @@ struct MarginServiceCore::PImpl
|
||||
PImpl(utils::Dispatcher& disp) :
|
||||
dispatcher(disp),
|
||||
last_results(), last_valid_results(), //api_bts_span BT5Margin!
|
||||
url_base("https://cmecorenr.cmegroup.com/MarginServiceApi/1.4/"), core_user(""), core_password(""),
|
||||
url_base("https://cmecorenr.cmegroup.com/MarginServiceApi/1.4/"), core_user("api_bts1"), core_password("Summer123"),
|
||||
post_margin_path("margins/?complete=true"),
|
||||
get_margin_path("margins/"),
|
||||
verbose(false),
|
||||
|
||||
@@ -7,7 +7,7 @@ This will test our ability to generate and enforce keycodes.
|
||||
|
||||
2) Now generate a keycode to a file:
|
||||
|
||||
./sparkd keycode encode -keyfile:spark_private.pem -pass: -users:1 -expiration:2020-05-18 > test.pem
|
||||
./sparkd keycode encode -keyfile:spark_private.pem -pass:BT5u53r -users:1 -expiration:2020-05-18 > test.pem
|
||||
|
||||
3) Now decode the keycode
|
||||
|
||||
@@ -30,7 +30,7 @@ You should see keycode details printed to screen
|
||||
|
||||
5) Test expiration. Generate a keycode in the past:
|
||||
|
||||
./sparkd keycode encode -keyfile:spark_private.pem -pass: -users:1 -expiration:2018-05-18 > ~/sparkdata/conf/keycode.conf
|
||||
./sparkd keycode encode -keyfile:spark_private.pem -pass:BT5u53r -users:1 -expiration:2018-05-18 > ~/sparkdata/conf/keycode.conf
|
||||
|
||||
a) start sparkd again.
|
||||
b) Start a spark -multiple login as bts_2 and go to where you would see some market data. You should NOT see data
|
||||
|
||||
Reference in new issue
Block a user