Compare commits
20
Commits
20.0.0_rc3
...
2.16.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c30bab899f | ||
|
|
46544f36f1 | ||
|
|
c7df3ac43c | ||
|
|
400143c84d | ||
|
|
479f3dccff | ||
|
|
c272d8a8c2 | ||
|
|
fbd8bbd4a8 | ||
|
|
3da52b350f | ||
|
|
9d1b03d1e2 | ||
|
|
9a677cf980 | ||
|
|
25445d58d0 | ||
|
|
0b313a8aa8 | ||
|
|
54f954fd4b | ||
|
|
980f38ba03 | ||
|
|
9eada2724b | ||
|
|
b5f53ee918 | ||
|
|
efc175f654 | ||
|
|
6074afd999 | ||
|
|
219dff4055 | ||
|
|
9a3b07b622 |
No files matched your search
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.co2;
|
||||
using bts.utils;
|
||||
using bts.utils.csv;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
@@ -44,6 +45,18 @@ namespace bts.shell.time_and_sales {
|
||||
public void raise_invalidated() =>
|
||||
invalidated?.Invoke();
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(
|
||||
typeof(TimeAndSalesRowVM),
|
||||
new string[] {
|
||||
nameof(security),
|
||||
nameof(time),
|
||||
nameof(price),
|
||||
nameof(size),
|
||||
nameof(aggressor_side),
|
||||
nameof(security_key),
|
||||
});
|
||||
|
||||
public bool Equals(TimeAndSalesRowVM other) =>
|
||||
other != null &&
|
||||
row_key == other.row_key;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using bts.utils.csv;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
@@ -114,6 +115,8 @@ namespace bts.shell.time_and_sales {
|
||||
show_in_context_menu = _can_pause_updates
|
||||
};
|
||||
_commands = new SparkCommand[] {
|
||||
// csv export
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(TimeAndSalesRowVM.csv_ordered_properties), "TaS")),
|
||||
// ladder
|
||||
SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder)
|
||||
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
|
||||
|
||||
@@ -5,6 +5,7 @@ using bts.collections;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using bts.utils.csv;
|
||||
using mbts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
@@ -330,6 +331,27 @@ namespace mbts.spark {
|
||||
public override bool Equals(object obj) => Equals(obj as Row);
|
||||
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(typeof(Row),
|
||||
new string[] {
|
||||
nameof(security),
|
||||
nameof(order_type),
|
||||
nameof(root_algo_display_name),
|
||||
nameof(side),
|
||||
nameof(fill_time),
|
||||
nameof(user),
|
||||
nameof(account),
|
||||
nameof(exchange),
|
||||
nameof(time_in_force),
|
||||
nameof(size),
|
||||
nameof(price),
|
||||
nameof(fill_kind),
|
||||
nameof(fill_status),
|
||||
nameof(fill_key),
|
||||
nameof(order_key),
|
||||
nameof(security_key)
|
||||
});
|
||||
|
||||
public event Action invalidated;
|
||||
void IRowVM.raise_invalidated() => invalidated?.Invoke();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using bts.utils.csv;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
@@ -61,6 +62,7 @@ namespace mbts.spark {
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
|
||||
_commands = new List<SparkCommand> {
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace mbts.spark {
|
||||
string candidate = string.Empty;
|
||||
if (a.HasValue) {
|
||||
var algo_name = a.Value.name;
|
||||
var words = algo_name.Split(delim);
|
||||
var words = algo_name.Split(delim, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (words.Length == 1) {
|
||||
var single_word = words[0];
|
||||
if (single_word.Length > 1) {
|
||||
@@ -152,7 +152,7 @@ namespace mbts.spark {
|
||||
candidate = single_word;
|
||||
}
|
||||
} else {
|
||||
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
|
||||
candidate = string.Join(string.Empty, words.Take(2).Select(w => w.Substring(0, 1)));
|
||||
}
|
||||
}
|
||||
return candidate.ToUpper();
|
||||
|
||||
@@ -4,6 +4,7 @@ using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using bts.utils.csv;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
@@ -11,6 +12,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
@@ -59,6 +61,35 @@ namespace mbts.spark {
|
||||
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(
|
||||
typeof(OrderBookRowVM),
|
||||
new string[] {
|
||||
nameof(security_text),
|
||||
nameof(symbol),
|
||||
nameof(time_created),
|
||||
nameof(last_change),
|
||||
nameof(user),
|
||||
nameof(account),
|
||||
nameof(exchange),
|
||||
nameof(time_in_force),
|
||||
nameof(order_type),
|
||||
nameof(root_algo_display_name),
|
||||
nameof(order_sub_type),
|
||||
nameof(side),
|
||||
nameof(quantity),
|
||||
nameof(filled_quantity),
|
||||
nameof(price),
|
||||
nameof(avg_price),
|
||||
nameof(last_price),
|
||||
nameof(piq),
|
||||
nameof(status),
|
||||
// last message (could contains commas, so skipping)
|
||||
nameof(security_key),
|
||||
nameof(order_key),
|
||||
nameof(parent_order_key),
|
||||
});
|
||||
|
||||
// Core
|
||||
public OrderSecurityRootPair order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order.order;
|
||||
|
||||
@@ -6,6 +6,7 @@ using bts.shell.fills;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using bts.utils.csv;
|
||||
using mbts.persist;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
@@ -231,6 +232,7 @@ namespace mbts.spark {
|
||||
toggle_collapse_all_command,
|
||||
toggle_expand_all_command,
|
||||
show_grid_settings_command,
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
|
||||
// hidden cmds (i.e., keyboard shortcuts at window level)
|
||||
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
|
||||
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
|
||||
|
||||
@@ -64,6 +64,25 @@ namespace mbts.ui {
|
||||
}
|
||||
}, can_execute == null ? null : new Func<object, bool>(_ => can_execute()));
|
||||
|
||||
public static IDialogCommand export_csv(
|
||||
Func<string> get_file_content,
|
||||
string source_window_name) =>
|
||||
new AnonymousFileDialogCommand<object>(_ =>
|
||||
_create_save_file_dialog(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
|
||||
$"BTS_Spark_{source_window_name}_{DateTime.Now.ToString("yyyyMMdd_HHmm")}.csv",
|
||||
new[] { ("CSV (comma delimited)", new string[] { "csv" }) }
|
||||
),
|
||||
(_, file) => {
|
||||
try {
|
||||
File.WriteAllText(file, get_file_content.Invoke());
|
||||
} catch (Exception ex) {
|
||||
BTSMessageBox.Show($"Error exporting data to csv: {ex}.", "CSV Export Error", BTSMessageBoxButtons.OK, System.Windows.MessageBoxImage.Error, true, false, true);
|
||||
}
|
||||
},
|
||||
// we are always able to perform an export, even when no rows (it will just be a header)
|
||||
(_) => true);
|
||||
|
||||
static TDialog _create_file_dialog<TDialog>(string initial_dir = null, IReadOnlyList<(string name, string[] extensions)> filter_items = null) where TDialog : FileDialog, new() {
|
||||
var rv = new TDialog();
|
||||
if (!string.IsNullOrEmpty(initial_dir))
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.utils.csv {
|
||||
public static class CsvHelper {
|
||||
|
||||
public static string ToCsv<T>(this IEnumerable<T> items, OrderedDictionary properties = null) where T : class {
|
||||
var output = "";
|
||||
var delimiter = ",";
|
||||
if (properties == null) {
|
||||
properties = new OrderedDictionary();
|
||||
foreach (var pi in typeof(T).GetProperties()) {
|
||||
properties.Add(pi.Name, pi);
|
||||
}
|
||||
}
|
||||
|
||||
using (var sw = new StringWriter()) {
|
||||
var dict_values = properties.Values.OfType<PropertyInfo>();
|
||||
var header = dict_values.Select(n => n.Name).Aggregate((first, second) => first + delimiter + second);
|
||||
sw.WriteLine(header);
|
||||
foreach (var i in items) {
|
||||
var row = dict_values.Select(x => x.GetValue(i, null))
|
||||
.Select(x => x == null ? "null" : x.ToString())
|
||||
.Aggregate((first, second) => first + delimiter + second);
|
||||
sw.WriteLine(row);
|
||||
}
|
||||
output = sw.ToString();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public static OrderedDictionary create_ordering_maintainer(Type t, string[] property_names_in_order) {
|
||||
OrderedDictionary od = new OrderedDictionary();
|
||||
var props = t.GetProperties();
|
||||
foreach (var n in property_names_in_order) {
|
||||
var query = props.Where(pi => pi.Name == n);
|
||||
if (query.Any()) {
|
||||
var property_info = query.First();
|
||||
if (!od.Contains(property_info)) {
|
||||
od.Add(n, property_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return od;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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/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.cpp;metrics/metrics.cpp;metricsLine 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/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.cpp;metrics/metrics.cpp;metricsLine truncated
|
||||
@@ -69,4 +69,5 @@ message DynamicAlgos{
|
||||
optional bool enable_tas = 3 [default = false];
|
||||
optional bool enable_tascounter = 4 [default = false];
|
||||
optional bool enable_limit_cover = 5 [default = false];
|
||||
optional bool enable_onsignal = 6 [default = false];
|
||||
}
|
||||
@@ -211,6 +211,9 @@ inline bool gr_than(double p1, double p2, double tick_size) {
|
||||
return gr_than_eq(p1, p2, tick_size) &&
|
||||
!test_price_equality(p1, p2, tick_size);
|
||||
}
|
||||
inline bool test_price_equality(double price_1, double price_2) {
|
||||
return std::fabs(price_2 - price_1) < market_epsilon();
|
||||
}
|
||||
|
||||
} // namespace securities
|
||||
} // namespace bts
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/IntPrice.h"
|
||||
#include "lib/md/MDConsumer.h"
|
||||
@@ -10,12 +11,11 @@
|
||||
#include "lib/mercury/events/TimerEvent.h"
|
||||
#include "lib/orderman/events/BookUpdateEvent.h"
|
||||
#include "lib/orderman/events/MDRequestEvent.h"
|
||||
#include "lib/orderman/events/TickerUpdateEvent.h"
|
||||
#include "lib/orderman/events/StatusUpdateEvent.h"
|
||||
#include "lib/orderman/events/TickerUpdateEvent.h"
|
||||
#include "lib/tracing/PerfLog.h"
|
||||
#include "lib/tracing/bts_trace.h"
|
||||
#include "lib/utils/TimeStamp.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
|
||||
#include <date/date.h>
|
||||
|
||||
@@ -31,9 +31,9 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
public:
|
||||
MDSource(md::MDConsumer &consumer, metrics::MetricsContainer &stats,
|
||||
trace::PerfLog &log)
|
||||
: _handles(), _pending_handles(), _status_subs(), _md_consumer(consumer),
|
||||
_book_update(), _ticker_update(), _status_update(),
|
||||
_perf_log(log), _stats(stats), _loop_timer(), _counter(0) {
|
||||
: _handles(), _pending_handles(), _status_subs(), _md_consumer(consumer),
|
||||
_book_update(), _ticker_update(), _status_update(), _perf_log(log),
|
||||
_stats(stats), _loop_timer(), _counter(0) {
|
||||
|
||||
_trades_buffer.Reserve(1000);
|
||||
auto cme_channels = md::get_configured_cme_channels();
|
||||
@@ -67,9 +67,8 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
auto ticker_channel_cout_metric =
|
||||
std::make_shared<metrics::CounterMetric<int>>(
|
||||
"spark_mdsource_ticker_channel_count");
|
||||
auto status_count_metric =
|
||||
std::make_shared<metrics::CounterMetric<int>>(
|
||||
"spark_mdsource_status_count");
|
||||
auto status_count_metric = std::make_shared<metrics::CounterMetric<int>>(
|
||||
"spark_mdsource_status_count");
|
||||
_stats.publish(security_count_metric);
|
||||
_stats.publish(synthetic_count_metric);
|
||||
_stats.publish(ticker_channel_cout_metric);
|
||||
@@ -78,37 +77,40 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
_book_update = make_stream<BookUpdateEvent>();
|
||||
_ticker_update = make_stream<TickerUpdateEvent>();
|
||||
_status_update = make_stream<StatusUpdateEvent>();
|
||||
subscribe<MDRequestEvent>(
|
||||
[this, security_count_metric, synthetic_count_metric,
|
||||
ticker_channel_cout_metric, status_count_metric](auto const &update) {
|
||||
if (update.events) {
|
||||
for (auto const &ev : *update.events) {
|
||||
auto k = ev.security_key;
|
||||
subscribe<MDRequestEvent>([this, security_count_metric,
|
||||
synthetic_count_metric,
|
||||
ticker_channel_cout_metric,
|
||||
status_count_metric](auto const &update) {
|
||||
if (update.events) {
|
||||
for (auto const &ev : *update.events) {
|
||||
auto k = ev.security_key;
|
||||
|
||||
switch (ev.md_type) {
|
||||
case MDRequestType::BOOK:
|
||||
on_book_sub(k, *synthetic_count_metric);
|
||||
security_count_metric->set(_handles.size());
|
||||
break;
|
||||
case MDRequestType::TICKER:
|
||||
on_ticker_sub(k, *ticker_channel_cout_metric);
|
||||
break;
|
||||
case MDRequestType::STATUS:
|
||||
on_status_sub(k);
|
||||
status_count_metric->set(_status_subs.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
switch (ev.md_type) {
|
||||
case MDRequestType::BOOK:
|
||||
on_book_sub(k, *synthetic_count_metric);
|
||||
security_count_metric->set(_handles.size());
|
||||
break;
|
||||
case MDRequestType::TICKER:
|
||||
on_ticker_sub(k, *ticker_channel_cout_metric);
|
||||
break;
|
||||
case MDRequestType::STATUS:
|
||||
on_status_sub(k);
|
||||
status_count_metric->set(_status_subs.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
security_count_metric->set(_handles.size());
|
||||
}
|
||||
|
||||
private:
|
||||
void on_poll(trace::PerfLog& log) override {
|
||||
void on_poll(trace::PerfLog &log) override {
|
||||
|
||||
auto scoped_do_work = trace::log_scope(log, {}, "MDSource::on_poll", utils::TimeStamp::Interval::from_microseconds(2));
|
||||
auto scoped_do_work =
|
||||
trace::log_scope(log, {}, "MDSource::on_poll",
|
||||
utils::TimeStamp::Interval::from_microseconds(2));
|
||||
|
||||
// Book Updates
|
||||
Book book;
|
||||
@@ -158,11 +160,11 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
}
|
||||
|
||||
// Status updates
|
||||
for (auto& s : _status_subs) {
|
||||
for (auto &s : _status_subs) {
|
||||
auto status = s.handle.trading_status();
|
||||
if (status != s.status) {
|
||||
s.status = status;
|
||||
publish(_status_update, SecurityKey { s.handle.key() }, status);
|
||||
publish(_status_update, SecurityKey{s.handle.key()}, status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +173,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
_loop_timer->observe((start - stop).nanoseconds());
|
||||
|
||||
_counter += 1;
|
||||
if (_counter % (2<<20) == 0) {
|
||||
if (_counter % (2 << 20) == 0) {
|
||||
// Invoke periodic work every 2M calls on on_poll
|
||||
// This is approximately 20-30 seconds
|
||||
do_periodic_work();
|
||||
@@ -180,35 +182,39 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
|
||||
void do_periodic_work() {
|
||||
if (!_pending_handles.empty()) {
|
||||
for (auto iter = _pending_handles.begin(); iter != _pending_handles.end(); ++iter) {
|
||||
for (auto iter = _pending_handles.begin();
|
||||
iter != _pending_handles.end(); ++iter) {
|
||||
if (iter->handle->is_initialized()) {
|
||||
auto handle = *(iter->handle);
|
||||
LOG(OrderMan, Info, "Found md for {}. Pending handle removed.", handle.key());
|
||||
switch(iter->type) {
|
||||
case MDRequestType::BOOK:
|
||||
_handles.push_back({utils::TimeStamp(), handle,
|
||||
get_external_event_channel(handle.get_usersymbol()),
|
||||
std::make_unique<std::string>(securities::descriptor(
|
||||
handle.get_normalized_security()))});
|
||||
break;
|
||||
case MDRequestType::STATUS:
|
||||
for (auto& h : _status_subs) {
|
||||
// no need to reset status
|
||||
if (h.handle.key() == handle.key()) break;
|
||||
}
|
||||
_status_subs.push_back(
|
||||
StatusRecord{ handle, co2::TRADINGSTATUS_UNKNOWN });
|
||||
case MDRequestType::TICKER:
|
||||
LOG(OrderMan, Info, "Found md for {}. Pending handle removed.",
|
||||
handle.key());
|
||||
switch (iter->type) {
|
||||
case MDRequestType::BOOK:
|
||||
_handles.push_back(
|
||||
{utils::TimeStamp(), handle,
|
||||
get_external_event_channel(handle.get_usersymbol()),
|
||||
std::make_unique<std::string>(securities::descriptor(
|
||||
handle.get_normalized_security()))});
|
||||
break;
|
||||
case MDRequestType::STATUS:
|
||||
for (auto &h : _status_subs) {
|
||||
// no need to reset status
|
||||
if (h.handle.key() == handle.key())
|
||||
break;
|
||||
}
|
||||
_status_subs.push_back(
|
||||
StatusRecord{handle, co2::TRADINGSTATUS_UNKNOWN});
|
||||
case MDRequestType::TICKER:
|
||||
// We aren't currently monitoring for pending ticker requests
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_pending_handles.erase(
|
||||
std::remove_if(_pending_handles.begin(),
|
||||
_pending_handles.end(),
|
||||
[](const auto& h) { return h.handle->is_initialized(); }),
|
||||
_pending_handles.end());
|
||||
std::remove_if(
|
||||
_pending_handles.begin(), _pending_handles.end(),
|
||||
[](const auto &h) { return h.handle->is_initialized(); }),
|
||||
_pending_handles.end());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,18 +235,18 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
auto mhandle = _md_consumer.get_managed_handle(k);
|
||||
if (mhandle->is_initialized()) {
|
||||
auto handle = *mhandle;
|
||||
_handles.push_back({utils::TimeStamp(), handle,
|
||||
get_external_event_channel(handle.get_usersymbol()),
|
||||
std::make_unique<std::string>(securities::descriptor(
|
||||
handle.get_normalized_security()))});
|
||||
_handles.push_back(
|
||||
{utils::TimeStamp(), handle,
|
||||
get_external_event_channel(handle.get_usersymbol()),
|
||||
std::make_unique<std::string>(
|
||||
securities::descriptor(handle.get_normalized_security()))});
|
||||
if (handle.is_synthetic())
|
||||
synthetic_count_metric.increment();
|
||||
}
|
||||
else {
|
||||
// A pending handle is a handle for security we are reasonbly sure exists
|
||||
// but we haven't yet found live market data for.
|
||||
} else {
|
||||
// A pending handle is a handle for security we are reasonbly sure
|
||||
// exists but we haven't yet found live market data for.
|
||||
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
|
||||
_pending_handles.push_back({ mhandle, MDRequestType::BOOK });
|
||||
_pending_handles.push_back({mhandle, MDRequestType::BOOK});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,8 +259,17 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
|
||||
void on_ticker_sub(SecurityKey sk,
|
||||
metrics::CounterMetric<int> &channel_count_metric) {
|
||||
auto handle = _md_consumer.get_managed_handle(sk.value());
|
||||
|
||||
auto user_symbol = _md_consumer.get_handle(sk.value()).get_usersymbol();
|
||||
if (!handle->is_initialized()) {
|
||||
LOG(OrderMan, Warn,
|
||||
"Received ticker subscription for security {} which does not have "
|
||||
"valid market data. Ignoring",
|
||||
sk.value());
|
||||
return;
|
||||
}
|
||||
|
||||
auto user_symbol = handle->get_usersymbol();
|
||||
for (auto &ch : _channels) {
|
||||
if (ch.user_symbol == user_symbol) {
|
||||
auto it =
|
||||
@@ -262,7 +277,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
[sk](auto const &ev) { return ev.key() == sk; });
|
||||
if (it == ch.securities.end()) {
|
||||
// first time we are asked for this security, we will
|
||||
// publish the trades for it next time next round
|
||||
// publish the trades for it next time around
|
||||
LOG(OrderMan, Warn,
|
||||
"Starting to publish ticker updates on user symbol {} for "
|
||||
"security key: {} (sec count for the symbol: {} )",
|
||||
@@ -294,7 +309,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
|
||||
void on_status_sub(SecurityKey sk) {
|
||||
auto k = sk.value();
|
||||
for (auto& h : _status_subs) {
|
||||
for (auto &h : _status_subs) {
|
||||
if (h.handle.key() == k) {
|
||||
// reset status so it get resent next on_poll
|
||||
h.status = co2::TRADINGSTATUS_UNKNOWN;
|
||||
@@ -303,11 +318,11 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
}
|
||||
auto handle = _md_consumer.get_managed_handle(k);
|
||||
if (handle->is_initialized()) {
|
||||
_status_subs.push_back(StatusRecord{ *handle, co2::TRADINGSTATUS_UNKNOWN });
|
||||
}
|
||||
else {
|
||||
_status_subs.push_back(
|
||||
StatusRecord{*handle, co2::TRADINGSTATUS_UNKNOWN});
|
||||
} else {
|
||||
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
|
||||
_pending_handles.push_back({ handle, MDRequestType::STATUS });
|
||||
_pending_handles.push_back({handle, MDRequestType::STATUS});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +352,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
|
||||
md::MDConsumerHandle handle;
|
||||
co2::TradingStatus status;
|
||||
};
|
||||
boost::container::small_vector<StatusRecord,20> _status_subs;
|
||||
boost::container::small_vector<StatusRecord, 20> _status_subs;
|
||||
|
||||
md::MDConsumer _md_consumer;
|
||||
mercury::Stream<BookUpdateEvent> _book_update;
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/OnSignalAlgo.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/orderman/actors/SODPosition.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/util/time_util.h>
|
||||
#include <boost/tokenizer.hpp>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
using namespace api::orderman;
|
||||
using namespace google::protobuf;
|
||||
using namespace boost;
|
||||
|
||||
struct OnSignalLegData{
|
||||
const SecurityKey sec_key;
|
||||
ChildTemplate* order_template = nullptr;
|
||||
const Price price;
|
||||
const Quantity quantity;
|
||||
const api::Side side;
|
||||
OrderKey order_key;
|
||||
ChildState order_state;
|
||||
};
|
||||
|
||||
enum class OnSignalAlgo::State { WAITING_FOR_SIGNAL, WORKING, DONE };
|
||||
|
||||
struct OnSignalAlgoParams{
|
||||
std::optional<SecurityKey> signal_key_1 = std::nullopt;
|
||||
std::optional<google::protobuf::Timestamp> time = std::nullopt;
|
||||
bool preopen = false;
|
||||
bool open = false;
|
||||
std::vector<OnSignalLegData> legs;
|
||||
OnSignalLegData* find_leg_from_ord_key (OrderKey ord_key){
|
||||
for (auto& l : legs)
|
||||
if(l.order_key == ord_key)
|
||||
return &l;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct OnSignalAlgo::PImpl {
|
||||
IAlgoServices* algo_services = nullptr;
|
||||
State state = State::WAITING_FOR_SIGNAL;
|
||||
OnSignalAlgoParams algo_params;
|
||||
PImpl(){
|
||||
}
|
||||
};
|
||||
|
||||
ChildTemplate* _get_template (IBeforeLaunchAlgoServices& serv,
|
||||
const CustomOrderDefinition& cod,
|
||||
SecurityKey key,
|
||||
api::Side side){
|
||||
OrderDefinition rv = serv.definition();
|
||||
rv.set_security_key(key.value());
|
||||
rv.set_side(side);
|
||||
rv.mutable_custom_def()->CopyFrom(cod);
|
||||
return &(serv.make_child_template(rv));
|
||||
}
|
||||
|
||||
bool _convert_params (IBeforeLaunchAlgoServices& serv,
|
||||
const CustomOrderDefinition& cod,
|
||||
OnSignalAlgoParams& out_p){
|
||||
const DynamicOrderDefinition& def = cod.dynamic();
|
||||
const auto& params = def.order_params();
|
||||
auto hh = get_if<uint32_t>(params, "Time Hours");
|
||||
auto mm = get_if<uint32_t>(params, "Time Minutes");
|
||||
auto ss = get_if<uint32_t>(params, "Time Seconds");
|
||||
if(hh.has_value() && hh.value() <= 23 &&
|
||||
mm.has_value() && mm.value() <= 59 &&
|
||||
ss.has_value() && ss.value() <= 59)
|
||||
out_p.time = SODPosition::today_local_time_to_utc_timestamp (
|
||||
hh.value(),mm.value(),ss.value(),true,false);
|
||||
auto preopen = get_if<bool>(params, "Preopen");
|
||||
out_p.preopen = preopen.has_value() ? preopen.value() : false;
|
||||
auto open = get_if<bool>(params, "Open");
|
||||
out_p.open = open.has_value() ? open.value() : false;
|
||||
auto k1 = get_if<uint32_t>(params, "Signal Contract 1");
|
||||
if(k1.has_value())
|
||||
out_p.signal_key_1 = SecurityKey(k1.value());
|
||||
for (int i=0;;i++){
|
||||
auto idx = std::to_string(i+1);
|
||||
auto contract = get_if<uint32_t>(params, "Order " + idx + " Contract");
|
||||
auto custom_ord = get_if<CustomOrderDefinition>(params, "Order " + idx);
|
||||
auto price = get_if<double>(params, "Order " + idx + " Price");
|
||||
auto qty = get_if<int32_t>(params, "Order " + idx + " Quantity");
|
||||
if(contract.has_value() && custom_ord.has_value() && price.has_value() &&
|
||||
qty.has_value() && qty.value() != 0){
|
||||
auto side = qty > 0 ? api::Side::SIDE_BUY : api::Side::SIDE_SELL;
|
||||
uint32_t eff_qty = std::abs(qty.value());
|
||||
auto key = SecurityKey(contract.value());
|
||||
ChildTemplate* child_template = _get_template(serv,custom_ord.value(),key,side);
|
||||
OnSignalLegData leg =
|
||||
{
|
||||
key,
|
||||
child_template,
|
||||
Price(price.value()),
|
||||
Quantity(eff_qty),
|
||||
side,
|
||||
OrderKey(0),
|
||||
ChildState()
|
||||
};
|
||||
out_p.legs.push_back(leg);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
bool valid = out_p.legs.size() > 0 &&
|
||||
(out_p.time.has_value() ||
|
||||
(out_p.signal_key_1.has_value() && (out_p.preopen || out_p.open)));
|
||||
return valid;
|
||||
}
|
||||
|
||||
OnSignalAlgo::OnSignalAlgo() : me (new PImpl()){}
|
||||
OnSignalAlgo::~OnSignalAlgo() {}
|
||||
|
||||
OrderOpResult OnSignalAlgo::do_before_launch(IBeforeLaunchAlgoServices& serv){
|
||||
auto& def = serv.definition();
|
||||
if (def.has_custom_def() &&
|
||||
def.custom_def().has_dynamic() &&
|
||||
_convert_params(serv,def.custom_def(),me->algo_params))
|
||||
return {};
|
||||
return {OrderError::ORDER_ERROR_INVALID_PARAMETER,
|
||||
std::string("Parameters are not well formed")};
|
||||
}
|
||||
void OnSignalAlgo::do_launch(IAlgoServices& algo_services){
|
||||
me->algo_services = &algo_services;
|
||||
me->algo_services->subscribe_timer();
|
||||
if(me->algo_params.signal_key_1.has_value())
|
||||
me->algo_services->subscribe_status(me->algo_params.signal_key_1.value());
|
||||
}
|
||||
void OnSignalAlgo::do_launch_attached(IAlgoServices& algo_services,
|
||||
OrderKey,
|
||||
ChildState){
|
||||
me->algo_services = &algo_services;
|
||||
_cxl_and_end(true, "OnSignal Algo can not be attached to working orders");
|
||||
}
|
||||
void OnSignalAlgo::do_update(api::orderman::Update const &){
|
||||
LOG(OrderMan, Warn, "OnSignalAlgo::do_update order:{} state:{} update not allowed, please cxl and re-enter ",
|
||||
me->algo_services->order_key().value(),
|
||||
static_cast<int>(me->state));
|
||||
}
|
||||
void OnSignalAlgo::on_child_change(OrderKey key, const ChildState& st){
|
||||
LOG(OrderMan, Info,
|
||||
"OnSignalAlgo::on_child_change key:{} child_key:{} state:{} status:{} "
|
||||
"total_fills:{} last_price:{}",
|
||||
me->algo_services->order_key(),
|
||||
key,
|
||||
static_cast<int>(me->state),
|
||||
static_cast<int>(st.status),
|
||||
st.total_fills,
|
||||
st.last_price);
|
||||
switch (me->state){
|
||||
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
|
||||
LOG(OrderMan, Error,
|
||||
"OnSignalAlgo::on_child_change key:{} in WAITING_FOR_SIGNAL",
|
||||
me->algo_services->order_key());
|
||||
break;
|
||||
case (OnSignalAlgo::State::WORKING):
|
||||
{
|
||||
auto ld_ptr = me->algo_params.find_leg_from_ord_key(key);
|
||||
if (ld_ptr != nullptr)
|
||||
ld_ptr->order_state = st;
|
||||
}
|
||||
for (const auto& l : me->algo_params.legs)
|
||||
if(l.order_state.status == ChildStatus::WORKING)
|
||||
return;
|
||||
_cxl_and_end(false,"");
|
||||
break;
|
||||
case (OnSignalAlgo::State::DONE):
|
||||
break;
|
||||
}
|
||||
}
|
||||
void OnSignalAlgo::on_status(SecurityKey key, co2::TradingStatus s){
|
||||
if(me->algo_params.signal_key_1.has_value() && me->algo_params.signal_key_1 == key)
|
||||
switch (me->state){
|
||||
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
|
||||
if(me->algo_params.preopen && s == co2::TradingStatus::TRADINGSTATUS_PRE_OPEN)
|
||||
_fire_orders();
|
||||
else if(me->algo_params.open && s == co2::TradingStatus::TRADINGSTATUS_NORMAL)
|
||||
_fire_orders();
|
||||
break;
|
||||
case (OnSignalAlgo::State::WORKING):
|
||||
case (OnSignalAlgo::State::DONE):
|
||||
break;
|
||||
}
|
||||
}
|
||||
void OnSignalAlgo::on_timer(){
|
||||
if(me->algo_params.time.has_value())
|
||||
switch (me->state){
|
||||
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
|
||||
{
|
||||
auto thresh = me->algo_params.time.value();
|
||||
auto now = util::TimeUtil::GetCurrentTime();
|
||||
auto diff = util::TimeUtil::TimestampToMicroseconds(now) -
|
||||
util::TimeUtil::TimestampToMicroseconds(thresh);
|
||||
if(diff >= 0)
|
||||
_fire_orders();
|
||||
break;
|
||||
}
|
||||
case (OnSignalAlgo::State::WORKING):
|
||||
case (OnSignalAlgo::State::DONE):
|
||||
break;
|
||||
}
|
||||
}
|
||||
void OnSignalAlgo::_fire_orders(){
|
||||
for (auto& l : me->algo_params.legs)
|
||||
l.order_key = me->algo_services->launch_child(*l.order_template,l.price,l.quantity);
|
||||
_switch_state(State::WORKING);
|
||||
}
|
||||
void OnSignalAlgo::_cxl_and_end (bool is_error, const std::string& err_msg){
|
||||
if(is_error)
|
||||
me->algo_services->terminate_error(err_msg);
|
||||
else
|
||||
me->algo_services->complete();
|
||||
_switch_state(OnSignalAlgo::State::DONE);
|
||||
}
|
||||
void OnSignalAlgo::_switch_state (State new_state){
|
||||
switch (new_state){
|
||||
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
|
||||
bts_assert(false);
|
||||
break;
|
||||
case (OnSignalAlgo::State::WORKING):
|
||||
bts_assert(me->state == OnSignalAlgo::State::WAITING_FOR_SIGNAL);
|
||||
break;
|
||||
case (OnSignalAlgo::State::DONE):
|
||||
bts_assert(me->state != OnSignalAlgo::State::DONE);
|
||||
break;
|
||||
}
|
||||
LOG(OrderMan, Info,"OnSignalAlgo::_switch_state key:{} {}->{} ",
|
||||
me->algo_services->order_key(),
|
||||
static_cast<int>(me->state),
|
||||
static_cast<int>(new_state));
|
||||
me->state = new_state;
|
||||
}
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class ChildTemplate;
|
||||
struct LegData;
|
||||
|
||||
class OnSignalAlgo : public IAlgoOrder {
|
||||
public:
|
||||
OnSignalAlgo();
|
||||
~OnSignalAlgo();
|
||||
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_status(SecurityKey, co2::TradingStatus) override;
|
||||
void on_timer() override;
|
||||
|
||||
private:
|
||||
enum class State;
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
private:
|
||||
IAlgoServices& _algo() const ;
|
||||
// acts on md, cognizant of the state
|
||||
void _switch_state (State new_state);
|
||||
void _fire_orders();
|
||||
void _cxl_and_end (bool is_error, const std::string& err_msg);
|
||||
};
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -16,7 +16,7 @@ namespace orderman {
|
||||
|
||||
using namespace api::orderman;
|
||||
|
||||
enum class PyramidAlgo::Status { WAITING_FOR_MD, WAITING_FOR_MD_ATTACH, WORKING, DONE };
|
||||
enum class PyramidAlgo::Status { WAITING_FOR_MD, WAITING_FOR_MD_ATTACH, WORKING, WAITING_FOR_CXL_ACK, DONE };
|
||||
enum class PyramidAlgo::ExitPath { CANCEL, ERR, COMPLETE };
|
||||
|
||||
struct LegData{
|
||||
@@ -192,6 +192,7 @@ void PyramidAlgo::do_update(const api::orderman::Update& update) {
|
||||
"Order cancelled (Cover prices not met)");
|
||||
break;
|
||||
}
|
||||
case PyramidAlgo::Status::WAITING_FOR_CXL_ACK:
|
||||
case PyramidAlgo::Status::DONE:
|
||||
break;
|
||||
}
|
||||
@@ -215,6 +216,7 @@ IAlgoOrder::OnCancelResponse PyramidAlgo::on_cancel(){
|
||||
case PyramidAlgo::Status::WORKING:
|
||||
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,"Algo cancelled");
|
||||
break;
|
||||
case (PyramidAlgo::Status::WAITING_FOR_CXL_ACK):
|
||||
case PyramidAlgo::Status::DONE:
|
||||
break;
|
||||
}
|
||||
@@ -242,6 +244,7 @@ void PyramidAlgo::on_child_change(OrderKey key, const ChildState &st) {
|
||||
_check_for_completion();
|
||||
break;
|
||||
case PyramidAlgo::Status::WORKING:
|
||||
case (PyramidAlgo::Status::WAITING_FOR_CXL_ACK):
|
||||
leg->order_state = st;
|
||||
_algo().update_fill_state(st.total_fills, st.avg_price);
|
||||
_send_hedges(st.last_fills);
|
||||
@@ -388,6 +391,7 @@ void PyramidAlgo::_analyze_md_and_act(){
|
||||
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,
|
||||
"Order cancelled (Cover prices not met)");
|
||||
break;
|
||||
case PyramidAlgo::Status::WAITING_FOR_CXL_ACK:
|
||||
case PyramidAlgo::Status::DONE:
|
||||
break;
|
||||
}
|
||||
@@ -405,14 +409,19 @@ void PyramidAlgo::_switch_state(Status new_state){
|
||||
bts_assert(me->state == PyramidAlgo::Status::WAITING_FOR_MD ||
|
||||
me->state == PyramidAlgo::Status::WAITING_FOR_MD_ATTACH);
|
||||
break;
|
||||
case (PyramidAlgo::Status::WAITING_FOR_CXL_ACK):
|
||||
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);
|
||||
// when the child order enters a terminal state, while the algo
|
||||
// is in a working state (external cxl, target reached)
|
||||
me->state == PyramidAlgo::Status::WORKING ||
|
||||
// the usual controlled cxl, ie working->waiting_for_cxl_ack->done
|
||||
me->state == PyramidAlgo::Status::WAITING_FOR_CXL_ACK);
|
||||
break;
|
||||
}
|
||||
LOG(OrderMan, Info,"PyramidAlgo::_switch_state key:{} {}->{} ",
|
||||
@@ -494,8 +503,8 @@ void PyramidAlgo::_check_for_completion(){
|
||||
break;
|
||||
case (ChildStatus::CANCELED):
|
||||
_cxl_and_end(PyramidAlgo::ExitPath::ERR,
|
||||
"Top spread unexpectedly cancelled");
|
||||
break;
|
||||
"Top spread cancelled");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,8 +516,11 @@ void PyramidAlgo::_cxl_and_end(PyramidAlgo::ExitPath exit_path,
|
||||
if(top_ord_state.status == ChildStatus::WORKING){
|
||||
if(detach_top)
|
||||
_algo().detach_child(me->legs[0].order_key);
|
||||
else
|
||||
else{
|
||||
_algo().cancel_child(me->legs[0].order_key);
|
||||
_switch_state(PyramidAlgo::Status::WAITING_FOR_CXL_ACK);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch(exit_path){
|
||||
@@ -524,6 +536,7 @@ void PyramidAlgo::_cxl_and_end(PyramidAlgo::ExitPath exit_path,
|
||||
}
|
||||
_switch_state(PyramidAlgo::Status::DONE);
|
||||
}
|
||||
|
||||
// true the out qty is changed it be below
|
||||
// the filled qty
|
||||
bool PyramidAlgo::_target_reached() const{
|
||||
@@ -538,6 +551,14 @@ bool PyramidAlgo::_md_recvd() const{
|
||||
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;
|
||||
@@ -545,10 +566,17 @@ bool PyramidAlgo::_working_state_met() const{
|
||||
if (i++ > 0)
|
||||
cover_sum += l.opp_md()[0].price();
|
||||
bool rv = !os::is_nan(cover_sum) &&
|
||||
((me->side == api::Side::SIDE_BUY &&
|
||||
me->out_price.value() <= cover_sum) ||
|
||||
(me->side == api::Side::SIDE_SELL &&
|
||||
me->out_price.value() >= 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
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "PyramidAlgo.h"
|
||||
#include "TASAlgo.h"
|
||||
#include "TASCounterAlgo.h"
|
||||
#include "OnSignalAlgo.h"
|
||||
|
||||
using namespace bts;
|
||||
using namespace api::orderman;
|
||||
@@ -200,7 +201,7 @@ extern "C" void init_tas_plugin(bts::SparkServices& srv) {
|
||||
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?");
|
||||
*tas_desc.add_param_desc() = param_bool("Order " + idx + " Buy?");
|
||||
}
|
||||
srv.register_dynamic_algo_order(tas_desc, []() { return std::make_unique<orderman::TASAlgo>(); });
|
||||
}
|
||||
@@ -212,6 +213,27 @@ extern "C" void init_limit_cover(bts::SparkServices & srv) {
|
||||
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())
|
||||
@@ -224,6 +246,8 @@ extern "C" void init_spark_plugin(bts::SparkServices &srv) {
|
||||
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);
|
||||
}
|
||||
|
||||
}}}
|
||||
@@ -26,17 +26,21 @@ enum class TASCounterAlgo::Status { WORKING, DONE };
|
||||
// (the vwap of the TAS contracts is not used, but for uniformity is kept in the
|
||||
// the same structure)
|
||||
struct ContractMarketData{
|
||||
private:
|
||||
uint32_t tot_vol = 0;
|
||||
double running_sum = 0;
|
||||
public:
|
||||
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(){
|
||||
double vwap() const{
|
||||
return tot_vol == 0 ? os::nan() : running_sum/tot_vol;
|
||||
}
|
||||
uint32_t traded_qty() const {return tot_vol;}
|
||||
double traded_value() const {return running_sum;}
|
||||
};
|
||||
// the input for the t1 front, second and third orders, as well as the template
|
||||
// and working order key are kept here
|
||||
@@ -100,6 +104,47 @@ bool trade_within_time_win (const Timestamp&, const Timestamp&,
|
||||
const TickerUpdateEvent::Trade& t);
|
||||
bool now_within_time_win (const Timestamp&, const Timestamp&);
|
||||
|
||||
double compute_t1_front_settle (const AlgoData& d){
|
||||
double rv = d.t1_front.vwap();
|
||||
rv = round_to_nearest(rv,d.tick_size);
|
||||
return rv;
|
||||
}
|
||||
|
||||
double compute_t1_second_settle (const AlgoData& d){
|
||||
double p_t1_front = compute_t1_front_settle(d);
|
||||
double part_1 = d.t1_second.traded_value();
|
||||
double part_2 = d.t2_front.traded_value() -
|
||||
(d.t2_front.traded_qty() * p_t1_front);
|
||||
uint32_t traded_qty_sum = d.t2_front.traded_qty() +
|
||||
d.t1_second.traded_qty();
|
||||
double rv = traded_qty_sum == 0 ? os::nan() :
|
||||
(part_1+part_2)/traded_qty_sum;
|
||||
LOG(OrderMan, Diag, "TASCounterAlgo::compute_t1_second_settle "
|
||||
"p_t1_front:{} part_1:{} part_2:{} traded_qty_sum:{} rv:{}",
|
||||
p_t1_front, part_1, part_2, traded_qty_sum, rv);
|
||||
return round_to_nearest(rv,d.tick_size);
|
||||
}
|
||||
|
||||
double compute_t1_third_settle (const AlgoData& d){
|
||||
double p_t1_front = compute_t1_front_settle(d);
|
||||
double p_t1_second = compute_t1_second_settle(d);
|
||||
double part_1 = d.t1_third.traded_value();
|
||||
double part_2 = d.t2_second.traded_value() -
|
||||
(d.t2_second.traded_qty()*p_t1_second);
|
||||
double part_3 = d.t3_front.traded_value() -
|
||||
(d.t3_front.traded_qty()*p_t1_front) -
|
||||
(d.t3_front.traded_qty()*p_t1_second);
|
||||
double traded_qty_sum = d.t3_front.traded_qty() +
|
||||
d.t2_second.traded_qty() +
|
||||
d.t1_third.traded_qty();
|
||||
double rv = traded_qty_sum == 0 ? os::nan() :
|
||||
(part_1+part_2+part_3)/traded_qty_sum;
|
||||
LOG(OrderMan, Diag, "TASCounterAlgo::compute_t1_third_settle "
|
||||
"p_t1_front:{} p_t1_second:{} part_1:{} part_2:{} part_3:{} "
|
||||
"traded_qty_sum:{} rv:{}",
|
||||
p_t1_front, p_t1_second, part_1, part_2, part_3, traded_qty_sum, rv);
|
||||
return round_to_nearest(rv,d.tick_size);
|
||||
}
|
||||
|
||||
TASCounterAlgo::TASCounterAlgo() : me(new PImpl()) {}
|
||||
TASCounterAlgo::~TASCounterAlgo() {}
|
||||
@@ -214,17 +259,9 @@ void TASCounterAlgo::_cxl_and_end (bool is_error, const std::string& err_msg){
|
||||
|
||||
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);
|
||||
d.t1_front.estimated_settle = compute_t1_front_settle(d);
|
||||
d.t1_second.estimated_settle = compute_t1_second_settle(d);
|
||||
d.t1_third.estimated_settle = compute_t1_third_settle(d);
|
||||
}
|
||||
|
||||
// the decision to send an order can be caused by
|
||||
|
||||
Reference in new issue
Block a user