Compare commits

...
23 Commits
Author SHA1 Message Date
Pete Fisher d4afcd8379 fixed time on ilink2 audit 2020-09-16 16:35:11 -04:00
Pedro Pinto d2704a5430 Fixed time reporting in iLink3. 2020-09-16 16:34:55 -04:00
Pedro Pinto 85cdfb9eeb Merged from master 2020-08-31 08:20:21 -04:00
Pedro Pinto 0cf6af4a85 Merged from master 2020-08-10 14:10:25 -04:00
Pedro Pinto 4e67956f88 Merged with master 2020-06-08 16:16:33 -04:00
Pedro Pinto cd7db5f3a6 Updated subrepos 2020-05-28 12:00:24 -04:00
John Colvin c15ae7b0b0 update jenkinsbuild.cmd to use externals nuget source (for windows) 2020-05-27 11:39:16 -05:00
Pedro Pinto e675db59d7 Merge branch 'cqg_package' of bitbucket.org:bluetradesys/spark into cqg_package 2020-05-27 12:29:58 -04:00
John Colvin 8bd29973df Correctly update externals with windows nuget packages 2020-05-27 11:29:24 -05:00
John Colvin d84d521603 nuget_restore.cmd uses externals package source (builds offline) 2020-05-27 10:56:33 -05:00
John Colvin 53ecf735e0 update externals with windows nuget packages 2020-05-27 10:55:14 -05:00
Pedro Pinto 17435c23a9 Added -f when removing simlinks in build setup. 2020-05-27 11:52:01 -04:00
Pedro Pinto 8873bf56ee Maybe fixed server side build to not require internet 2020-05-26 21:17:46 -04:00
Pedro Pinto b848accc8f Removed dependency on nuget website 2020-05-26 17:30:46 -04:00
Pedro Pinto 8c1606e7c9 Updated setup_builds to work even if its run outside the tree. 2020-05-26 11:29:22 -04:00
John Colvin d277511806 hard code 5 second ladder-recenter-interval 2020-05-24 11:11:08 -05:00
Pedro Pinto 05ed3a39ac Removed private jetfix sessions 2020-05-21 17:42:14 -04:00
Pedro Pinto a389609e73 Made OnixS optional at compile time 2020-05-21 17:20:35 -04:00
Pedro Pinto 6c5ac7f8e0 Defaulted configuration to not use OnixS. 2020-05-21 10:28:59 -04:00
Pedro Pinto 68599af2e4 Removed Onix license from code. 2020-05-20 11:59:42 -04:00
Pedro Pinto 2cd12516b2 Removed ops_notes 2020-05-19 11:55:54 -04:00
Pedro Pinto f48ebcb00d Removed npm download build step 2020-05-19 11:52:04 -04:00
Pedro Pinto a7f404d88f Removed propietary algos, celoxica, passwords, cert_secure_log file 2020-05-19 10:15:03 -04:00
47 changed files with 621 additions and 8782 deletions

No files matched your search

