Compare commits

..
10 Commits
Author SHA1 Message Date
Pete Fisher e974edfc6e Merge branch 'master' of bitbucket.org:bluetradesys/spark 2021-03-18 16:22:27 -04:00
Pete Fisher 3b7740cee0 updating submodule to latest 2021-03-18 16:22:12 -04:00
John Colvin 258d4a3fc1 import compute_synthetic_book method from spartan 2020-12-08 23:00:18 -06:00
John Colvin c53f6425cd import IntPrice arithmetic operators from spartan
The compute_synthetic_book method (also to be imported
from spartan) depends on a couple of them.
2020-12-08 22:52:28 -06:00
Pedro Pinto e5d77593d1 Changed logging 2020-10-15 14:24:02 -04:00
Pedro Pinto 6580f3883b Added logging of cancel/replace 2020-10-15 14:18:57 -04:00
Pete Fisher a66eae66bb fixing for python3 2020-10-14 21:56:49 -04:00
Pedro Pinto 090a9dfb17 Added iLink3 documentation. 2020-09-14 12:02:37 -04:00
Pete Fisher 27aa43f44d fixed time on ilink2 audit 2020-09-09 12:16:57 -04:00
Pedro Pinto 19e087aa69 Fixed time reporting in iLink3. 2020-09-09 10:58:34 -04:00
53 changed files with 9160 additions and 635 deletions

No files matched your search

+1
View File
@@ -37,3 +37,4 @@ trace_data/
*.rej
/build
.clangd/
*CMakeDeps.cmake
-8
View File
@@ -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
View File
@@ -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)
+26
View File
@@ -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
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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);
+130 -137
View File
@@ -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
View File
@@ -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;
}
}}
}}
-2
View File
@@ -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
+14
View File
@@ -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;
}
+179
View File
@@ -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
+155 -6
View File
@@ -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
+10 -5
View File
@@ -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>(&params)) {
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>(&params)) {
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
+5
View File
@@ -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) {
+3 -7
View File
@@ -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
+2 -8
View File
@@ -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),
+2 -2
View File
@@ -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