Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4afcd8379 | ||
|
|
d2704a5430 | ||
|
|
85cdfb9eeb | ||
|
|
0cf6af4a85 | ||
|
|
4e67956f88 | ||
|
|
cd7db5f3a6 | ||
|
|
c15ae7b0b0 | ||
|
|
e675db59d7 | ||
|
|
8bd29973df | ||
|
|
d84d521603 | ||
|
|
53ecf735e0 | ||
|
|
17435c23a9 | ||
|
|
8873bf56ee | ||
|
|
b848accc8f | ||
|
|
8c1606e7c9 | ||
|
|
d277511806 | ||
|
|
05ed3a39ac | ||
|
|
a389609e73 | ||
|
|
6c5ac7f8e0 | ||
|
|
68599af2e4 | ||
|
|
2cd12516b2 | ||
|
|
f48ebcb00d | ||
|
|
a7f404d88f |
No files matched your search
@@ -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")
|
||||
|
||||
+1
-1
Submodule docker/linux updated: 6f0dd9324b...78ed1605aa.
@@ -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
|
||||
@@ -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 @@
|
||||
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)
|
||||
@@ -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 @@
|
||||
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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
Vendored
-14
@@ -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>"
|
||||
};
|
||||
|
||||
|
||||
@@ -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>(¶ms)) {
|
||||
return *error;
|
||||
}
|
||||
me->params = std::get<AlgoParams>(params);
|
||||
me->side = def.side();
|
||||
if (me->side != api::Side::SIDE_BUY && me->side != api::Side::SIDE_SELL) {
|
||||
return OrderOpResult { ORDER_ERROR_INVALID_ORDER_DEFINITON, "Invalid side" };
|
||||
}
|
||||
|
||||
if (me->params.trade_accumulation) {
|
||||
auto& t = me->params.trade_accumulation.value();
|
||||
me->trade_accumulator = {Accumulator {t.contracts, t.seconds, ibl.get_time()}};
|
||||
}
|
||||
|
||||
CustomOrderDefinition default_order;
|
||||
default_order.mutable_limit();
|
||||
auto child_def =
|
||||
get_if<CustomOrderDefinition>(def.custom_def().dynamic().order_params(), "Child")
|
||||
.value_or(default_order);
|
||||
|
||||
update_child_template(ibl, child_def);
|
||||
|
||||
if (ibl.is_attaching()) {
|
||||
return OrderOpResult{ORDER_ERROR_OP_NOT_SUPPORTED,
|
||||
"This algo does not support attach."};
|
||||
}
|
||||
|
||||
return {/*ok*/};
|
||||
}
|
||||
|
||||
|
||||
void DormanAlgo::do_launch(IAlgoServices& algo) {
|
||||
me->algo = &algo;
|
||||
auto skey = SecurityKey(algo.security().id());
|
||||
algo.subscribe_book(skey);
|
||||
algo.subscribe_timer();
|
||||
if (me->trade_accumulator) {
|
||||
algo.subscribe_ticker(skey);
|
||||
}
|
||||
}
|
||||
|
||||
void DormanAlgo::do_launch_attached(IAlgoServices&, OrderKey, ChildState) {
|
||||
bts_assert(false);
|
||||
}
|
||||
void DormanAlgo::do_update(const api::orderman::Update& update) {
|
||||
LOG(OrderMan, Diag, "Dorman order {} receiving update: {}",
|
||||
algo().order_key().value(), utils::pb::to_string(update));
|
||||
|
||||
if (update.has_custom()) {
|
||||
if (!update.custom().has_dynamic()) {
|
||||
algo().cancel("Update cannot change order type");
|
||||
return;
|
||||
}
|
||||
auto params = to_dorman_params(update.custom().dynamic());
|
||||
|
||||
if (auto error = std::get_if<OrderOpResult>(¶ms)) {
|
||||
algo().cancel(error->error_msg);
|
||||
}
|
||||
|
||||
me->params = std::get<AlgoParams>(params);
|
||||
auto skey = SecurityKey(algo().security().id());
|
||||
if (me->params.trade_accumulation && !me->trade_accumulator) {
|
||||
algo().subscribe_ticker(skey);
|
||||
auto& t = me->params.trade_accumulation.value();
|
||||
me->trade_accumulator = {Accumulator {t.contracts, t.seconds, algo().get_time()}};
|
||||
}
|
||||
else if (!me->params.trade_accumulation && me->trade_accumulator) {
|
||||
algo().unsubscribe_ticker(skey);
|
||||
me->trade_accumulator = std::nullopt;
|
||||
}
|
||||
else if (me->params.trade_accumulation &&
|
||||
me->trade_accumulator &&
|
||||
(me->trade_accumulator.value().threshold != me->params.trade_accumulation.value().contracts ||
|
||||
me->trade_accumulator.value().bucket_interval != std::chrono::seconds(me->params.trade_accumulation.value().seconds))
|
||||
) {
|
||||
// accumulator params have changed
|
||||
auto& t = me->params.trade_accumulation.value();
|
||||
me->trade_accumulator = {Accumulator {t.contracts, t.seconds, algo().get_time()}};
|
||||
}
|
||||
|
||||
// Check for child
|
||||
auto child = get_if<CustomOrderDefinition>(update.custom().dynamic().order_params(), "Child");
|
||||
if (child) {
|
||||
update_child_template(algo(), child.value());
|
||||
}
|
||||
}
|
||||
|
||||
if (me->fills >= update.quantity()) {
|
||||
complete();
|
||||
}
|
||||
else {
|
||||
// In case of price/qty change always update the child
|
||||
// NOTE: it might be useful to cache the available market data size
|
||||
// and update based on that if the update contains a size table change
|
||||
update_child(me->exposed_qty);
|
||||
}
|
||||
}
|
||||
|
||||
void DormanAlgo::on_child_change(OrderKey key, const ChildState& state) {
|
||||
if (key != me->child_id) return;
|
||||
|
||||
if (state.status == ChildStatus::CANCELED && !is_paused()) {
|
||||
algo().cancel("Unexpected child cancel");
|
||||
return;
|
||||
}
|
||||
|
||||
auto delta = state.total_fills.value() - me->child_state.total_fills.value();
|
||||
if (delta != 0) {
|
||||
me->fills += delta;
|
||||
algo().update_fill_state(Quantity {me->fills}, state.last_price);
|
||||
send_profit_order(state.last_price, Quantity {delta});
|
||||
}
|
||||
me->child_state = state;
|
||||
|
||||
if (me->params.scratch &&
|
||||
me->fills >= me->params.scratch.value().max_overfill) {
|
||||
do_scratch();
|
||||
}
|
||||
|
||||
if (me->fills >= algo().quantity().value() || state.status == ChildStatus::COMPLETED) {
|
||||
complete();
|
||||
}
|
||||
}
|
||||
|
||||
void DormanAlgo::on_book(SecurityKey key, const Book& book) {
|
||||
if (key.value() != me->algo->security().id()) { return; }
|
||||
auto price = me->algo->price();
|
||||
auto tick = me->algo->security().contract_spec().low_tick();
|
||||
|
||||
// Determine available size on the market
|
||||
auto& side = book.side(me->side);
|
||||
uint32_t available = 0;
|
||||
for (const auto& level : side) {
|
||||
if (securities::test_price_equality(level.price(), price.value(), tick)) {
|
||||
available += level.size();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the step_table
|
||||
auto target = Quantity {0};
|
||||
if (available > 0) {
|
||||
for (const auto& entry : me->params.steps.step_table) {
|
||||
if (std::get<0>(entry) > available) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
target = Quantity { std::get<1>(entry) };
|
||||
}
|
||||
}
|
||||
}
|
||||
target = Quantity { std::min(target.value(), me->params.steps.maximum) };
|
||||
|
||||
// First on_book
|
||||
if (!child_active()) {
|
||||
update_child(target);
|
||||
}
|
||||
// See if we need to adjust
|
||||
else {
|
||||
if (target == me->exposed_qty) {
|
||||
me->next_size = std::nullopt;
|
||||
}
|
||||
// If target < size reduce immediately
|
||||
else if (target < me->exposed_qty) {
|
||||
update_child(target);
|
||||
me->next_size = std::nullopt;
|
||||
}
|
||||
// if target > size schedule increase
|
||||
else if (target > me->exposed_qty) {
|
||||
if (me->next_size) {
|
||||
// already planning to increase, update the target
|
||||
me->next_size.value().size = target;
|
||||
}
|
||||
else {
|
||||
// no existing scheduled increase, create one now
|
||||
me->next_size = { FutureQty { target, me->algo->get_time() + std::chrono::seconds(me->params.steps.trail_seconds) } };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void DormanAlgo::on_timer() {
|
||||
// Check for pending size changes
|
||||
auto now = algo().get_time();
|
||||
if (me->pause_until &&
|
||||
now >= me->pause_until.value()) {
|
||||
|
||||
resume();
|
||||
}
|
||||
if (me->next_size &&
|
||||
now >= me->next_size.value().when) {
|
||||
auto size = me->next_size.value().size;
|
||||
update_child(size);
|
||||
me->next_size = std::nullopt;
|
||||
}
|
||||
}
|
||||
void DormanAlgo::on_ticker(SecurityKey, const TradeVector& vec) {
|
||||
if (me->trade_accumulator) {
|
||||
size_t contracts = 0;
|
||||
for (const auto& t : vec) {
|
||||
contracts += t.quantity.value();
|
||||
}
|
||||
auto& acc = me->trade_accumulator.value();
|
||||
acc.push_back(algo().get_time(), contracts);
|
||||
if (acc.exceeded()) {
|
||||
auto delay = std::chrono::seconds(me->params.trade_accumulation.value().sleep_seconds);
|
||||
pause_for(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IAlgoOrder::OnCancelResponse DormanAlgo::on_cancel() {
|
||||
// detach any remaining profit orders so they keep running
|
||||
for(const auto& key : me->profit_orders) {
|
||||
algo().detach_child(key);
|
||||
}
|
||||
return OnCancelResponse::HONOR;
|
||||
}
|
||||
|
||||
IAlgoServices& DormanAlgo::algo() const { return *me->algo; }
|
||||
|
||||
//
|
||||
// private
|
||||
|
||||
void DormanAlgo::update_child(Quantity exposed) {
|
||||
auto& splitter = *me->splitter_definition.mutable_custom_def()->mutable_splitter();
|
||||
splitter.set_max_order_qty(me->params.clip_limit);
|
||||
me->exposed_qty = exposed;
|
||||
splitter.set_exposed_qty(is_paused() ? 0 : exposed.value());
|
||||
|
||||
if (!child_active()) {
|
||||
// launch child
|
||||
me->child_id = algo().launch_child(me->splitter_definition, algo().price(), algo().quantity());
|
||||
}
|
||||
else {
|
||||
Update update;
|
||||
update.set_price(algo().price().value());
|
||||
update.set_quantity(algo().quantity().value());
|
||||
*update.mutable_custom()->mutable_splitter() = splitter;
|
||||
algo().update_child(me->child_id, update);
|
||||
}
|
||||
}
|
||||
|
||||
void DormanAlgo::update_child_template(IBeforeLaunchAlgoServices& ibl, const CustomOrderDefinition& child) {
|
||||
auto child_def = ibl.definition();
|
||||
// Insert a spliter between this and the configured child definition.
|
||||
api::orderman::SplitterOrderDefinition& splitter_def = *child_def.mutable_custom_def()->mutable_splitter();
|
||||
*splitter_def.mutable_child() = child;
|
||||
me->splitter_definition = child_def;
|
||||
}
|
||||
|
||||
Quantity DormanAlgo::exposed_qty() const { return me->exposed_qty; }
|
||||
|
||||
bool DormanAlgo::child_active() const { return me->child_id.value() != 0; }
|
||||
|
||||
void DormanAlgo::do_scratch() {
|
||||
// When the # of fills exceeds the max overfill quantity
|
||||
// We enter fills opposite fills at the fill price and terminate
|
||||
// Use a splitter to bypass clip limit if necessary
|
||||
|
||||
OrderDefinition def = me->splitter_definition;
|
||||
def.set_side(securities::other_side(me->side));
|
||||
def.mutable_custom_def()->mutable_splitter()->set_exposed_qty(me->fills);
|
||||
|
||||
auto price = me->child_state.last_price;
|
||||
auto scratch_id = algo().launch_child(def, price, Quantity { me->fills }, false);
|
||||
|
||||
algo().detach_child(scratch_id);
|
||||
complete();
|
||||
}
|
||||
|
||||
void DormanAlgo::send_profit_order(Price p, Quantity q) {
|
||||
if (me->params.profit_orders) {
|
||||
auto side = securities::other_side(me->side);
|
||||
Price offset =
|
||||
Price { algo().security().contract_spec().low_tick() * me->params.profit_orders.value().ticks_away };
|
||||
auto price =
|
||||
securities::worsen_price(side, p, offset);
|
||||
|
||||
OrderDefinition def = me->splitter_definition;
|
||||
// BTS Simulator does not currently support GTC
|
||||
// Note: This could be made a configurable order type in the algo parameters
|
||||
def.set_time_in_force(TimeInForce::TIME_IN_FORCE_DAY);
|
||||
def.set_side(side);
|
||||
def.mutable_custom_def()->mutable_limit();
|
||||
me->profit_orders.push_back(
|
||||
algo().launch_child(def, price, q, false));
|
||||
}
|
||||
}
|
||||
|
||||
void DormanAlgo::complete() {
|
||||
// Allow profit orders to keep running
|
||||
if (me->params.profit_orders) {
|
||||
for(const auto& key : me->profit_orders) {
|
||||
algo().detach_child(key);
|
||||
}
|
||||
}
|
||||
algo().complete();
|
||||
}
|
||||
|
||||
|
||||
void DormanAlgo::pause_for(Duration d) {
|
||||
if (me->params.trade_accumulation) {
|
||||
if (is_paused()) {
|
||||
me->pause_until = std::max(me->pause_until.value(), { algo().get_time() + d });
|
||||
}
|
||||
else {
|
||||
me->pause_until = { algo().get_time() + d };
|
||||
// When paused child update will always expose 0
|
||||
update_child(me->exposed_qty);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool DormanAlgo::is_paused() const {
|
||||
return (bool)(me->pause_until);
|
||||
}
|
||||
void DormanAlgo::resume() {
|
||||
if (is_paused()) {
|
||||
me->pause_until = std::nullopt;
|
||||
update_child(me->exposed_qty);
|
||||
}
|
||||
}
|
||||
|
||||
}}} //namespace
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user