+8
View File
@@ -1,12 +1,15 @@
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")
@@ -40,6 +43,11 @@ 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")
-26
View File
@@ -1,26 +0,0 @@
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
+1 -3
View File
@@ -10,18 +10,16 @@ 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/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)
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)
+2 -2
View File
@@ -48,7 +48,7 @@ def get_value(json_key):
# Column 1 - Sending Timestamps
def SendingTimeEpoch(epochtime): #1
#TIME = time.strftime('%Y%m%d-%H:%M:%S.{}'.format(epochtime/1000000000), time.gmtime(epochtime/1000000000))
TIME = datetime.datetime.fromtimestamp(epochtime/1000000000).strftime('%Y%m%d-%H:%M:%S.%f')
TIME = datetime.datetime.fromtimestamp(int(epochtime) / 1e9).strftime('%Y%m%d-%H:%M:%S.%f')
return TIME
# Column 2 - Message Direction
@@ -402,4 +402,4 @@ with file_opener(args.json_log) as json_file:
@@ -1,19 +0,0 @@
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>katas2f-ps</userName>
<password>Starts123</password>
<userName></userName>
<password></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>katas2f-ps</userName>
<password>Starts123</password>
<userName>Value from ICE Support</userName>
<password>Value from ICE Support</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>bluetrdl-ps</userName>
<password>Starts123</password>
<userName></userName>
<password></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>bt_ps</userName>
<password>Starts123</password>
<userName></userName>
<password></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
@@ -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("bluetrd-fx");
exchange_user_data.mutable_ice_data()->set_authorized_trader("");
exchange_user_data.mutable_ice_data()->set_authorized_group("Group");
exchange_user_data.mutable_ice_data()->set_password("Starts123");
exchange_user_data.mutable_ice_data()->set_password("");
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("bluetrd-fx");
exchange_user_data.mutable_ice_data()->set_authorized_trader("");
exchange_user_data.mutable_ice_data()->set_authorized_group("Group");
exchange_user_data.mutable_ice_data()->set_password("Starts123");
exchange_user_data.mutable_ice_data()->set_password("");
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("bluetrd-fx");
exchange_user_data.mutable_ice_data()->set_authorized_trader("");
exchange_user_data.mutable_ice_data()->set_authorized_group("Group");
exchange_user_data.mutable_ice_data()->set_password("Starts123");
exchange_user_data.mutable_ice_data()->set_password("");
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);
+137 -130
View File
@@ -1,136 +1,143 @@
#
# 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
}
}
}
#
# 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
}
}
}
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
+2
View File
@@ -1,3 +1,4 @@
#ifdef USE_ONIX
#ifndef _WIN32
#include "ICEUtils.h"
@@ -129,3 +130,4 @@ const std::vector<ICEChannel> get_configured_ice_channels() {
}}} //namespace
#endif
#endif
-14
View File
@@ -13,20 +13,6 @@ 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>"
};
+1 -1
View File
@@ -18,7 +18,7 @@ struct TimeKeeper {
uint64_t now() const {
auto now = utils::TimeStamp::now();
auto delta = now - start;
return unix_time + delta.microseconds();
return unix_time + delta.nanoseconds();
}
};
@@ -1,376 +0,0 @@
#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
@@ -1,96 +0,0 @@
#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
@@ -1,401 +0,0 @@
#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
@@ -1,94 +0,0 @@
#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
@@ -1,584 +0,0 @@
#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
@@ -1,74 +0,0 @@
#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
@@ -1,253 +0,0 @@
#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);
}
}}}
@@ -1,222 +0,0 @@
#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
@@ -1,45 +0,0 @@
#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
@@ -1,490 +0,0 @@
#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
@@ -1,41 +0,0 @@
#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
@@ -1,152 +0,0 @@
#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
@@ -1,15 +0,0 @@
#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
@@ -1,71 +0,0 @@
#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
@@ -1,87 +0,0 @@
#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
@@ -1,196 +0,0 @@
#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
@@ -1,743 +0,0 @@
#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
@@ -1,199 +0,0 @@
#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
@@ -1,121 +0,0 @@
#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
@@ -1,44 +0,0 @@
#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
@@ -1,221 +0,0 @@
#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
@@ -1,126 +0,0 @@
#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,8 +9,6 @@
#include <boost/dll.hpp>
#include <boost/filesystem.hpp>
extern "C" void init_spark_plugin(bts::SparkServices &serv);
namespace bts {
namespace plugin {
@@ -31,9 +29,6 @@ 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) {
+7 -3
View File
@@ -19,17 +19,21 @@ 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,455 +1,423 @@
#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
#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
@@ -1,3 +1,5 @@
#ifdef USE_ONIX
#include "ICEMain.h"
#include "lib/md/ice/ICEUtils.h"
@@ -247,3 +249,4 @@ unique_ptr<ICEListener> ICEMain::prepare_listener(onixs_ice::Handler& handler, c
}}} //namespace
#endif
@@ -1,3 +1,4 @@
#ifdef USE_ONIX
#include "MDP3Listener.h"
#include "lib/utils/datetime.h"
#include <vector>
@@ -302,3 +303,4 @@ md::MDP3Listener::BookData::BookData(MDProducerHandle & handle) : handle(handle)
}}//namespace
#endif
@@ -1,7 +1,10 @@
#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"
@@ -365,3 +368,4 @@ unique_ptr<MDP3Listener> MDP3Main::prepare_listener(onixs::Handler& handler, con
}}//namespace
#endif
+8 -2
View File
@@ -10,14 +10,16 @@
#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>
@@ -71,6 +73,9 @@ 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;
@@ -104,6 +109,7 @@ 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("api_bts1"), core_password("Summer123"),
url_base("https://cmecorenr.cmegroup.com/MarginServiceApi/1.4/"), core_user(""), core_password(""),
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:BT5u53r -users:1 -expiration:2020-05-18 > test.pem
./sparkd keycode encode -keyfile:spark_private.pem -pass: -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:BT5u53r -users:1 -expiration:2018-05-18 > ~/sparkdata/conf/keycode.conf
./sparkd keycode encode -keyfile:spark_private.pem -pass: -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