Compare commits
107
Commits
vwap_ilink3
...
2.16.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fab3334b1e | ||
|
|
7239dd3ade | ||
|
|
dd4c8eb649 | ||
|
|
76a29bdc8a | ||
|
|
06d708f3ea | ||
|
|
e2f70cbc19 | ||
|
|
9d54e47a03 | ||
|
|
faccdf581e | ||
|
|
1a64714a54 | ||
|
|
3ba6792a85 | ||
|
|
c32a7f1d62 | ||
|
|
10e75f30e6 | ||
|
|
20eac19c4f | ||
|
|
8c0765a2c7 | ||
|
|
7a1f88e813 | ||
|
|
3420f14c8a | ||
|
|
86277dfbfe | ||
|
|
4839e464c6 | ||
|
|
dc2ad071a9 | ||
|
|
65e49992f9 | ||
|
|
afbb9ae92d | ||
|
|
ff088142d9 | ||
|
|
93b7cf346b | ||
|
|
f0edeadd50 | ||
|
|
8ee9b84488 | ||
|
|
e3648f5373 | ||
|
|
facd3a9d81 | ||
|
|
84969cb481 | ||
|
|
6d521e77db | ||
|
|
82c49f8f49 | ||
|
|
e074309da1 | ||
|
|
2d622a5f39 | ||
|
|
d51d20e84c | ||
|
|
2ccdef9dc5 | ||
|
|
42e62a30bf | ||
|
|
c233c607fa | ||
|
|
462cb930f7 | ||
|
|
2c0f920422 | ||
|
|
5ee26065ca | ||
|
|
550a4e8d50 | ||
|
|
99b8221efc | ||
|
|
19e74c30f8 | ||
|
|
26693a4c98 | ||
|
|
d8bdb69a7c | ||
|
|
6d357a74e1 | ||
|
|
73f4e4814c | ||
|
|
312bc4be15 | ||
|
|
a0c3ba449a | ||
|
|
d5a2a7f3f0 | ||
|
|
eda8c94def | ||
|
|
9a8e80e149 | ||
|
|
6a7e3d739d | ||
|
|
e6b8274813 | ||
|
|
9783520c62 | ||
|
|
7e7f0bc37f | ||
|
|
b9e2ecdffa | ||
|
|
f1c021a474 | ||
|
|
6fda54ffa1 | ||
|
|
bdb0adc16c | ||
|
|
4b19b0ea14 | ||
|
|
fab1b4dd0f | ||
|
|
f3ed3075cf | ||
|
|
742af36b93 | ||
|
|
c573b5fbb2 | ||
|
|
2d6d4af594 | ||
|
|
2dcfad979c | ||
|
|
ba7e35649e | ||
|
|
cda4a870b7 | ||
|
|
b3a8564365 | ||
|
|
1e0114a4cf | ||
|
|
3a6e95ef05 | ||
|
|
6875263743 | ||
|
|
eac828f323 | ||
|
|
1635d6f649 | ||
|
|
2da535e9ff | ||
|
|
a98be44a40 | ||
|
|
6210ac2ee6 | ||
|
|
cfebab0488 | ||
|
|
eacd766359 | ||
|
|
8fb8f8ff58 | ||
|
|
f4b22816a2 | ||
|
|
1029a464d7 | ||
|
|
fd8b8e03ea | ||
|
|
41cf66a943 | ||
|
|
1c57c9a012 | ||
|
|
8ccab66120 | ||
|
|
e2b2f7ec9c | ||
|
|
b3c897bf0f | ||
|
|
87c9f54c8b | ||
|
|
df56b76ab6 | ||
|
|
28ff13db11 | ||
|
|
2957075769 | ||
|
|
a6477824a8 | ||
|
|
17431f5ac0 | ||
|
|
8708f02fe0 | ||
|
|
752fb2bc4a | ||
|
|
6efb78ecf9 | ||
|
|
75c1970aef | ||
|
|
c2583518d7 | ||
|
|
8792871387 | ||
|
|
68a593af7b | ||
|
|
7c29a3d1d8 | ||
|
|
90bc20575a | ||
|
|
cb268f505a | ||
|
|
dfd93b836b | ||
|
|
28e9c66675 | ||
|
|
c0520d6a31 |
No files matched your search
@@ -35,3 +35,4 @@ trace_data/
|
||||
.ionide/*
|
||||
/.ignore
|
||||
/build
|
||||
.clangd/
|
||||
Vendored
+24
@@ -1,6 +1,30 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Spark Local",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"run",
|
||||
// "test",
|
||||
// "-a",
|
||||
// "[ilink3]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceRoot}/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SparkVM Launch",
|
||||
"type": "cppdbg",
|
||||
|
||||
@@ -68,12 +68,21 @@ To update your Spark SDK simply download an unzip a new [spark_sdk.zip](https://
|
||||
|
||||
To preserve the old image so you can go back to it later run `docker tag spark_sdk spark_sdk:your_custom_tag` before loading the new SDK image.
|
||||
|
||||
<!--
|
||||
Then stop, remove and restart spark_sdk.
|
||||
### Re-build the Dev Container
|
||||
|
||||
If you are using Visual Studio Code you also need to rebuild the dev-container so it picks up the new image.
|
||||
|
||||
#### Inside VSCode
|
||||
To re-build from inside Visual Studio Code. Click the 'Remote Explorer' icon on the left hand side.
|
||||
You should see your spark_sdk project listed under the Containers section. Right click on it and select **rebuild-container**.
|
||||
|
||||
#### Command Line
|
||||
To re-build from the command line run `docker ps -a` and find the row with the spark_sdk image.
|
||||
```
|
||||
c:\bts> docker stop spark_sdk
|
||||
c:\bts> docker rm spark_sdk
|
||||
c:\bts> docker run -d -v %cd%:/sdk --name spark_sdk bluetradingsytems/spark_sdk
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
54d96cfa3888 spark_sdk "/bin/sh -c 'echo Co…" About a minute ago Up About a minute 127.0.0.1:9000-9001->9000-9001/tcp quizzical_lichterman
|
||||
```
|
||||
Then run `docker rm 54d96cfa3888` (use your container id instead of 54d96cfa3888) or `docker rm quizzical_lichterman` (use your container name).
|
||||
|
||||
|
||||
``` -->
|
||||
|
||||
@@ -4,6 +4,20 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.16.0
|
||||
### Bug Fixes
|
||||
- Solved an issue that could result in incorrect ladder levels
|
||||
- Solved an issue that could lead to incorrect routing of orders in the event of certain MSGW changes
|
||||
### API Changes
|
||||
- Add C# client side API samples for order entry, position monitoring and market data observation
|
||||
|
||||
## 2.15.10
|
||||
### Bug Fixes
|
||||
- Solved an issue that could cause the user interface to fail when playing some alert sounds
|
||||
### API Changes
|
||||
- Added settlement and position information to the API
|
||||
- Added the ability for user algos to report custom state to the user interface
|
||||
- Added the ability for algos to discover all active securities
|
||||
## 2.15.3
|
||||
### Bug Fixes
|
||||
- Fixed a bug where some CME currency spreads exhibited incorrect tick values
|
||||
|
||||
+1
-1
Submodule docker/linux updated: 0df20ec20a...0afd5e92de.
+1
-1
Submodule externals updated: c695e96940...38945c5f49.
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "2.1.508",
|
||||
"rollForward": "latestMajor"
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,10 @@
|
||||
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(VCTargetsPath)'==''">
|
||||
<VCTargetsPath>C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\VC\VCTargets</VCTargetsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -534,13 +534,44 @@ namespace mbts.spark {
|
||||
_size_from_row(row, _price_list, _asks, _vm.security.ContractSpec);
|
||||
|
||||
uint _volume_from_row(int row) {
|
||||
if (_price_list.SafeTryGetAt(row, out var price)) {
|
||||
var vapls = _volume_container.read_new_value();
|
||||
if (vapls != null) {
|
||||
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
|
||||
}
|
||||
const int max_tries = 3;
|
||||
var errors = new List<string>(0);
|
||||
int num_tries = 0;
|
||||
for (; num_tries < max_tries; num_tries++) {
|
||||
try {
|
||||
var rv = impl();
|
||||
if (num_tries > 0) {
|
||||
log_errors(true);
|
||||
}
|
||||
return rv;
|
||||
} catch (Exception ex) {
|
||||
errors.Add(ex.ToString());
|
||||
}
|
||||
}
|
||||
log_errors(false);
|
||||
return 0;
|
||||
|
||||
void log_errors(bool succeeded) {
|
||||
// Only log distinct errors to avoid useless noise.
|
||||
// It's likely that the exception will always be the same (IndexOutOfRange),
|
||||
// however, this bug is not fully understood, so assume nothing.
|
||||
var error_msg = string.Join("; ", errors.Distinct());
|
||||
if (succeeded) {
|
||||
Log.Ui.error($"Ladder '{security}' got volume for row at index '{row}' after {num_tries + 1} attempts (price_list array length is {_price_list.Length}). The following exceptions were observed: {error_msg}");
|
||||
} else {
|
||||
Log.Ui.error($"Ladder '{security}' failed to get volume for row at index '{row}' after {max_tries} attempts (price_list array length is {_price_list.Length}): {error_msg}");
|
||||
}
|
||||
}
|
||||
|
||||
uint impl() {
|
||||
if (_price_list.SafeTryGetAt(row, out var price)) {
|
||||
var vapls = _volume_container.read_new_value();
|
||||
if (vapls != null) {
|
||||
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
|
||||
}
|
||||
}
|
||||
return 0u;
|
||||
}
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace mbts.spark {
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurityRootPair order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
|
||||
_owner = owner;
|
||||
this.order_tuple = order_tuple;
|
||||
is_visible = !is_child;
|
||||
_is_expanded = is_expanded;
|
||||
prospective_quantity = this.quantity;
|
||||
prospective_price = this.price;
|
||||
@@ -113,7 +112,6 @@ namespace mbts.spark {
|
||||
// Tree
|
||||
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
|
||||
public bool is_child => order.has_parent();
|
||||
public bool is_visible { get; set; }
|
||||
public bool is_expanded {
|
||||
get => _is_expanded;
|
||||
set {
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace mbts.spark {
|
||||
readonly OrderParamsAggregator _selected_params_mon;
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _selected_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly RightPanelModel _right_panel = new RightPanelModel();
|
||||
readonly CustomStateSynchronizer _custom_state_sync;
|
||||
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
|
||||
double
|
||||
_raw_right_panel_width = OrderBookStartupData.Default.right_panel_width,
|
||||
@@ -124,6 +125,8 @@ namespace mbts.spark {
|
||||
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
|
||||
_current_row = backing<OrderBookRowVM>(nameof(current_row));
|
||||
_columns.filter_changed += _on_filter_changed;
|
||||
_custom_state_sync = new CustomStateSynchronizer(this);
|
||||
// commands
|
||||
collapse_all_rows_command = new DelegateCommand(_row_collection.collapse_all);
|
||||
expand_all_rows_command = new DelegateCommand(_row_collection.expand_all);
|
||||
var modify_selected_orders_async_cmd = new AsyncCommand(_modify_selected_orders_async, () => _selected_params_mon.all_active && order_mod_vm.is_dirty);
|
||||
@@ -188,6 +191,7 @@ namespace mbts.spark {
|
||||
security_plurality = p;
|
||||
RaisePropertyChanged(nameof(security_plurality));
|
||||
}),
|
||||
_current_row.Subscribe(_ => _custom_state_sync.sync()),
|
||||
orders_source.OnDispatcher().Subscribe(t => {
|
||||
if (_selected_rows.Count == 0) {
|
||||
_cached_orders = t.state;
|
||||
@@ -207,6 +211,8 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
_custom_state_sync.sync(true);
|
||||
orders_changed?.Invoke();
|
||||
}),
|
||||
model.on_orders_can_attach.OnDispatcher().Subscribe(table => {
|
||||
_cached_attachable = new MappedDictionary<uint, KeyValuePair<uint, bool>, bool>(table.state, kv => kv.Value);
|
||||
@@ -375,7 +381,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public event Action<OrderRecordImmutable?> selected_order_changed;
|
||||
public event Action begin_attaching, end_attaching;
|
||||
public event Action begin_attaching, end_attaching, orders_changed;
|
||||
public event Action<(IEnumerable, bool)> request_selection;
|
||||
|
||||
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
|
||||
@@ -395,6 +401,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
public OrderRecordImmutable? current_order => current_row?.order;
|
||||
public ObservableCollection<NamedValueVM> current_algo_custom_state { get; } = new ObservableCollection<NamedValueVM>();
|
||||
public AlgoDefinitionVM algo_vm => order_mod_vm.algo_vm;
|
||||
public OrderParamsVM order_mod_vm { get; }
|
||||
public FillsVMContainer fills_vm { get; } = new FillsVMContainer();
|
||||
@@ -474,6 +481,28 @@ namespace mbts.spark {
|
||||
public IObservable<bool> subscribe_show_fills() => _show_fills.observe_current(true);
|
||||
public IObservable<bool> subscribe_show_ladder() => _show_ladder.observe_current(true);
|
||||
public IObservable<bool> subscribe_show_order_params() => _show_order_params.observe_current(true);
|
||||
public IObservable<OrderSecurityAlgo?> subscribe_single_selected_order() =>
|
||||
Observable.Create<OrderSecurityAlgo?>(o => {
|
||||
OrderSecurityAlgo? last_fired = null;
|
||||
orders_changed += notify;
|
||||
_current_row.Changed += on_current_row_changed;
|
||||
notify();
|
||||
return Disposable.Create(dispose);
|
||||
// local methods
|
||||
void notify() {
|
||||
var next = current_row == null ? (OrderSecurityAlgo?)null : current_row.order_tuple.order;
|
||||
if (next != last_fired)
|
||||
o.OnNext(last_fired = next);
|
||||
}
|
||||
|
||||
void on_current_row_changed(OrderBookRowVM oldval, OrderBookRowVM newval) =>
|
||||
notify();
|
||||
|
||||
void dispose() {
|
||||
orders_changed -= notify;
|
||||
_current_row.Changed -= on_current_row_changed;
|
||||
}
|
||||
});
|
||||
|
||||
public void row_selection_changed(SelectionChangedEventArgs e) {
|
||||
try {
|
||||
@@ -503,6 +532,7 @@ namespace mbts.spark {
|
||||
_can_attach_selected_orders.refresh();
|
||||
_can_detach_selected_orders.refresh();
|
||||
selected_order_changed?.Invoke(current_order);
|
||||
_custom_state_sync.sync();
|
||||
} catch (Exception ex) {
|
||||
// [John]I observed a strange null-reference exception in this callback when the launcher was shutting down (see https://bluetradesys.atlassian.net/browse/SPARK-2089)
|
||||
Log.Ui.error($"[{nameof(OrderBookVM)}] Error handling selection change: {ex}");
|
||||
@@ -723,6 +753,45 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
class CustomStateSynchronizer {
|
||||
readonly OrderBookVM _vm;
|
||||
OrderBookRowVM _synced_row;
|
||||
|
||||
public CustomStateSynchronizer(OrderBookVM vm) =>
|
||||
_vm = vm;
|
||||
|
||||
public void sync(bool force_refresh = false) {
|
||||
var row = _vm.current_row != null &&
|
||||
_vm.selected_rows.Select(r => r.order_tuple.root.order.Key).Distinct().TryGetSingle(out uint droot) &&
|
||||
_vm.current_row.order_tuple.root.order.Key == droot ?
|
||||
_vm.current_row : null;
|
||||
if (force_refresh || row != _synced_row) {
|
||||
_synced_row = row;
|
||||
_refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh() {
|
||||
_vm.current_algo_custom_state.Clear();
|
||||
if (_synced_row == null)
|
||||
return;
|
||||
var dcs = _synced_row.order.dynamic_custom_state();
|
||||
// while dcs is not expected to be null, best to play it safe and double check.
|
||||
if (dcs.is_null_or_empty()) {
|
||||
var descendent_rows = _vm._row_collection.get_descendent_rows(_synced_row.order_key);
|
||||
if (_vm._row_collection.filter != null)
|
||||
descendent_rows = descendent_rows.Where(child => _vm._row_collection.filter(child.order_tuple));
|
||||
foreach (var dr in descendent_rows) {
|
||||
dcs = dr.order.dynamic_custom_state();
|
||||
if (!dcs.is_null_or_empty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (var p in dcs)
|
||||
_vm.current_algo_custom_state.Add(new NamedValueVM(p.Key, DynamicOrderHelper.fmt_param(p.Value)));
|
||||
}
|
||||
}
|
||||
|
||||
public class OrderParamsVM : DisposableViewModelBase {
|
||||
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
|
||||
readonly OrderBookVM _owner;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
@@ -6,9 +7,11 @@ using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using mbts.persist;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -24,7 +27,10 @@ namespace mbts.spark {
|
||||
const string _window_type = "OrderBook", _default_title = "Order Book";
|
||||
readonly INotificationService _notification_service;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
readonly bool _can_edit_specific_algo_params;
|
||||
IDisposable _attachment_exit_sub;
|
||||
// initialized in thread_init
|
||||
List<IDisposable> _thread_init_resources;
|
||||
OrderBookStartupData _state;
|
||||
OrdersModel _model;
|
||||
OrderBookVM _vm;
|
||||
@@ -34,13 +40,14 @@ namespace mbts.spark {
|
||||
SparkCommand[] _commands;
|
||||
|
||||
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
|
||||
OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
|
||||
OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool can_edit_specific_algo_params)
|
||||
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
|
||||
_model = model;
|
||||
_factory = factory;
|
||||
_notification_service = notification_service;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
_can_edit_specific_algo_params = can_edit_specific_algo_params;
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
|
||||
}
|
||||
|
||||
@@ -64,12 +71,14 @@ namespace mbts.spark {
|
||||
public string thread_id { get; }
|
||||
|
||||
public override void Dispose() {
|
||||
_commands.OfType<IDisposable>().DisposeAll();
|
||||
Disposable.dispose(_thread_init_resources);
|
||||
Disposable.dispose(ref _attachment_exit_sub);
|
||||
Disposable.dispose(ref _vm);
|
||||
}
|
||||
|
||||
public void thread_init() {
|
||||
Log.assert(_thread_init_resources == null, $"{nameof(_thread_init_resources)} must be initialized in thread_init.");
|
||||
_thread_init_resources = new List<IDisposable>();
|
||||
_menu_display_vm = new SparkMenuDisplayVM();
|
||||
if (_state != null)
|
||||
_menu_display_vm.load_from(_state.menu_display);
|
||||
@@ -147,6 +156,15 @@ namespace mbts.spark {
|
||||
can_execute: () => _vm.selected_orders.Any(x => passes_recreate_search_criteria(x))
|
||||
);
|
||||
recreate_selected_orders_command.group = CommandGroups.Modify;
|
||||
ICommand edit_algo_params_cmd;
|
||||
if (_can_edit_specific_algo_params) {
|
||||
_thread_init_resources.Add(CommandHelper.bind_param_source(
|
||||
new DialogCommand<OrderSecurityAlgo?, Window>(mk_algo_params_dialog, has_algo_params),
|
||||
_vm.subscribe_single_selected_order(),
|
||||
out edit_algo_params_cmd, true));
|
||||
} else {
|
||||
edit_algo_params_cmd = DelegateCommand.empty;
|
||||
}
|
||||
|
||||
var attach_selected_orders_command = new SparkCommandCommand(
|
||||
"Attach To Selected",
|
||||
@@ -194,14 +212,22 @@ namespace mbts.spark {
|
||||
show_grid_settings_command.group = CommandGroups.Auxiliary;
|
||||
|
||||
_commands = new SparkCommand[] {
|
||||
// modify commands
|
||||
toggle_show_right_panel_command,
|
||||
attach_selected_orders_command,
|
||||
detach_selected_orders_command,
|
||||
recreate_selected_orders_command,
|
||||
new SparkCommandCommand("Algo Params...", Icons.Algo, edit_algo_params_cmd) {
|
||||
group = CommandGroups.Modify,
|
||||
show_in_toolbar = false,
|
||||
show_in_context_menu = _can_edit_specific_algo_params
|
||||
},
|
||||
// cancel commands
|
||||
cancel_selected_orders_command,
|
||||
cancel_buy_orders_command,
|
||||
cancel_sell_orders_command,
|
||||
cancel_all_orders_command,
|
||||
|
||||
toggle_collapse_all_command,
|
||||
toggle_expand_all_command,
|
||||
show_grid_settings_command,
|
||||
@@ -210,9 +236,12 @@ namespace mbts.spark {
|
||||
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
|
||||
_menu_display_vm.spark_cmd
|
||||
};
|
||||
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.show_right_panel))
|
||||
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title());
|
||||
_thread_init_resources.AddRange(_commands.OfType<IDisposable>());
|
||||
_thread_init_resources.Add(_vm.as_demuxer()
|
||||
.subscribe<bool>(nameof(OrderBookVM.show_right_panel))
|
||||
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title()));
|
||||
_vm.selected_order_changed += vm_selected_order_changed;
|
||||
_thread_init_resources.Add(Disposable.Create(() => _vm.selected_order_changed -= vm_selected_order_changed));
|
||||
// local methods
|
||||
string get_toggle_show_right_panel_cmd_title() =>
|
||||
_vm.show_right_panel ? OrderBookView.CommandDescriptions.HideRightPanel : OrderBookView.CommandDescriptions.ShowRightPanel;
|
||||
@@ -234,6 +263,36 @@ namespace mbts.spark {
|
||||
attach_selected_orders_command.raiseCanExecuteChanged();
|
||||
detach_selected_orders_command.raiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
bool has_algo_params(OrderSecurityAlgo? order) =>
|
||||
order.HasValue && !OrderDataHelper.get_metadata(order.Value.algo.value.CustomCase).NoProps;
|
||||
|
||||
Window mk_algo_params_dialog(OrderSecurityAlgo? order) {
|
||||
if (!has_algo_params(order))
|
||||
return null;
|
||||
var algo_def = AlgoDefinitionVM.create(_model, order.Value.algo, editing_permissions: AlgoEditingPermissions.AllowEditValues);
|
||||
var algo_params_vm = new OrderBookAlgoParamsVM(algo_def, order.Value.order.is_non_terminal());
|
||||
var dialog = new OrderBookAlgoParamsDialog {
|
||||
Content = algo_params_vm
|
||||
};
|
||||
ui.WindowStyle.SetTitleContent(dialog, algo_def);
|
||||
dialog.Closed += (s, e) => {
|
||||
Disposable.dispose(ref algo_params_vm);
|
||||
dialog = null;
|
||||
};
|
||||
algo_params_vm.RequestCommit += commit;
|
||||
algo_params_vm.RequestClose += (s, e) => close_dialog();
|
||||
return dialog;
|
||||
|
||||
void close_dialog() {
|
||||
dialog?.Close();
|
||||
dialog = null;
|
||||
}
|
||||
void commit(CustomOrderDefinitionImmutable value) {
|
||||
_model.update_order(order.Value.order, new_algo: value).ignore();
|
||||
close_dialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
@@ -15,8 +16,11 @@ namespace bts.shell {
|
||||
readonly INotificationServiceDataCache _notification_service_data_cache;
|
||||
readonly OrdersModel _model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly OrderBookConfig _config;
|
||||
|
||||
public OrderBookWindowFactory(IShellModule owner, IBus bus, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache, OrdersModel model, UserSettingsHub settings_hub) : base(owner, "SparkOrderBookWindow") {
|
||||
var raw_config = Configuration.get<bts.config.OrderBookOptions>();
|
||||
_config = new OrderBookConfig(raw_config.can_edit_specific_algo_params);
|
||||
_bus = bus;
|
||||
_notification_service = notification_service;
|
||||
_notification_service_data_cache = notification_service_data_cache;
|
||||
@@ -29,7 +33,8 @@ namespace bts.shell {
|
||||
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
|
||||
us => us.order_book_grid ?? OrderBookStartupData.DefaultGrid,
|
||||
(us, s) => us.with(order_book_grid: s));
|
||||
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
|
||||
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state,
|
||||
SparkCommands.ShowOrders.icon, settings_manager.as_non_shared(), _config.can_edit_specific_algo_params);
|
||||
return Task.FromResult<IShellWindow>(w);
|
||||
}
|
||||
|
||||
@@ -37,5 +42,12 @@ namespace bts.shell {
|
||||
var w = await load(startup_data);
|
||||
_bus.post(SparkTags.ShowWindow, w);
|
||||
}
|
||||
|
||||
readonly struct OrderBookConfig {
|
||||
public readonly bool can_edit_specific_algo_params;
|
||||
|
||||
public OrderBookConfig(bool can_edit_specific_algo_params) =>
|
||||
this.can_edit_specific_algo_params = can_edit_specific_algo_params;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<Window
|
||||
x:Class="bts.shell.orderbook.OrderBookAlgoParamsDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:ob="clr-namespace:bts.shell.orderbook"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
Style="{StaticResource BTSWindowTitleTemplate}"
|
||||
>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionVM}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<usp:OrderTypeItem Margin="4,0" DisplayName="{Binding name, StringFormat='{}{0} Params'}" BadgeFill="{Binding badge_fill}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
<Window.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type ob:OrderBookAlgoParamsVM}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<usp:AlgoEditorGridView
|
||||
Width="390"
|
||||
Margin="5"
|
||||
AlgoParamRows="{Binding param_rows}"
|
||||
CanUserChangeAlgoRoot="False"
|
||||
/>
|
||||
<c:DialogSkirt Grid.Row="1" RightItems="{Binding dialog_skirt}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Window.ContentTemplate>
|
||||
</Window>
|
||||
@@ -0,0 +1,49 @@
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
/// <summary>
|
||||
/// Interaction logic for OrderBookAlgoParamsDialog.xaml
|
||||
/// </summary>
|
||||
partial class OrderBookAlgoParamsDialog : Window {
|
||||
public OrderBookAlgoParamsDialog() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class OrderBookAlgoParamsVM : DisposableViewModelBase, IRequestViewClose {
|
||||
readonly ObservableAggregateCollection<AlgoParamRowVM> _param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
|
||||
readonly DialogSkirtItem _commit, _cancel;
|
||||
|
||||
public OrderBookAlgoParamsVM(AlgoDefinitionVM algo_definition, bool can_user_edit) {
|
||||
this.algo_definition = algo_definition;
|
||||
_dispose = AlgoParamRowsHelper.connect(algo_definition, _param_rows, false);
|
||||
_commit = new DialogSkirtItem("OK", new DelegateCommand(commit, () => can_user_edit)) { is_default = true };
|
||||
_cancel = new DialogSkirtItem("Cancel", new DelegateCommand(close)) { is_cancel = true };
|
||||
// local methods
|
||||
void commit() {
|
||||
RequestCommit?.Invoke(new CustomOrderDefinitionImmutable(algo_definition.algo_root.created_assigned()));
|
||||
close();
|
||||
}
|
||||
void close() => RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler RequestClose;
|
||||
public event Action<CustomOrderDefinitionImmutable> RequestCommit;
|
||||
|
||||
public AlgoDefinitionVM algo_definition { get; }
|
||||
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows => _param_rows;
|
||||
public IEnumerable<DialogSkirtItem> dialog_skirt {
|
||||
get {
|
||||
yield return _commit;
|
||||
yield return _cancel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -484,6 +484,7 @@
|
||||
<Border Padding="12,12,12,6">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
@@ -495,7 +496,45 @@
|
||||
AlgoParamRows="{Binding param_rows}"
|
||||
IsReadOnly="{Binding DataContext.can_user_modify_selected_orders, ElementName=Root, Converter={StaticResource NotConverter}}"
|
||||
/>
|
||||
<StackPanel Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,8,0,0" Orientation="Horizontal">
|
||||
<c:ExpanderToggleButton
|
||||
x:Name="algo_state_toggle"
|
||||
Grid.Row="1"
|
||||
BorderThickness="0"
|
||||
Content="Algo State"
|
||||
HorizontalContentAlignment="Left"
|
||||
Margin="0,4,0,0"
|
||||
VerticalAlignment="Top"
|
||||
Visibility="{Binding current_algo_custom_state.Count, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<c:DIYGrid Grid.Row="1" Rows="{Binding current_algo_custom_state}" Margin="0,28,0,4">
|
||||
<c:DIYGrid.Visibility>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<cv:MultiValueConverterPipe>
|
||||
<cv:BooleanAndConverter/>
|
||||
<cv:BooleanToVisibilityConverter/>
|
||||
</cv:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="current_algo_custom_state.Count" Mode="OneWay" Converter="{StaticResource IntNotZeroConverter}"/>
|
||||
<Binding Path="IsChecked" ElementName="algo_state_toggle"/>
|
||||
</MultiBinding>
|
||||
</c:DIYGrid.Visibility>
|
||||
<c:DIYGrid.Columns>
|
||||
<c:DIYGridColumnCollection>
|
||||
<c:DIYGridColumn id="Name" Header="Param" Width="176"/>
|
||||
<c:DIYGridColumn id="Value" Header="Value" Width="176"/>
|
||||
</c:DIYGridColumnCollection>
|
||||
</c:DIYGrid.Columns>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
|
||||
<c:TextCell c:DIYGrid.ColumnKey="Name" Text="{Binding name}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="Value" Text="{Binding value}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<StackPanel Grid.Row="2" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,8,0,0" Orientation="Horizontal">
|
||||
<Button x:Name="modify_orders_button" Command="{Binding modify_selected_orders_cmd}">
|
||||
<Button.Margin>
|
||||
<Thickness Right="{StaticResource DialogSkirtButtonPadding.Right}"/>
|
||||
|
||||
@@ -23,6 +23,32 @@ namespace mbts.spark {
|
||||
|
||||
public IEnumerable<string> dynamic_types => _dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType);
|
||||
|
||||
public static string fmt_param(DynamicOrderParam dyn_param) {
|
||||
if (dyn_param == null)
|
||||
return null;
|
||||
switch (dyn_param.ValueCase) {
|
||||
case DynamicOrderParam.ValueOneofCase.AsArray:
|
||||
return string.Join(", ", dyn_param.AsArray.Element.Select(fmt_param));
|
||||
case DynamicOrderParam.ValueOneofCase.AsBool:
|
||||
return dyn_param.AsBool.ToString();
|
||||
case DynamicOrderParam.ValueOneofCase.AsDouble:
|
||||
return dyn_param.AsDouble.ToString();
|
||||
case DynamicOrderParam.ValueOneofCase.AsEnumerated:
|
||||
return dyn_param.AsEnumerated.EnumValue;
|
||||
case DynamicOrderParam.ValueOneofCase.AsInt32:
|
||||
return dyn_param.AsInt32.ToString();
|
||||
case DynamicOrderParam.ValueOneofCase.AsQuantity:
|
||||
return dyn_param.AsQuantity.ToString();
|
||||
case DynamicOrderParam.ValueOneofCase.AsString:
|
||||
return dyn_param.AsString;
|
||||
default:
|
||||
// implement additional cases as needed
|
||||
// (as of 4/17/20, this method is only used to format
|
||||
// dynamic orders' CustomState params in the orderbook)
|
||||
return $"({dyn_param.ValueCase})";
|
||||
}
|
||||
}
|
||||
|
||||
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs, Func<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinition> factory) {
|
||||
foreach (var d in _dynamic_types.DynamicOrderDesc) {
|
||||
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace mbts.spark
|
||||
{
|
||||
return;
|
||||
}
|
||||
var ev = fd.EnumType.Values.First(x => x.Name == value)?.Number;
|
||||
var ev = fd.EnumType.Values.FirstOrDefault(x => x.Name == value)?.Number;
|
||||
if(ev.HasValue)
|
||||
fd.Accessor.SetValue(message, ev.Value);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.collections;
|
||||
@@ -15,21 +14,19 @@ using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
internal class GenericOrderFactory
|
||||
{
|
||||
static public IAlgoVM create_vm(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
{
|
||||
static partial class OrderTypeVMFactory {
|
||||
internal class GenericOrderFactory {
|
||||
static public IAlgoVM create_vm(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions) {
|
||||
var prop = typeof(CustomOrderDefinition).GetProperty(algo_type.ToString(),
|
||||
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
var rt = prop.PropertyType;
|
||||
|
||||
var propi = typeof(CustomOrderDefinitionImmutable).GetProperty(algo_type.ToString(),
|
||||
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
var rti = propi.PropertyType;
|
||||
|
||||
var vmt = typeof(GenericOrderVM<,>);
|
||||
@@ -38,38 +35,33 @@ namespace mbts.spark {
|
||||
object vm = Activator.CreateInstance(makevm, key, algo_type, display_name, editing_permissions);
|
||||
return (IAlgoVM)vm;
|
||||
}
|
||||
static public void setup_generic_custom_order(CustomOrderDefinition custom, CustomOrderDefinition.CustomOneofCase type)
|
||||
{
|
||||
static public void setup_generic_custom_order(CustomOrderDefinition custom, CustomOrderDefinition.CustomOneofCase type) {
|
||||
var prop = typeof(CustomOrderDefinition).GetProperty(type.ToString(),
|
||||
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
var rt = prop.PropertyType;
|
||||
var rtt = Activator.CreateInstance(rt);
|
||||
|
||||
|
||||
var ci = rt.GetProperties().Where(x => x.PropertyType == typeof(CustomOrderDefinition));
|
||||
foreach(var cid in ci)
|
||||
foreach (var cid in ci)
|
||||
cid.SetValue(rtt, new CustomOrderDefinition { Limit = new LimitOrderDefinition() });
|
||||
prop.SetValue(custom, rtt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GenericOrderVM<TMutable, TImmutable> : OrderTypeVMBase<TMutable, TImmutable>
|
||||
where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage
|
||||
{
|
||||
where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
|
||||
readonly ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string> _parameters =
|
||||
new ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string>(vm => vm.descriptor.Name);
|
||||
readonly AlgoType _algo_type;
|
||||
|
||||
public GenericOrderVM(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(key, display_name, editing_permissions)
|
||||
{
|
||||
: base(key, display_name, editing_permissions) {
|
||||
_algo_type = algo_type;
|
||||
_parameters = new ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string>(vm => vm.descriptor.Name);
|
||||
_resources.AddRange(
|
||||
ObservableAutoWrapper.connect_item_hooks(_parameters, p =>
|
||||
{
|
||||
ObservableAutoWrapper.connect_item_hooks(_parameters, p => {
|
||||
p.PropertyChanged += on_param_prop_changed;
|
||||
return Disposable.Create(() =>
|
||||
{
|
||||
return Disposable.Create(() => {
|
||||
p.PropertyChanged -= on_param_prop_changed;
|
||||
remove_property(p.descriptor.Name);
|
||||
p.Dispose();
|
||||
@@ -80,84 +72,74 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public string algo_type => _algo_type.ToString();
|
||||
public override string abbreviation =>
|
||||
public override string abbreviation =>
|
||||
model == null ? display_name : model.factory.abbreviate_algo(new OneOf<CustomOrderDefinition.CustomOneofCase, string>(_algo_type));
|
||||
public override AlgoDisplayGroup group => AlgoDisplayGroup.API;
|
||||
public ObservableCollection<GenericOrderParamVM<TMutable>> parameters => _parameters;
|
||||
|
||||
protected override void on_model_internal()
|
||||
{
|
||||
protected override void on_model_internal() =>
|
||||
_sync_generic_params();
|
||||
}
|
||||
|
||||
void _sync_generic_params()
|
||||
{
|
||||
if (model != null)
|
||||
{
|
||||
Google.Protobuf.Reflection.MessageDescriptor desc = new TMutable().Descriptor;
|
||||
void _sync_generic_params() {
|
||||
if (model != null) {
|
||||
MessageDescriptor desc = new TMutable().Descriptor;
|
||||
_parameters.SyncWithDictionary(
|
||||
desc.Fields.InDeclarationOrder().Where(_is_value_param_desc).ToDictionary(p => p.Name), _mk_param_vm);
|
||||
}
|
||||
}
|
||||
static bool _is_value_param_desc(FieldDescriptor desc)
|
||||
{
|
||||
return (desc.FieldType != Google.Protobuf.Reflection.FieldType.Message ||
|
||||
|
||||
static bool _is_value_param_desc(FieldDescriptor desc) =>
|
||||
(desc.FieldType != FieldType.Message ||
|
||||
_is_quantity_measure_desc(desc));
|
||||
}
|
||||
|
||||
static bool _is_quantity_measure_desc(FieldDescriptor desc)
|
||||
{
|
||||
return desc.FieldType == Google.Protobuf.Reflection.FieldType.Message &&
|
||||
static bool _is_quantity_measure_desc(FieldDescriptor desc) =>
|
||||
desc.FieldType == FieldType.Message &&
|
||||
desc.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName;
|
||||
}
|
||||
|
||||
protected override IEnumerable<ChildAlgoData> get_selector_data()
|
||||
{
|
||||
var v = OrderDataHelper.get_child_algo_data(_algo_type);
|
||||
return v;
|
||||
}
|
||||
protected override IEnumerable<ChildAlgoData> get_selector_data() =>
|
||||
OrderDataHelper.get_child_algo_data(_algo_type);
|
||||
|
||||
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor)
|
||||
{
|
||||
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor) {
|
||||
ExceptionAssert.Argument.NotNull(descriptor, nameof(descriptor));
|
||||
|
||||
if (descriptor.FieldType == FieldType.Message &&
|
||||
descriptor.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName)
|
||||
{
|
||||
if (descriptor.FieldType == FieldType.Message &&
|
||||
descriptor.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName) {
|
||||
var qm = new GenericOrderQuantityVM<TMutable>(descriptor);
|
||||
return attached(qm, GenericOrderHelper.get_quantity_value, GenericOrderHelper.set_quantity_value);
|
||||
}
|
||||
else if(descriptor.FieldType == FieldType.Enum)
|
||||
{
|
||||
var pvm = new GenericOrderEnumeratedVM<TMutable>(descriptor);
|
||||
attached(pvm, GenericOrderHelper.get_enumerated_value, GenericOrderHelper.set_enumerated_value);
|
||||
return pvm;
|
||||
}
|
||||
else if (descriptor.FieldType == FieldType.Bool)
|
||||
} else if (descriptor.FieldType == FieldType.Enum) {
|
||||
return EnumParamFactory.create(this, descriptor);
|
||||
} else if (descriptor.FieldType == FieldType.Bool) {
|
||||
return mk<bool?, bool>(descriptor, v => v, v => v.GetValueOrDefault());
|
||||
else if (descriptor.FieldType == FieldType.Double || descriptor.FieldType == FieldType.Float)
|
||||
} else if (descriptor.FieldType == FieldType.Double || descriptor.FieldType == FieldType.Float) {
|
||||
return mk<double, double>(descriptor, v => v, v => v, double.NaN);
|
||||
else if (descriptor.FieldType == FieldType.Int32)
|
||||
} else if (descriptor.FieldType == FieldType.Int32) {
|
||||
return mk<int?, int>(descriptor, v => v, v => v.GetValueOrDefault());
|
||||
else if (descriptor.FieldType == FieldType.UInt32)
|
||||
} else if (descriptor.FieldType == FieldType.UInt32) {
|
||||
return mk<uint?, uint>(descriptor, v => v, v => v.GetValueOrDefault());
|
||||
else if (descriptor.FieldType == FieldType.String)
|
||||
} else if (descriptor.FieldType == FieldType.String) {
|
||||
return mk<string, string>(descriptor, v => v, v => v);
|
||||
else
|
||||
return null;
|
||||
} else return null;
|
||||
}
|
||||
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(FieldDescriptor fdesc, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default)
|
||||
{
|
||||
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back);
|
||||
return attached<TView, GenericOrderParamVM<TMutable, TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
|
||||
|
||||
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(
|
||||
FieldDescriptor fdesc,
|
||||
Func<TModel, TView> convert,
|
||||
Func<TView, TModel> convert_back,
|
||||
TView default_value = default) {
|
||||
//
|
||||
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back);
|
||||
return attached(pvm, get_from_param, assign_to_param, default_value);
|
||||
TView get_from_param(IMessage param, FieldDescriptor fd) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
|
||||
void assign_to_param(IMessage param, FieldDescriptor fd, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
|
||||
}
|
||||
|
||||
TParam attached<T, TParam>(TParam pvm, Func<IMessage, FieldDescriptor, T> selector, Action<IMessage, FieldDescriptor, T> assign_to_param, T default_value = default) where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T>
|
||||
{
|
||||
add_property<T>(
|
||||
TParam attached<T, TParam>(
|
||||
TParam pvm,
|
||||
Func<IMessage, FieldDescriptor, T>
|
||||
selector, Action<IMessage, FieldDescriptor, T> assign_to_param,
|
||||
T default_value = default)
|
||||
where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T> {
|
||||
//
|
||||
add_property(
|
||||
pvm.descriptor.Name,
|
||||
mdef => mdef != null ? selector(mdef, pvm.descriptor) : default_value,
|
||||
(mdef, v) => assign_to_param(mdef, pvm.descriptor, v),
|
||||
@@ -166,7 +148,37 @@ namespace mbts.spark {
|
||||
pvm.subscribe_value(),
|
||||
default_value);
|
||||
return pvm;
|
||||
}
|
||||
}
|
||||
|
||||
static class EnumParamFactory {
|
||||
[ThreadStatic]
|
||||
static Dictionary<Type, IFactory> _factories_backing;
|
||||
static Dictionary<Type, IFactory> _factories =>
|
||||
_factories_backing ?? (_factories_backing = new Dictionary<Type, IFactory>());
|
||||
|
||||
public static GenericOrderParamVM<TMutable> create(GenericOrderVM<TMutable, TImmutable> owner, FieldDescriptor descriptor) {
|
||||
if (descriptor.FieldType != FieldType.Enum)
|
||||
throw ExceptionBuilder.Argument.unexpected_case(descriptor.FieldType, $"{nameof(descriptor)}.{nameof(FieldDescriptor.FieldType)}");
|
||||
if (!_factories.TryGetValue(descriptor.EnumType.ClrType, out var f)) {
|
||||
// Impl<T> is a nested type, so parent class's generic type args must be included.
|
||||
_factories[descriptor.EnumType.ClrType] = f = (IFactory)Activator.CreateInstance(typeof(Impl<>)
|
||||
.MakeGenericType(typeof(TMutable), typeof(TImmutable), descriptor.EnumType.ClrType));
|
||||
}
|
||||
return f.create(owner, descriptor);
|
||||
}
|
||||
|
||||
interface IFactory {
|
||||
GenericOrderParamVM<TMutable> create(GenericOrderVM<TMutable, TImmutable> owner, FieldDescriptor descriptor);
|
||||
}
|
||||
|
||||
class Impl<T> : IFactory where T : struct {
|
||||
static readonly Func<T, T?> _convert = v => v;
|
||||
static readonly Func<T?, T> _convert_back = v => v ?? default;
|
||||
|
||||
public GenericOrderParamVM<TMutable> create(GenericOrderVM<TMutable, TImmutable> owner, FieldDescriptor descriptor) =>
|
||||
owner.mk(descriptor, _convert, _convert_back, default);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,11 @@ namespace mbts.spark {
|
||||
public static string last_msg(this OrderRecordImmutable order) =>
|
||||
order.HasState ? order.State.LastMsg : string.Empty;
|
||||
|
||||
public static IReadOnlyDictionary<string, DynamicOrderParam> dynamic_custom_state(this OrderRecordImmutable order) =>
|
||||
order.HasState && order.State.HasDynamic ?
|
||||
(order.State.Dynamic.CustomState ?? DictionaryHelper.empty<string, DynamicOrderParam>()) :
|
||||
DictionaryHelper.empty<string, DynamicOrderParam>();
|
||||
|
||||
public static DateTime created(this OrderRecordImmutable order) =>
|
||||
order.HasState && order.State.HasCreated ? order.State.Created.ToDateTime().ToLocalTime() : DateTime.MinValue;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<RuntimeIdentifiers>win7-x64</RuntimeIdentifiers>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
|
||||
<LanguageTargets Condition="'$(VisualStudioVersion)'=='16.0'">$(MSBuildToolsPath)\Microsoft.CSharp.targets</LanguageTargets>
|
||||
<ApplicationIcon>logo.ico</ApplicationIcon>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<StartupObject>mbts.ui.shell.App</StartupObject>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace mbts.ui.controls {
|
||||
class DIYGridColumn : DIYGrid.ColumnBase<string> {
|
||||
public DIYGridColumn()
|
||||
: base() {
|
||||
is_visible = true;
|
||||
Width = 60;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,7 +326,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
static bool _check_tick_size(double tick_size) =>
|
||||
(tick_size > 0 && tick_size <= 0.5) || tick_size == 1;
|
||||
tick_size > 0 && (tick_size <= 0.5 || MathUtils.is_int(tick_size));
|
||||
|
||||
struct SpecKey : IEquatable<SpecKey> {
|
||||
public readonly uint denominator, display_digits;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using mbts.ui.converters;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
@@ -12,6 +14,31 @@ namespace mbts.ui.utilities {
|
||||
public static string fmt(string title, KeyGesture key_gesture) =>
|
||||
key_gesture == null ? title : $"{title} ({key_gesture.GetDisplayStringForCulture(CultureInfo.CurrentCulture)})";
|
||||
|
||||
public static IDisposable bind_param_source<T>(ICommand command, IObservable<T> param_source, out ICommand bound_cmd, bool dispose_command_on_dispose = false) {
|
||||
bool initialized = false;
|
||||
bool can_execute = false;
|
||||
T param = default;
|
||||
var bcmd = new DelegateCommand(() => command.Execute(param), () => can_execute);
|
||||
bound_cmd = bcmd;
|
||||
var sub = param_source.Subscribe(p => {
|
||||
param = p;
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
can_execute = command.CanExecute(p);
|
||||
bcmd.RaiseCanExecuteChanged();
|
||||
} else if (can_execute != command.CanExecute(p)) {
|
||||
can_execute = !can_execute;
|
||||
bcmd.RaiseCanExecuteChanged();
|
||||
}
|
||||
});
|
||||
return dispose_command_on_dispose ?
|
||||
Disposable.Create(() => {
|
||||
Disposable.dispose(ref sub);
|
||||
(command as IDisposable)?.Dispose();
|
||||
command = null;
|
||||
}) : sub;
|
||||
}
|
||||
|
||||
#region Paraphrased from https://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Commands/CommandHelpers.cs
|
||||
|
||||
public static bool CanExecuteCommandSource(ICommandSource commandSource) {
|
||||
|
||||
@@ -30,6 +30,21 @@ namespace mbts.ui {
|
||||
public string name { get; }
|
||||
}
|
||||
|
||||
class NamedValueVM : ViewModelBase {
|
||||
string _name;
|
||||
object _value;
|
||||
|
||||
public NamedValueVM() { }
|
||||
|
||||
public NamedValueVM(string name, object value) {
|
||||
_name = name;
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public string name { get => _name; set => Set(ref _name, value); }
|
||||
public object value { get => _value; set => Set(ref _value, value); }
|
||||
}
|
||||
|
||||
static class EnabledValueVMExtensions {
|
||||
public static T? get_nullable<T>(this EnabledValueVM<T> vm) where T : struct =>
|
||||
vm.enabled ? vm.value : (T?)null;
|
||||
|
||||
@@ -83,22 +83,24 @@ namespace bts.api {
|
||||
}
|
||||
|
||||
public static int Compare(SecurityImmutable x, SecurityImmutable y) {
|
||||
if (x.ContractCase == ContractOneofCase.Future) return y.ContractCase == ContractOneofCase.Future ? Compare(x.Symbol, x.Future, y.Symbol, y.Future) : -1;
|
||||
else if (y.ContractCase == ContractOneofCase.Future) return 1;
|
||||
if (x.ContractCase == ContractOneofCase.Future) {
|
||||
return y.ContractCase == ContractOneofCase.Future ? Compare(x.Symbol, x.Future, y.Symbol, y.Future) : -1;
|
||||
} else if (y.ContractCase == ContractOneofCase.Future) {
|
||||
return 1;
|
||||
}
|
||||
var xlegs = get_flat_future_legs(x).ToList();
|
||||
var ylegs = get_flat_future_legs(y).ToList();
|
||||
int max = Math.Min(xlegs.Count, ylegs.Count);
|
||||
for (int i = 0; i < max; i++) {
|
||||
int rv = Compare(xlegs[i].symbol, xlegs[i].future, ylegs[i].symbol, ylegs[i].future);
|
||||
if (rv != 0) return rv;
|
||||
}
|
||||
for (int i = 0; i < max; i++) {
|
||||
int rv = xlegs[i].quantity.CompareTo(ylegs[i].quantity);
|
||||
if (rv != 0) return rv;
|
||||
}
|
||||
for (int i = 0; i < max; i++) {
|
||||
int rv = xlegs[i].exchange.CompareTo(ylegs[i].exchange);
|
||||
if (rv != 0) return rv;
|
||||
if (rv != 0)
|
||||
return rv;
|
||||
rv = xlegs[i].quantity.CompareTo(ylegs[i].quantity);
|
||||
if (rv != 0)
|
||||
return rv;
|
||||
rv = xlegs[i].exchange.CompareTo(ylegs[i].exchange);
|
||||
if (rv != 0)
|
||||
return rv;
|
||||
}
|
||||
return xlegs.Count.CompareTo(ylegs.Count);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace bts.utils {
|
||||
public static class FilePathHelper {
|
||||
public static string GetUniquePathWithDate(string path_root, string ext) =>
|
||||
GetUniquePath($"{path_root}_{DateTime.Now:MM - dd - yyyy_hh - mm}", ext);
|
||||
|
||||
public static string GetUniquePath(string path_root, string ext) {
|
||||
var path = $"{path_root}.{ext}";
|
||||
const int max_copies = 2 ^ 14;
|
||||
int i = 1;
|
||||
while (File.Exists(path)) {
|
||||
path = $"{path_root}({i++}).{ext}";
|
||||
if (i == max_copies)
|
||||
throw new InvalidOperationException("Unable to make path unique by concating a number.");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -209,7 +209,7 @@ namespace System.Reactive.Linq {
|
||||
/// <returns>An IDisposable object used to unsubscribe from the source sequence, and dispose the latest value subscription.</returns>
|
||||
public static IDisposable SubscribeEach<T>(this IObservable<T> source, Func<T, IDisposable> subscribe_value) {
|
||||
IDisposable value_sub = null;
|
||||
IDisposable source_sub = source.Select(v => v).Subscribe(on_source);
|
||||
IDisposable source_sub = source.Subscribe(on_source);
|
||||
return Disposable.Create(dispose);
|
||||
// local methods
|
||||
void on_source(T value) {
|
||||
|
||||
@@ -1,208 +1,253 @@
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.utils {
|
||||
public static class UpdateExtensions {
|
||||
public static TableUpdate<T, TKey> with_added<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> added) where TKey : IEquatable<TKey> =>
|
||||
TableUpdate<T, TKey>.update(table, added, Enumerable.Empty<Change<T>>(), Enumerable.Empty<T>(), table.key_extractor);
|
||||
|
||||
public static TableUpdate<T, TKey> with_removed<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> removed) where TKey : IEquatable<TKey> =>
|
||||
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), Enumerable.Empty<Change<T>>(), removed, table.key_extractor);
|
||||
|
||||
public static TableUpdate<T, TKey> with_changed<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> changed) where TKey : IEquatable<TKey> =>
|
||||
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), changed.Select(v => new Change<T>(table.state[table.key_extractor(v)], v)), Enumerable.Empty<T>(), table.key_extractor);
|
||||
|
||||
public static TableUpdate<T, TKey> with_changes<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<Change<T>> changes) where TKey : IEquatable<TKey> =>
|
||||
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), changes, Enumerable.Empty<T>(), table.key_extractor);
|
||||
|
||||
public static IObservable<TableUpdate<TResult, TKey>> RootDependent<T, TResult, TKey>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, TResult> with_root,
|
||||
Func<T, TResult> without_root,
|
||||
Func<TResult, TKey> result_key)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
RootDependent(source, key, parent_key, with_root, without_root, result_key, default);
|
||||
|
||||
// TODO: unit tests
|
||||
public static IObservable<TableUpdate<TResult, TKey>> RootDependent<T, TKey, TResult>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, TResult> get_result,
|
||||
Func<T, TResult> get_result_no_parent,
|
||||
Func<TResult, TKey> result_key,
|
||||
TKey no_parent_key)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
Observable.Create<TableUpdate<TResult, TKey>>(observer => {
|
||||
var state = DictionaryHelper.empty<TKey, T>();
|
||||
var output = TableUpdate<TResult, TKey>.mk_empty(result_key);
|
||||
var parent_to_children = new SetMap<TKey, T>();
|
||||
return source.Subscribe(update);
|
||||
//
|
||||
void update(TableUpdate<T, TKey> input) {
|
||||
state = input.state;
|
||||
var builder = output.to_builder();
|
||||
// execute all parent_to_children changes first, so descendent-propagation will be efficient (otherwise propagation might be needlessly performed on values that are yet to be removed, or performed with the wrong root, if the real root is yet to be added).
|
||||
foreach (var v in input.removed.Concat(input.changes.Select(ch => ch.Old))) {
|
||||
var k = input.key_extractor(v);
|
||||
parent_to_children.remove(parent_key(v), v);
|
||||
}
|
||||
foreach (var v in input.added.Concat(input.changed)) {
|
||||
var pk = parent_key(v);
|
||||
if (!pk.Equals(no_parent_key))
|
||||
parent_to_children.add(pk, v);
|
||||
}
|
||||
foreach (var v in input.removed) {
|
||||
var k = input.key_extractor(v);
|
||||
builder.remove(k);
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children) {
|
||||
var ck = key(child);
|
||||
builder.add_or_update(ck, get_result_no_parent(child));
|
||||
foreach (var d in descendents(ck))
|
||||
builder.add_or_update(key(d), get_result(d, child));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var v in input.changed) {
|
||||
var k = input.key_extractor(v);
|
||||
var root = get_root(v);
|
||||
TResult result;
|
||||
if (key(root).Equals(k)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(key(d), get_result(d, v));
|
||||
} else {
|
||||
result = get_result(v, root);
|
||||
}
|
||||
builder.add_or_update(k, result);
|
||||
}
|
||||
foreach (var v in input.added) {
|
||||
var k = input.key_extractor(v);
|
||||
var pk = parent_key(v);
|
||||
TResult result;
|
||||
if (pk.Equals(no_parent_key)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(key(d), get_result(d, v));
|
||||
} else {
|
||||
parent_to_children.add(pk, v);
|
||||
var root = get_root(v);
|
||||
if (key(root).Equals(k)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(key(d), get_result(d, v));
|
||||
} else {
|
||||
result = get_result(v, root);
|
||||
}
|
||||
}
|
||||
builder.add_or_update(k, result);
|
||||
}
|
||||
observer.OnNext(output = builder.to_table());
|
||||
}
|
||||
T get_root(T value) {
|
||||
var pk = parent_key(value);
|
||||
while (!pk.Equals(no_parent_key) && state.TryGetValue(pk, out var parent)) {
|
||||
value = parent;
|
||||
pk = parent_key(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
IEnumerable<T> descendents(TKey k) {
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children) {
|
||||
yield return child;
|
||||
foreach (var d in descendents(key(child)))
|
||||
yield return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<TableUpdate<T, TKey>> MergeTable<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, IObservable<TableUpdate<T, TKey>> other) where TKey : IEquatable<TKey> =>
|
||||
source.AggregateLatest(other, _Merge<T, TKey>.source_change_del, _Merge<T, TKey>.other_change_del, () => TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty);
|
||||
|
||||
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> ParentDependent<T, TKey, TResult>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, TResult> get_result,
|
||||
Func<T, TResult> get_result_no_parent)
|
||||
where TResult : IEquatable<TResult>
|
||||
where TKey : IEquatable<TKey> =>
|
||||
ParentDependent(source, key, parent_key, get_result, get_result_no_parent, EqualityComparer<TResult>.Default, default);
|
||||
|
||||
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> ParentDependent<T, TKey, TResult>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, TResult> get_result,
|
||||
Func<T, TResult> get_result_no_parent,
|
||||
IEqualityComparer<TResult> result_comparer,
|
||||
TKey no_parent_key)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
Observable.Create<TableUpdate<KeyValuePair<TKey, TResult>, TKey>>(observer => {
|
||||
var value_comparer = EqualityComparer.Create<KeyValuePair<TKey, TResult>, TResult>(kv => kv.Value, result_comparer);
|
||||
var state = DictionaryHelper.empty<TKey, T>();
|
||||
var output = TableUpdate<KeyValuePair<TKey, TResult>, TKey>.mk_empty(kv => kv.Key);
|
||||
var parent_to_children = new SetMap<TKey, T>();
|
||||
return source.Subscribe(update);
|
||||
//
|
||||
void update(TableUpdate<T, TKey> input) {
|
||||
state = input.state;
|
||||
var builder = output.to_builder();
|
||||
foreach (var v in input.removed) {
|
||||
var k = input.key_extractor(v);
|
||||
builder.remove(k);
|
||||
parent_to_children.remove(parent_key(v), v);
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children)
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result_no_parent(child)));
|
||||
}
|
||||
}
|
||||
foreach (var v in input.changed) {
|
||||
var k = key(v);
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, lookup_result(v)));
|
||||
parent_to_children.set(parent_key(v), v);
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children)
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result(child, v)));
|
||||
}
|
||||
}
|
||||
foreach (var v in input.added) {
|
||||
var k = key(v);
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, lookup_result(v)));
|
||||
parent_to_children.add(parent_key(v), v);
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children)
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result(child, v)));
|
||||
}
|
||||
}
|
||||
var result = builder.to_table_checked(value_comparer);
|
||||
if (result.has_changes())
|
||||
observer.OnNext(output = result);
|
||||
}
|
||||
TResult lookup_result(T child) {
|
||||
var pk = parent_key(child);
|
||||
return !pk.Equals(no_parent_key) && state.TryGetValue(pk, out T parent) ? get_result(child, parent) : get_result_no_parent(child);
|
||||
}
|
||||
});
|
||||
|
||||
static class _Merge<T, TKey> where TKey : IEquatable<TKey> {
|
||||
public static readonly Func<TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>, TableUpdate<T, TKey>> source_change_del = _source_changed_impl;
|
||||
public static readonly Func<TableUpdate<T, TKey>, TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>> other_change_del = _other_changed_impl;
|
||||
|
||||
static TableUpdate<T, TKey> _source_changed_impl(TableUpdate<T, TKey> target, Change<TableUpdate<T, TKey>> source_change, TableUpdate<T, TKey> other_table) =>
|
||||
TableUpdate<T, TKey>.update(target, source_change.New.added, source_change.New.changes, source_change.New.removed, source_change.New.key_extractor);
|
||||
|
||||
static TableUpdate<T, TKey> _other_changed_impl(TableUpdate<T, TKey> target, TableUpdate<T, TKey> source_table, Change<TableUpdate<T, TKey>> other_change) =>
|
||||
TableUpdate<T, TKey>.update(target, other_change.New.added, other_change.New.changes, other_change.New.removed, other_change.New.key_extractor);
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using bts.utils.proto;
|
||||
using Google.Protobuf;
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.utils {
|
||||
public static class UpdateExtensions {
|
||||
public static TableUpdate<T, TKey> with_added<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> added) where TKey : IEquatable<TKey> =>
|
||||
TableUpdate<T, TKey>.update(table, added, Enumerable.Empty<Change<T>>(), Enumerable.Empty<T>(), table.key_extractor);
|
||||
|
||||
public static TableUpdate<T, TKey> with_removed<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> removed) where TKey : IEquatable<TKey> =>
|
||||
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), Enumerable.Empty<Change<T>>(), removed, table.key_extractor);
|
||||
|
||||
public static TableUpdate<T, TKey> with_changed<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> changed) where TKey : IEquatable<TKey> =>
|
||||
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), changed.Select(v => new Change<T>(table.state[table.key_extractor(v)], v)), Enumerable.Empty<T>(), table.key_extractor);
|
||||
|
||||
public static TableUpdate<T, TKey> with_changes<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<Change<T>> changes) where TKey : IEquatable<TKey> =>
|
||||
TableUpdate<T, TKey>.update(table, Enumerable.Empty<T>(), changes, Enumerable.Empty<T>(), table.key_extractor);
|
||||
|
||||
public static IObservable<TableUpdate<TResult, TKey>> RootDependent<T, TResult, TKey>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, TResult> with_root,
|
||||
Func<T, TResult> without_root,
|
||||
Func<TResult, TKey> result_key)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
RootDependent(source, key, parent_key, with_root, without_root, result_key, default);
|
||||
|
||||
// TODO: unit tests
|
||||
public static IObservable<TableUpdate<TResult, TKey>> RootDependent<T, TKey, TResult>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, TResult> get_result,
|
||||
Func<T, TResult> get_result_no_parent,
|
||||
Func<TResult, TKey> result_key,
|
||||
TKey no_parent_key)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
Observable.Create<TableUpdate<TResult, TKey>>(observer => {
|
||||
var state = DictionaryHelper.empty<TKey, T>();
|
||||
var output = TableUpdate<TResult, TKey>.mk_empty(result_key);
|
||||
var parent_to_children = new SetMap<TKey, T>();
|
||||
return source.Subscribe(update);
|
||||
//
|
||||
void update(TableUpdate<T, TKey> input) {
|
||||
state = input.state;
|
||||
var builder = output.to_builder();
|
||||
// execute all parent_to_children changes first, so descendent-propagation will be efficient (otherwise propagation might be needlessly performed on values that are yet to be removed, or performed with the wrong root, if the real root is yet to be added).
|
||||
foreach (var v in input.removed.Concat(input.changes.Select(ch => ch.Old))) {
|
||||
var k = input.key_extractor(v);
|
||||
parent_to_children.remove(parent_key(v), v);
|
||||
}
|
||||
foreach (var v in input.added.Concat(input.changed)) {
|
||||
var pk = parent_key(v);
|
||||
if (!pk.Equals(no_parent_key))
|
||||
parent_to_children.add(pk, v);
|
||||
}
|
||||
foreach (var v in input.removed) {
|
||||
var k = input.key_extractor(v);
|
||||
builder.remove(k);
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children) {
|
||||
var ck = key(child);
|
||||
builder.add_or_update(ck, get_result_no_parent(child));
|
||||
foreach (var d in descendents(ck))
|
||||
builder.add_or_update(key(d), get_result(d, child));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var v in input.changed) {
|
||||
var k = input.key_extractor(v);
|
||||
var root = get_root(v);
|
||||
TResult result;
|
||||
if (key(root).Equals(k)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(key(d), get_result(d, v));
|
||||
} else {
|
||||
result = get_result(v, root);
|
||||
}
|
||||
builder.add_or_update(k, result);
|
||||
}
|
||||
foreach (var v in input.added) {
|
||||
var k = input.key_extractor(v);
|
||||
var pk = parent_key(v);
|
||||
TResult result;
|
||||
if (pk.Equals(no_parent_key)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(key(d), get_result(d, v));
|
||||
} else {
|
||||
parent_to_children.add(pk, v);
|
||||
var root = get_root(v);
|
||||
if (key(root).Equals(k)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(key(d), get_result(d, v));
|
||||
} else {
|
||||
result = get_result(v, root);
|
||||
}
|
||||
}
|
||||
builder.add_or_update(k, result);
|
||||
}
|
||||
observer.OnNext(output = builder.to_table());
|
||||
}
|
||||
T get_root(T value) {
|
||||
var pk = parent_key(value);
|
||||
while (!pk.Equals(no_parent_key) && state.TryGetValue(pk, out var parent)) {
|
||||
value = parent;
|
||||
pk = parent_key(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
IEnumerable<T> descendents(TKey k) {
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children) {
|
||||
yield return child;
|
||||
foreach (var d in descendents(key(child)))
|
||||
yield return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<TableUpdate<T, TKey>> MergeTable<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, IObservable<TableUpdate<T, TKey>> other) where TKey : IEquatable<TKey> =>
|
||||
source.AggregateLatest(other, _Merge<T, TKey>.source_change_del, _Merge<T, TKey>.other_change_del, () => TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty);
|
||||
|
||||
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> ParentDependent<T, TKey, TResult>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, TResult> get_result,
|
||||
Func<T, TResult> get_result_no_parent)
|
||||
where TResult : IEquatable<TResult>
|
||||
where TKey : IEquatable<TKey> =>
|
||||
ParentDependent(source, key, parent_key, get_result, get_result_no_parent, EqualityComparer<TResult>.Default, default);
|
||||
|
||||
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> ParentDependent<T, TKey, TResult>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, TResult> get_result,
|
||||
Func<T, TResult> get_result_no_parent,
|
||||
IEqualityComparer<TResult> result_comparer,
|
||||
TKey no_parent_key)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
Observable.Create<TableUpdate<KeyValuePair<TKey, TResult>, TKey>>(observer => {
|
||||
var value_comparer = EqualityComparer.Create<KeyValuePair<TKey, TResult>, TResult>(kv => kv.Value, result_comparer);
|
||||
var state = DictionaryHelper.empty<TKey, T>();
|
||||
var output = TableUpdate<KeyValuePair<TKey, TResult>, TKey>.mk_empty(kv => kv.Key);
|
||||
var parent_to_children = new SetMap<TKey, T>();
|
||||
return source.Subscribe(update);
|
||||
//
|
||||
void update(TableUpdate<T, TKey> input) {
|
||||
state = input.state;
|
||||
var builder = output.to_builder();
|
||||
foreach (var v in input.removed) {
|
||||
var k = input.key_extractor(v);
|
||||
builder.remove(k);
|
||||
parent_to_children.remove(parent_key(v), v);
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children)
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result_no_parent(child)));
|
||||
}
|
||||
}
|
||||
foreach (var v in input.changed) {
|
||||
var k = key(v);
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, lookup_result(v)));
|
||||
parent_to_children.set(parent_key(v), v);
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children)
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result(child, v)));
|
||||
}
|
||||
}
|
||||
foreach (var v in input.added) {
|
||||
var k = key(v);
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, lookup_result(v)));
|
||||
parent_to_children.add(parent_key(v), v);
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children)
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(child), get_result(child, v)));
|
||||
}
|
||||
}
|
||||
var result = builder.to_table_checked(value_comparer);
|
||||
if (result.has_changes())
|
||||
observer.OnNext(output = result);
|
||||
}
|
||||
TResult lookup_result(T child) {
|
||||
var pk = parent_key(child);
|
||||
return !pk.Equals(no_parent_key) && state.TryGetValue(pk, out T parent) ? get_result(child, parent) : get_result_no_parent(child);
|
||||
}
|
||||
});
|
||||
|
||||
static class _Merge<T, TKey> where TKey : IEquatable<TKey> {
|
||||
public static readonly Func<TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>, TableUpdate<T, TKey>> source_change_del = _source_changed_impl;
|
||||
public static readonly Func<TableUpdate<T, TKey>, TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>> other_change_del = _other_changed_impl;
|
||||
|
||||
static TableUpdate<T, TKey> _source_changed_impl(TableUpdate<T, TKey> target, Change<TableUpdate<T, TKey>> source_change, TableUpdate<T, TKey> other_table) =>
|
||||
TableUpdate<T, TKey>.update(target, source_change.New.added, source_change.New.changes, source_change.New.removed, source_change.New.key_extractor);
|
||||
|
||||
static TableUpdate<T, TKey> _other_changed_impl(TableUpdate<T, TKey> target, TableUpdate<T, TKey> source_table, Change<TableUpdate<T, TKey>> other_change) =>
|
||||
TableUpdate<T, TKey>.update(target, other_change.New.added, other_change.New.changes, other_change.New.removed, other_change.New.key_extractor);
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace bts.utils.diagnostics {
|
||||
public static class DiagnosticUpdateExtensions {
|
||||
// Hard to imagine why this util would be needed outside of diagnosig bugs.
|
||||
// The #if DEBUG directive is an extra precaution to avoid accidentally writing
|
||||
// files like this on a client's machine.
|
||||
#if DEBUG
|
||||
static readonly string _proto_capture_ext = "proto_capture";
|
||||
|
||||
public static IObservable<TableUpdate<T, TKey>> ProtoBufCapture<T, TKey, TProto>(this IObservable<TableUpdate<T, TKey>> source, Func<T, TProto> to_message)
|
||||
where TKey : IEquatable<TKey>
|
||||
where TProto : class, IMessage<TProto> =>
|
||||
ProtoBufCapture(source,
|
||||
FilePathHelper.GetUniquePathWithDate(Path.Combine(Directory.GetCurrentDirectory(), typeof(TProto).Name), _proto_capture_ext),
|
||||
to_message);
|
||||
|
||||
public static IObservable<TableUpdate<T, TKey>> ProtoBufCapture<T, TKey, TProto>(this IObservable<TableUpdate<T, TKey>> source, string path, Func<T, TProto> to_message)
|
||||
where TKey : IEquatable<TKey>
|
||||
where TProto : class, IMessage<TProto> =>
|
||||
Observable.Create<TableUpdate<T, TKey>>(o => {
|
||||
// write the state on dispose, and/or on error
|
||||
IReadOnlyDictionary<TKey, T> state = DictionaryHelper.empty<TKey, T>();
|
||||
var sub = source.Subscribe(table => {
|
||||
state = table.state;
|
||||
o.OnNext(table);
|
||||
}, ex => {
|
||||
save(true);
|
||||
o.OnError(ex);
|
||||
},
|
||||
o.OnCompleted);
|
||||
return Disposable.Create(() => {
|
||||
save(false);
|
||||
Disposable.dispose(ref sub);
|
||||
});
|
||||
void save(bool has_error) {
|
||||
if (state.Count > 0)
|
||||
FileStreamHelper.WriteProtos(has_error ? $"{path}(error)" : path, state.Values.Select(to_message).ExceptNull());
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.resources {
|
||||
class ResourceHelper {
|
||||
const string _default_namespace = "spark_test.Resources";
|
||||
|
||||
public static Stream LoadResource(string name) {
|
||||
var ass = Assembly.GetExecutingAssembly();
|
||||
return ass.GetManifestResourceStream(name) ?? ass.GetManifestResourceStream($"{_default_namespace}.{name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using bts.api;
|
||||
using bts.resources;
|
||||
using bts.test;
|
||||
using bts.utils.proto;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using bts.api;
|
||||
using bts.test;
|
||||
|
||||
namespace bts.mcore
|
||||
{
|
||||
namespace bts.mcore {
|
||||
[TestGroup]
|
||||
class SecurityTests {
|
||||
[Test]
|
||||
@@ -28,5 +26,23 @@ namespace bts.mcore
|
||||
Assert.That(dict[b] == 1);
|
||||
Assert.That(dict[c] == 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_security_compare() {
|
||||
const string securities_resource = "KrandallOrderBookCrash.security_proto";
|
||||
// verify that the sample size doesn't shrink for some reason.
|
||||
// if it grows a bit that's fine; the generic comparison test
|
||||
// asserts against collections that are too large.
|
||||
const int min_sample_size = 91;
|
||||
|
||||
var securities = StreamHelper.ReadProtos<Security>(
|
||||
ResourceHelper.LoadResource(securities_resource))
|
||||
.Select(s => new SecurityImmutable(s))
|
||||
.ToList();
|
||||
Assert.IsTrue(securities.Count >= min_sample_size);
|
||||
|
||||
ComparisonTests.transitive_comparison(securities, SecurityHelper.Comparer.InstanceImmutable);
|
||||
ComparisonTests.identity_comparison(securities, SecurityHelper.Comparer.InstanceImmutable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using bts.test;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.mcore {
|
||||
static class ComparisonTests {
|
||||
public static void transitive_comparison<T>(IEnumerable<T> items, IComparer<T> comparer) {
|
||||
// Verify that the sample size isn't too big.
|
||||
// This test compares each item to every other item
|
||||
// (technically twice; lhs/rhs is flipped the second time),
|
||||
// so performance is O(n^2).
|
||||
const int max_sample_size = 4092;
|
||||
var sorted_items = items.ToList();
|
||||
Assert.That(sorted_items.Count <= max_sample_size);
|
||||
sorted_items.Sort(comparer);
|
||||
for (int i = 0; i < sorted_items.Count - 1; i++) {
|
||||
for (int j = i + 1; j < sorted_items.Count; j++) {
|
||||
assert_comp(i, j);
|
||||
}
|
||||
}
|
||||
void assert_comp(int lhs, int rhs) {
|
||||
int pos_comp = lhs.CompareTo(rhs);
|
||||
// hit both sides for good measure
|
||||
Assert.That(comparer.Compare(sorted_items[lhs], sorted_items[rhs]) == pos_comp);
|
||||
Assert.That(comparer.Compare(sorted_items[rhs], sorted_items[lhs]) == -pos_comp);
|
||||
}
|
||||
}
|
||||
|
||||
public static void identity_comparison<T>(IEnumerable<T> items, IComparer<T> comparer) {
|
||||
foreach (var v in items)
|
||||
Assert.That(comparer.Compare(v, v) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public static async Task<IEnumerable<SecurityImmutable>> get_multiple_securities
|
||||
return Enumerable.Empty<SecurityImmutable>();
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<FutureContract>> get_expirations(MarketDataRelay relay, string symbol, SecurityType security_type, MDRequest.Types.Flags flags = MDRequest.Types.Flags.Bbo, Venue v = Venue.Sim) {
|
||||
public static async Task<(bool, List<FutureContract>)> get_expirations(MarketDataRelay relay, string symbol, SecurityType security_type, MDRequest.Types.Flags flags = MDRequest.Types.Flags.Bbo, Venue v = Venue.Sim) {
|
||||
var request = new MDRequest() {
|
||||
Host = _MD_HOST,
|
||||
Channels = {
|
||||
@@ -89,7 +89,7 @@ public static async Task<IEnumerable<SecurityImmutable>> get_multiple_securities
|
||||
}
|
||||
}
|
||||
|
||||
return (IEnumerable<FutureContract>) futures;
|
||||
return (result.success, futures);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ namespace bts.tests
|
||||
public class SpreaderUtilities {
|
||||
|
||||
public static async Task<(SecurityImmutable s1, SecurityImmutable s2)> get_securities_for_common_setup_one(MarketDataRelay relay, string symbol) {
|
||||
var exps = (await MarketDataUtilities.get_expirations(relay, symbol, SecurityType.Future)).ToList();
|
||||
var (success, exps) = await MarketDataUtilities.get_expirations(relay, symbol, SecurityType.Future);
|
||||
|
||||
Assert.IsTrue(success, "Could not reach market data service");
|
||||
Assert.IsNotNull(exps);
|
||||
Assert.IsTrue(exps.Count() > 2, "Need at least two expirations");
|
||||
|
||||
|
||||
@@ -14,5 +14,7 @@
|
||||
<ItemGroup>
|
||||
<Content Include="../spark_test/system_test_scripts/*.sh" CopyToOutputDirectory="PreserveNewest"></Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\KrandallOrderBookCrash.security_proto" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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
|
||||
@@ -685,6 +685,7 @@ bts::api::admin::BTSAccount* AdminServiceUtils::create_account(bts::api::admin::
|
||||
new_account_ptr->set_name(account_name);
|
||||
new_account_ptr->set_description(description);
|
||||
new_account_ptr->set_is_active(is_active);
|
||||
new_account_ptr->mutable_risk_limit_enforcement()->set_ignore_spread_position_limits(true);
|
||||
return new_account_ptr;
|
||||
}
|
||||
void AdminServiceUtils::update_account(bts::api::admin::AdminData& admin_data,
|
||||
|
||||
@@ -104,6 +104,7 @@ message CustomOrderDefinition
|
||||
SplitterOrderDefinition splitter = 29;
|
||||
ScratchOrderDefinition scratch = 31;
|
||||
ProRataOrderDefinition prorata = 32;
|
||||
DeleteAndCoverDefinition delete_and_cover = 34;
|
||||
|
||||
DynamicOrderDefinition dynamic = 1000;
|
||||
}
|
||||
@@ -144,6 +145,13 @@ message TickOrderDefinition {
|
||||
bool detach_on_toward = 5;
|
||||
CustomOrderDefinition child = 6;
|
||||
}
|
||||
enum LeanOnToward
|
||||
{
|
||||
NO_CHANGE = 0; //continue to lean on initial inside market
|
||||
CANCEL = 1; //cancel algo and order
|
||||
DETACH = 2; //cancel algo but leave order working
|
||||
REPRICE = 3; //lean on new inside market
|
||||
}
|
||||
message LeanOrderDefinition {
|
||||
spark.QuantityMeasure cross_threshold = 1;
|
||||
uint32 max_chase_ticks = 2;
|
||||
@@ -179,6 +187,21 @@ message CoverOrderDefinition {
|
||||
CustomOrderDefinition child = 3;
|
||||
bool cancel_on_external_hedge_cancel = 4;
|
||||
bool cancel_hedges_on_cancel = 5;
|
||||
bool detach_hedges = 6;
|
||||
}
|
||||
message DeleteAndCoverDefinition{
|
||||
// cover
|
||||
uint32 hedge_ticks = 1;
|
||||
bool cancel_on_external_hedge_cancel = 2;
|
||||
bool cancel_hedges_on_cancel = 3;
|
||||
bool detach_hedges = 4;
|
||||
// main child = top
|
||||
spark.QuantityMeasure top_threshold = 10;
|
||||
uint32 ticks_off = 11;
|
||||
// hedge child = Lean
|
||||
spark.QuantityMeasure cross_threshold = 20;
|
||||
uint32 max_chase_ticks = 21;
|
||||
LeanOnToward trade_toward_behavior = 22;
|
||||
}
|
||||
message OffsetOrderDefinition {
|
||||
uint32 tick_offset_on_deploy = 1;
|
||||
@@ -383,7 +406,9 @@ message OCOOrderState {}
|
||||
message PricerOrderState {}
|
||||
message ScratchOrderState {}
|
||||
message ProRataOrderState {}
|
||||
message DynamicOrderState {}
|
||||
message DynamicOrderState {
|
||||
map<string,DynamicOrderParam> custom_state = 1;
|
||||
}
|
||||
message QueueHolderState {}
|
||||
message SplitterOrderState {}
|
||||
|
||||
@@ -644,7 +669,7 @@ message RegisteredDynamicOrderTypes {
|
||||
|
||||
message DynamicOrderParam {
|
||||
oneof value {
|
||||
uint32 as_int32 = 1;
|
||||
int32 as_int32 = 1;
|
||||
uint32 as_uint32 = 2;
|
||||
double as_double = 3;
|
||||
string as_string = 4;
|
||||
|
||||
@@ -52,6 +52,11 @@ message PublisherOptions
|
||||
optional uint32 heartbeat_boot_seconds = 12 [default = 60]; // 1 minute grace period from shm creation
|
||||
|
||||
optional bool skip_options = 13 [default = true];
|
||||
|
||||
optional uint32 hot_initial = 20 [default = 1024];
|
||||
optional uint32 hot_growth = 21 [default = 1024];
|
||||
optional uint32 cold_initial = 22 [default = 8192];
|
||||
optional uint32 cold_growth = 23 [default = 2048];
|
||||
}
|
||||
|
||||
message ICEPublisherOptions
|
||||
@@ -193,6 +198,11 @@ message RecordOptions {
|
||||
optional bool record_sim = 5 [default=false];
|
||||
}
|
||||
|
||||
// If starting on Sunday pre-load the selected symbols from disk
|
||||
message SecurityPreload {
|
||||
repeated string symbol = 1;
|
||||
}
|
||||
|
||||
//
|
||||
// Replay options
|
||||
//
|
||||
|
||||
@@ -76,6 +76,7 @@ message LadderKnobs {
|
||||
message OrderBookOptions {
|
||||
optional bool animate_child_order_rows = 1 [default = false];
|
||||
optional double status_color_opacity_when_selected = 2 [default = 0.35];
|
||||
optional bool can_edit_specific_algo_params = 3 [default = false];
|
||||
}
|
||||
|
||||
message OrderTicketOptions {
|
||||
|
||||
@@ -67,4 +67,6 @@ message DynamicAlgos{
|
||||
optional bool enable_dorman = 1 [default = false];
|
||||
optional bool enable_pyramid = 2 [default = false];
|
||||
optional bool enable_tas = 3 [default = false];
|
||||
optional bool enable_tascounter = 4 [default = false];
|
||||
optional bool enable_limit_cover = 5 [default = false];
|
||||
}
|
||||
@@ -41,16 +41,16 @@ inline api::Side other_side(api::Side side) {
|
||||
}
|
||||
}
|
||||
|
||||
inline double market_epislon() { return 1e-10; }
|
||||
inline double market_epsilon() { return 1e-10; }
|
||||
|
||||
// better == p1 more aggressive than p1 ie for a buy order p1 is higher
|
||||
// than p2, and for a sell order p1 is lower than p2
|
||||
inline bool is_better(api::Side side, Price p1, Price p2) {
|
||||
switch (side) {
|
||||
case api::SIDE_BUY:
|
||||
return p1.value() - p2.value() > market_epislon();
|
||||
return p1.value() - p2.value() > market_epsilon();
|
||||
case api::SIDE_SELL:
|
||||
return p2.value() - p1.value() > market_epislon();
|
||||
return p2.value() - p1.value() > market_epsilon();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ inline Price worsen_price(api::Side side, Price p, Price offset) {
|
||||
}
|
||||
|
||||
inline bool is_same(Price p1, Price p2) {
|
||||
return std::abs(p1.value() - p2.value()) < market_epislon();
|
||||
return std::abs(p1.value() - p2.value()) < market_epsilon();
|
||||
}
|
||||
|
||||
inline bool is_equal_or_better(api::Side side, Price p1, Price p2) {
|
||||
@@ -212,5 +212,5 @@ inline bool gr_than(double p1, double p2, double tick_size) {
|
||||
!test_price_equality(p1, p2, tick_size);
|
||||
}
|
||||
|
||||
|
||||
}}
|
||||
} // namespace securities
|
||||
} // namespace bts
|
||||
@@ -792,7 +792,7 @@ public:
|
||||
// We can safely send a security key to every _instance because by definition
|
||||
// a security cannot appear in 2 instances
|
||||
if (unresolved_securities.size() > 250) {
|
||||
LOG(md,Warn,"Failed to resolve legs for {}"
|
||||
LOG(md,Info,"Failed to resolve legs for {}"
|
||||
" spreads. This can happen when a spread is subscribed without "
|
||||
"it's underliers and may lead to higher than expected CPU use.",
|
||||
unresolved_securities.size() );
|
||||
@@ -901,6 +901,10 @@ public:
|
||||
return _cache.get_security(key);
|
||||
}
|
||||
|
||||
std::vector<api::Security const *> get_securities() const {
|
||||
return _cache.get_securities();
|
||||
}
|
||||
|
||||
MDConsumerChannel get_channel(const std::string& symbol, SecurityType type, Venue exchange) const {
|
||||
for (auto& inst : _instances) {
|
||||
if ((inst->symbol() == symbol && inst->type() == type && inst->exchange() == exchange) ||
|
||||
@@ -976,6 +980,7 @@ size_t MDConsumer::walk_security_list(SecurityCallback cb, const string& symbol,
|
||||
size_t MDConsumer::scan_securities(bool resolve_legs) { return _pimpl->scan_securities(resolve_legs); }
|
||||
void MDConsumer::on_security(SecurityCallback cb, bool resolve_legs) { _pimpl->on_security(cb, resolve_legs); }
|
||||
const api::Security* MDConsumer::get_security(SecurityKey key) const { return _pimpl->get_security(key).get(); }
|
||||
std::vector<api::Security const*> MDConsumer::get_securities() const {return _pimpl->get_securities();}
|
||||
|
||||
MDConsumerChannel MDConsumer::get_channel(const std::string& symbol, SecurityType type, Venue exchange) const {
|
||||
return _pimpl->get_channel(symbol, type, exchange);
|
||||
|
||||
@@ -59,6 +59,9 @@ public:
|
||||
// You still need to call scan_securities() periodically to discover new securities
|
||||
const api::Security* get_security(SecurityKey key) const;
|
||||
|
||||
// Retrieve all cached securities
|
||||
std::vector<api::Security const*> get_securities() const;
|
||||
|
||||
MDConsumerChannel get_channel(const std::string& symbol, SecurityType type, Venue exchange) const;
|
||||
MDConsumerChannel get_channel(const UserSymbol&) const;
|
||||
|
||||
|
||||
@@ -20,8 +20,14 @@ public:
|
||||
_symbol(symbol),
|
||||
_type(type),
|
||||
_exchange(exchange),
|
||||
_hot(hot_segment_name(symbol, type, exchange, platform), 1024, 1024, flags & MDProducerFlags::Shared),
|
||||
_cold(cold_segment_name(symbol, type, exchange, platform), 8192, 2048, flags & MDProducerFlags::Shared),
|
||||
_hot(hot_segment_name(symbol, type, exchange, platform),
|
||||
config::get<PublisherOptions>().hot_initial(),
|
||||
config::get<PublisherOptions>().hot_growth(),
|
||||
flags & MDProducerFlags::Shared),
|
||||
_cold(cold_segment_name(symbol, type, exchange, platform),
|
||||
config::get<PublisherOptions>().cold_initial(),
|
||||
config::get<PublisherOptions>().cold_growth(),
|
||||
flags & MDProducerFlags::Shared),
|
||||
_trades(_cold.header().trade_buffer, BTS_ARRAYSIZE(_cold.header().trade_buffer), true),
|
||||
_rfqs(_cold.header().rfq_buffer, BTS_ARRAYSIZE(_cold.header().rfq_buffer), true)
|
||||
{
|
||||
|
||||
@@ -69,6 +69,17 @@ SecurityPtr SecurityCache::get_security(SecurityKey key) {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<api::Security const*> SecurityCache::get_securities()
|
||||
{
|
||||
std::vector<api::Security const*> result;
|
||||
result.reserve(100000);
|
||||
|
||||
std::unique_lock<std::mutex> lock(_me->cache_mutex);
|
||||
for (auto const&p : _me->cache)
|
||||
result.push_back(p.second.get());
|
||||
return result;
|
||||
}
|
||||
|
||||
SecurityPtr SecurityCache::add_db_security(const SecurityPtr& security) {
|
||||
bts_assert(security);
|
||||
std::lock_guard<std::mutex> lock(_me->cache_mutex);
|
||||
@@ -90,4 +101,4 @@ bool SecurityCache::add_live_security(const SecurityPtr& security) {
|
||||
|
||||
void SecurityCache::flush_to_disk() { _me->flush(); }
|
||||
|
||||
}}//ns
|
||||
}}//ns
|
||||
@@ -22,6 +22,9 @@ public:
|
||||
//Load security from cache with fallback to database
|
||||
SecurityPtr get_security(SecurityKey key);
|
||||
|
||||
// Load all securities from cache, no fallback to database
|
||||
std::vector<api::Security const*> get_securities();
|
||||
|
||||
// Get the security if it's in the cache otherwise return nullptr. Does not query security database.
|
||||
SecurityPtr try_get_security(SecurityKey key) const;
|
||||
|
||||
@@ -33,4 +36,4 @@ public:
|
||||
|
||||
};
|
||||
|
||||
}}//ns
|
||||
}}//ns
|
||||
@@ -10,4 +10,8 @@ const api::Security* SecurityOracle::get_security(SecurityKey key) const {
|
||||
return _client.current_consumer().get_security(key);
|
||||
}
|
||||
|
||||
}}//namespace
|
||||
std::vector<api::Security const*> SecurityOracle::get_securities() const {
|
||||
return _client.current_consumer().get_securities();
|
||||
}
|
||||
|
||||
}}//namespace
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/domain_model/SecurityKey.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace bts {
|
||||
namespace api {class Security;}
|
||||
@@ -12,6 +14,7 @@ public:
|
||||
SecurityOracle(const MDClient&);
|
||||
~SecurityOracle();
|
||||
const api::Security* get_security(SecurityKey key) const;
|
||||
std::vector<api::Security const*> get_securities() const;
|
||||
};
|
||||
|
||||
}}//namespace
|
||||
}}//namespace
|
||||
Vendored
+4
-4
@@ -18,16 +18,16 @@ const char * onixs_license[] = {
|
||||
" <name>Blue Trading Systems OF 9420-2</name>",
|
||||
" </customer>",
|
||||
" <period>",
|
||||
" <since>2019-05-02</since>",
|
||||
" <till>2020-05-13</till>",
|
||||
" <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>5E50A1DACD94D7C1BECD316EEAE5AB02</checksum>",
|
||||
"</license>",
|
||||
" <checksum>1C9210495AA3C9C884B7A37A4C7AD8F9</checksum>",
|
||||
"</license>"
|
||||
};
|
||||
|
||||
OnixsTemplLicense onixs_create_temporary_license() {
|
||||
|
||||
@@ -154,10 +154,13 @@ void OrderStore::store_and_publish_if_complete(
|
||||
|
||||
bool OrderStore::_order_cutoff(const api::orderman::OrderRecord &rec) {
|
||||
// TODO: move this to orderutils
|
||||
bool working = (rec.state().status() ==
|
||||
api::orderman::OrderStatus::ORDER_STATUS_WORKING) ||
|
||||
(rec.state().status() ==
|
||||
api::orderman::OrderStatus::ORDER_STATUS_PENDING_WORKING);
|
||||
bool working =
|
||||
(rec.state().status() ==
|
||||
api::orderman::OrderStatus::ORDER_STATUS_WORKING) ||
|
||||
(rec.state().status() ==
|
||||
api::orderman::OrderStatus::ORDER_STATUS_DISCONNECTED) ||
|
||||
(rec.state().status() ==
|
||||
api::orderman::OrderStatus::ORDER_STATUS_PENDING_WORKING);
|
||||
return me->soft_cleanup_enabled && !working &&
|
||||
rec.state().created().seconds() < me->cutoff_time.tv_sec;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include <google/protobuf/timestamp.pb.h>
|
||||
#include <google/protobuf/util/time_util.h>
|
||||
#include <sstream>
|
||||
#include <sys/time.h>
|
||||
|
||||
@@ -53,7 +54,7 @@ timeval SODPosition::sod_time() {
|
||||
// if the server started before the sod for today
|
||||
if (rv.tv_sec >= now_epoch)
|
||||
rv = subtract_days (rv,1);
|
||||
return rv;
|
||||
return rv;
|
||||
}
|
||||
|
||||
timeval SODPosition::get_order_cutoff_time(
|
||||
@@ -98,6 +99,48 @@ timeval SODPosition::now(){
|
||||
return tv;
|
||||
}
|
||||
|
||||
google::protobuf::Timestamp SODPosition::today_local_time_to_utc_timestamp (
|
||||
uint16_t hours,
|
||||
uint16_t mins,
|
||||
uint16_t secs,
|
||||
bool add_day_if_less,
|
||||
bool subtract_day_if_more){
|
||||
time_t now_epoch;
|
||||
time(&now_epoch); // seconds since epoch (utc) (independent of machine timezone)
|
||||
auto now_ts = google::protobuf::util::TimeUtil::TimeTToTimestamp(now_epoch);
|
||||
struct tm *ptm_local = localtime(&now_epoch); // in local timezone
|
||||
ptm_local->tm_hour = hours;
|
||||
ptm_local->tm_min = mins;
|
||||
ptm_local->tm_sec = secs;
|
||||
ptm_local->tm_isdst = -1;
|
||||
time_t ovrd_epoch = mktime(ptm_local); // converts local time to UTC epoch time
|
||||
if(ovrd_epoch < now_epoch){
|
||||
if (add_day_if_less)
|
||||
ovrd_epoch += 24*60*60;
|
||||
}
|
||||
else{
|
||||
if(subtract_day_if_more)
|
||||
ovrd_epoch -= 24*60*60;
|
||||
}
|
||||
auto ovrd_ts = google::protobuf::util::TimeUtil::TimeTToTimestamp(ovrd_epoch);
|
||||
return ovrd_ts;
|
||||
}
|
||||
|
||||
google::protobuf::Timestamp SODPosition::add_seconds (
|
||||
const google::protobuf::Timestamp& ts,
|
||||
int secs_diff){
|
||||
auto secs = google::protobuf::util::TimeUtil::TimestampToSeconds(ts);
|
||||
secs += secs_diff;
|
||||
return google::protobuf::util::TimeUtil::SecondsToTimestamp(secs);
|
||||
}
|
||||
|
||||
google::protobuf::Timestamp SODPosition::add_nanoseconds (
|
||||
const google::protobuf::Timestamp& ts,
|
||||
long diff){
|
||||
return google::protobuf::util::TimeUtil::NanosecondsToTimestamp(
|
||||
google::protobuf::util::TimeUtil::TimestampToNanoseconds(ts) + diff);
|
||||
}
|
||||
|
||||
google::protobuf::Timestamp SODPosition::to_timestamp (timeval tv){
|
||||
google::protobuf::Timestamp rv;
|
||||
rv.set_seconds(tv.tv_sec);
|
||||
|
||||
@@ -31,6 +31,18 @@ public:
|
||||
const std::map<std::string,std::string>& ordermap);
|
||||
static timeval now();
|
||||
static google::protobuf::Timestamp to_timestamp (timeval);
|
||||
static google::protobuf::Timestamp today_local_time_to_utc_timestamp (
|
||||
uint16_t hours,
|
||||
uint16_t mins,
|
||||
uint16_t secs,
|
||||
bool add_day_if_less,
|
||||
bool subtract_day_if_more);
|
||||
static google::protobuf::Timestamp add_seconds (
|
||||
const google::protobuf::Timestamp& ts,
|
||||
int secs);
|
||||
static google::protobuf::Timestamp add_nanoseconds (
|
||||
const google::protobuf::Timestamp& ts,
|
||||
long diff);
|
||||
bool is_sod (const google::protobuf::Timestamp& fill_time) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ void OrderManager::on_filled_position(
|
||||
}
|
||||
|
||||
for (auto const &ev : *(update.events)) {
|
||||
me->services_data.position.update(ev);
|
||||
me->services_data.limiter.set_filled_position(
|
||||
*ev.security, ev.account_id, ev.long_quantity, ev.short_quantity);
|
||||
}
|
||||
@@ -550,6 +551,8 @@ void OrderManager::init_order_services() {
|
||||
std::make_unique<ContentSubscription<TimerEvent, NoKey>>(*this, log);
|
||||
me->services_data.order_creation_subs =
|
||||
std::make_unique<ContentSubscription<CreateOrderReplyEvent>>(*this, log);
|
||||
me->services_data.position_subs =
|
||||
std::make_unique<ContentSubscription<FilledPositionEvent,NoKey>>(*this, log);
|
||||
}
|
||||
|
||||
void OrderManager::after_event() {
|
||||
|
||||
@@ -32,6 +32,7 @@ void OrderServices::init(OrderServicesData &data) {
|
||||
_status_updates.init(*data.status_subs);
|
||||
_settlement_updates.init(*data.settlement_subs);
|
||||
_time_change.init(*data.time_subs);
|
||||
_position_update.init(*data.position_subs);
|
||||
if (_sessions.empty())
|
||||
for (auto &sd : data.sessions)
|
||||
_sessions.emplace_back(*sd);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "lib/orderman/actors/order_manager/SessionServices.h"
|
||||
#include "lib/orderman/events/TickerUpdateEvent.h"
|
||||
#include "lib/orderman/events/StatusUpdateEvent.h"
|
||||
#include "lib/orderman/events/FilledPositionEvent.h"
|
||||
#include "lib/orderman/governor/Governor.h"
|
||||
#include "lib/orderman/orderman_utils/EventStream.h"
|
||||
#include "lib/orderman/orders/Order.h"
|
||||
@@ -62,6 +63,10 @@ class OrderServices {
|
||||
_data->log.log(corid(), entry_kind, uint64_1, txt_1, uint32_1);
|
||||
}
|
||||
|
||||
std::vector<PositionUpdate> const& current_positions() {
|
||||
return _data->position.current_positions();
|
||||
}
|
||||
|
||||
trace::ScopedPerfLogEntry log_scope(const char *entry_kind,
|
||||
uint64_t uint64_1 = 0,
|
||||
const char *txt_1 = "",
|
||||
@@ -123,6 +128,15 @@ class OrderServices {
|
||||
void unsubscribe_from_time() { _time_change.unsub(_time_change_sub); }
|
||||
// Time
|
||||
|
||||
|
||||
// Position Updates
|
||||
template <class CB> void subscribe_to_position_updates(CB &&cb) {
|
||||
_position_update_sub = _position_update.sub(
|
||||
[this, cb](auto const &ev) { this->on_external_event(ev, cb); });
|
||||
}
|
||||
void unsubscribe_from_position_updates() { _position_update.unsub(_position_update_sub); }
|
||||
// Position Updates
|
||||
|
||||
// Execution Reports
|
||||
using ExeReportSubID = EventStream<NewExecutionReportEvent>::SubID;
|
||||
template <class CB>
|
||||
@@ -298,8 +312,10 @@ class OrderServices {
|
||||
EventStream<StatusUpdateEvent> _status_updates;
|
||||
EventStream<SettlementUpdateEvent> _settlement_updates;
|
||||
EventStream<mercury::TimerEvent, mercury::NoKey> _time_change;
|
||||
EventStream<orderman::FilledPositionEvent, mercury::NoKey> _position_update;
|
||||
boost::container::small_vector<SessionServices, 20> _sessions;
|
||||
EventStream<mercury::TimerEvent, mercury::NoKey>::SubID _time_change_sub;
|
||||
EventStream<orderman::FilledPositionEvent, mercury::NoKey>::SubID _position_update_sub;
|
||||
EventStream<CreateOrderReplyEvent> _create_order_reply;
|
||||
}; // namespace orderman
|
||||
} // namespace orderman
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "lib/orderman/order_router/OrderRouter.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include "lib/utils/Puid.h"
|
||||
#include "lib/orderman/actors/order_manager/PositionIndex.h"
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
@@ -87,6 +88,7 @@ class OrderServicesData {
|
||||
OrderManager &owner;
|
||||
trace::PerfLog &log;
|
||||
securities::MapSecurityDB sec_db;
|
||||
PositionIndex position;
|
||||
OrderRouter order_router;
|
||||
Limiter limiter;
|
||||
OrderFactory order_factory;
|
||||
@@ -101,6 +103,8 @@ class OrderServicesData {
|
||||
time_subs;
|
||||
std::unique_ptr<mercury::ContentSubscription<CreateOrderReplyEvent>>
|
||||
order_creation_subs;
|
||||
std::unique_ptr<mercury::ContentSubscription<FilledPositionEvent,mercury::NoKey>>
|
||||
position_subs;
|
||||
|
||||
mercury::Stream<CreateOrderRequestEvent> create_order_req_pub;
|
||||
mercury::Stream<CreateFIXBufferRequestEvent> create_fix_req_pub;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "PositionIndex.h"
|
||||
|
||||
#include "lib/orderman/events/FilledPositionEvent.h"
|
||||
#include "lib/orderman/orders/algo/PositionUpdate.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
struct PositionIndex::PImpl {
|
||||
PImpl() : position_idx(), current_positions() {}
|
||||
std::map<std::pair<SecurityKey,AccountID>,size_t> position_idx;
|
||||
std::vector<PositionUpdate> current_positions;
|
||||
};
|
||||
|
||||
void PositionIndex::update(FilledPositionEvent const&ev) {
|
||||
auto &idx = me->position_idx[{SecurityKey(ev.security->id()),ev.account_id}];
|
||||
if (idx == 0) {
|
||||
me->current_positions.push_back({});
|
||||
idx = me->current_positions.size();
|
||||
}
|
||||
me->current_positions[idx-1] = {ev.security,ev.account_id,ev.long_quantity, ev.short_quantity};
|
||||
}
|
||||
|
||||
std::vector<PositionUpdate> const& PositionIndex::current_positions() const {
|
||||
return me->current_positions;
|
||||
}
|
||||
|
||||
PositionIndex::PositionIndex() : me (new PImpl()) {}
|
||||
PositionIndex::~PositionIndex() {}
|
||||
|
||||
}}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class FilledPositionEvent;
|
||||
struct PositionUpdate;
|
||||
|
||||
class PositionIndex {
|
||||
public:
|
||||
PositionIndex();
|
||||
~PositionIndex();
|
||||
void update(FilledPositionEvent const&);
|
||||
std::vector<PositionUpdate> const ¤t_positions() const;
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
};
|
||||
|
||||
}}
|
||||
@@ -11,16 +11,16 @@ namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
|
||||
public:
|
||||
public:
|
||||
using SynopsisPolicy = mercury::CacheAll<OrderStateUpdateEvent>;
|
||||
|
||||
OrderStateUpdateEvent()
|
||||
: key(null_order_key()), parent_key(null_order_key()) {}
|
||||
|
||||
OrderStateUpdateEvent(
|
||||
OrderKey k, OrderKey pk,
|
||||
std::shared_ptr<const api::orderman::OrderDefinition> const &def,
|
||||
api::orderman::OrderState const &state) {
|
||||
OrderKey k, OrderKey pk,
|
||||
std::shared_ptr<const api::orderman::OrderDefinition> const &def,
|
||||
api::orderman::OrderState const &state) {
|
||||
init(k, pk, def, state);
|
||||
}
|
||||
|
||||
@@ -41,18 +41,21 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
|
||||
display_status = state.display_status();
|
||||
|
||||
custom = state.custom_case();
|
||||
|
||||
auto native_state = get_native_state(state);
|
||||
if (native_state) {
|
||||
exchange_order_id = native_state->exchange_order_id();
|
||||
last_msg_seq_num = native_state->last_msg_seq_num();
|
||||
sub_error_code = native_state->sub_error_code();
|
||||
native_status = native_state->native_status();
|
||||
routing_record = native_state->routing_record();
|
||||
native_type = native_state->native_type();
|
||||
orderman::str_to(native_state->last_transact_time(),
|
||||
&last_transact_time);
|
||||
stop_px = native_state->stop_px();
|
||||
if (custom == api::orderman::OrderState::kDynamic) {
|
||||
dynamic_order_state = state.dynamic();
|
||||
} else {
|
||||
auto native_state = get_native_state(state);
|
||||
if (native_state) {
|
||||
exchange_order_id = native_state->exchange_order_id();
|
||||
last_msg_seq_num = native_state->last_msg_seq_num();
|
||||
sub_error_code = native_state->sub_error_code();
|
||||
native_status = native_state->native_status();
|
||||
routing_record = native_state->routing_record();
|
||||
native_type = native_state->native_type();
|
||||
orderman::str_to(native_state->last_transact_time(),
|
||||
&last_transact_time);
|
||||
stop_px = native_state->stop_px();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +73,6 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
|
||||
uint32_t filled_at_attach_time_quantity;
|
||||
std::string last_msg;
|
||||
api::orderman::OrderState::DisplayStatus display_status;
|
||||
|
||||
api::orderman::OrderState::CustomCase custom;
|
||||
|
||||
// native order state
|
||||
@@ -82,7 +84,10 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
|
||||
api::orderman::NativeOrderType native_type;
|
||||
mercury::TimeStampStr last_transact_time;
|
||||
double stop_px;
|
||||
|
||||
|
||||
// dynamic order state
|
||||
std::optional<api::orderman::DynamicOrderState> dynamic_order_state;
|
||||
|
||||
api::orderman::OrderState quick_rebuild_state() const {
|
||||
api::orderman::OrderState result;
|
||||
result.set_status(status);
|
||||
@@ -111,11 +116,11 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
|
||||
break;
|
||||
case api::orderman::OrderState::kStopLimit:
|
||||
rebuild_native_state(
|
||||
result.mutable_stop_limit()->mutable_native_state());
|
||||
result.mutable_stop_limit()->mutable_native_state());
|
||||
break;
|
||||
case api::orderman::OrderState::kGlbxIceberg:
|
||||
rebuild_native_state(
|
||||
result.mutable_glbx_iceberg()->mutable_native_state());
|
||||
result.mutable_glbx_iceberg()->mutable_native_state());
|
||||
break;
|
||||
case api::orderman::OrderState::kIceberg:
|
||||
result.mutable_iceberg();
|
||||
@@ -163,7 +168,7 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
|
||||
result.mutable_prorata();
|
||||
break;
|
||||
case api::orderman::OrderState::kDynamic:
|
||||
result.mutable_dynamic();
|
||||
*result.mutable_dynamic() = dynamic_order_state.value();
|
||||
break;
|
||||
case api::orderman::OrderState::kSplitter:
|
||||
result.mutable_splitter();
|
||||
@@ -175,7 +180,7 @@ class OrderStateUpdateEvent : public mercury::Event<OrderStateUpdateEvent> {
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
void rebuild_native_state(api::orderman::NativeOrderState *st) const {
|
||||
st->set_stop_px(stop_px);
|
||||
st->set_exchange_order_id(exchange_order_id);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/OffsetAlgo.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/ScratchAlgo.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/ProRataAlgo.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/DeleteAndCoverAlgo.h"
|
||||
#include "lib/orderman/orders/book_algos/OCOOrder.h"
|
||||
#include "lib/orderman/orders/book_algos/PricerOrder.h"
|
||||
#include "lib/orderman/orders/spreader_orders/SpreaderOrder.h"
|
||||
@@ -242,6 +243,10 @@ Order &OrderFactory::allocate(OrderKey k, OrderKey pk,
|
||||
order_ptr.reset(
|
||||
new orderman::AlgoWrapperOrder(std::make_unique<splitter::SplitterAlgo>()));
|
||||
break;
|
||||
case CustomOrderDefinition::kDeleteAndCover:
|
||||
order_ptr.reset(
|
||||
new orderman::AlgoWrapperOrder(std::make_unique<DeleteAndCoverAlgo>()));
|
||||
break;
|
||||
case CustomOrderDefinition::CUSTOM_NOT_SET:
|
||||
bts_assert_msg(false, "Cannot create order with unknown type");
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "BookUtils.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
using Size = bts::md::Size;
|
||||
using Side = bts::api::Side;
|
||||
|
||||
Size BookUtils::volume_at_or_better(const Book& book, Side side, Price price) {
|
||||
return volume_at_or_better(book, side, price.value());
|
||||
}
|
||||
Size BookUtils::volume_at_or_better(const Book& book, Side side, double price) {
|
||||
Size volume = 0;
|
||||
SidePriceComp spc(side);
|
||||
for (const auto& plevel : book.side(side)) {
|
||||
if (spc(price, plevel.price()))
|
||||
break;
|
||||
volume += plevel.size();
|
||||
}
|
||||
return volume;
|
||||
}
|
||||
|
||||
SidePriceComp BookUtils::price_comp(Side side) {
|
||||
return SidePriceComp(side);
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/domain_model/common.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orderman_utils/PriceComp.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
struct BookUtils {
|
||||
static md::Size volume_at_or_better(const Book& book, api::Side side, Price price);
|
||||
static md::Size volume_at_or_better(const Book& book, api::Side side, double price);
|
||||
static SidePriceComp price_comp(api::Side side);
|
||||
};
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,283 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/domain_model/common.h"
|
||||
#include "lib/md/PriceSize.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
struct Formatter {
|
||||
using Side = bts::api::Side;
|
||||
// this is a placeholder for other book formats down the road.
|
||||
enum class BookFormat { BBO = 1 };
|
||||
enum class Tense {
|
||||
Present,
|
||||
Preterite
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline static void concat(std::ostream& ostr, const T& v) {
|
||||
ostr << v;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
inline static void concat(std::ostream& ostr, const T& first, Args... args) {
|
||||
ostr << first;
|
||||
(concat(ostr, args), ...);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline static std::string fmt(const T& curr) {
|
||||
std::stringstream str;
|
||||
str << curr;
|
||||
return str.str();
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
inline static std::string fmt(const T& curr, Args... next) {
|
||||
std::stringstream str;
|
||||
concat(str, curr, next...);
|
||||
return str.str();
|
||||
}
|
||||
|
||||
template <class TKey, class TValue>
|
||||
static std::string fmt_values(const std::map<TKey, TValue>& map) {
|
||||
if (map.empty())
|
||||
return "None";
|
||||
std::stringstream str;
|
||||
auto it = map.begin();
|
||||
str << it->second;
|
||||
while (++it != map.end()) {
|
||||
str << ", " << it->second;
|
||||
}
|
||||
return str.str();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static std::string fmt_values(const std::vector<T>& values) {
|
||||
if (values.empty())
|
||||
return "None";
|
||||
std::stringstream str;
|
||||
auto it = values.begin();
|
||||
str << *it;
|
||||
while (++it != values.end()) {
|
||||
str << ", " << *it;
|
||||
}
|
||||
return str.str();
|
||||
}
|
||||
|
||||
static std::string fmt_fill(Side side, uint32_t qty, double price) {
|
||||
std::stringstream ostr;
|
||||
ostr << to_string(side, Tense::Preterite) << " " << qty << " @ " << price;
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
static std::string fmt_order(Side side, uint32_t qty, double price) {
|
||||
std::stringstream ostr;
|
||||
ostr << to_string(side) << " " << qty << " @ " << price;
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
static std::string fmt_order(OrderKey key, uint32_t qty, double price, const api::orderman::OrderDefinition& def) {
|
||||
std::stringstream ostr;
|
||||
ostr << key << " " << to_string(def.side()) << " " << qty << " @ "
|
||||
<< price << std::endl << "[Def]" << utils::pb::to_string(def);
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
static std::string fmt_order(OrderKey key, Side side, uint32_t qty, double price, ChildState state) {
|
||||
std::stringstream ostr;
|
||||
ostr << key << " " << to_string(side) << " " << qty << " @ " << price
|
||||
<< " " << to_string(state);
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename T, typename... Args>
|
||||
inline static void join_next(std::stringstream&, const std::string&) { }
|
||||
|
||||
template<typename T, typename... Args>
|
||||
inline static void join_next(std::stringstream&, std::function<std::string()>) { }
|
||||
|
||||
template<typename T, typename... Args>
|
||||
inline static void join_next(std::stringstream& str, const std::string& separator, const T& curr) {
|
||||
str << separator << curr;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
inline static void join_next(std::stringstream& str, std::function<std::string()> get_separator, const T& curr) {
|
||||
str << get_separator() << curr;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
inline static void join_next(std::stringstream& str, const std::string& separator, const T& curr, Args... next) {
|
||||
str << separator << curr;
|
||||
(join_next(str, separator, next), ...);
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
inline static void join_next(std::stringstream& str, std::function<std::string()> get_separator, const T& curr, Args... next) {
|
||||
str << get_separator() << curr;
|
||||
(join_next(str, get_separator, next), ...);
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename T, typename... Next>
|
||||
inline static std::string join(const std::string&, const T& curr) {
|
||||
std::stringstream str;
|
||||
str << curr;
|
||||
return str.str();
|
||||
}
|
||||
|
||||
template<typename T, typename... Next>
|
||||
inline static std::string join(const std::string& separator, const T& curr, Next... next) {
|
||||
std::stringstream str;
|
||||
str << curr;
|
||||
join_next(str, separator, next...);
|
||||
return str.str();
|
||||
}
|
||||
|
||||
template<typename T, typename... Next>
|
||||
inline static std::string join(std::function<std::string()> get_separator, const T& curr, Next... next) {
|
||||
std::stringstream str;
|
||||
str << curr;
|
||||
join_next(str, get_separator, next...);
|
||||
return str.str();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static std::string to_string(const T& value) {
|
||||
std::stringstream str;
|
||||
str << value;
|
||||
return str.str();
|
||||
}
|
||||
|
||||
static std::string to_string(bool value) {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
static std::string to_string(Side side, Tense tense = Tense::Present) {
|
||||
switch (side) {
|
||||
case bts::api::SIDE_BUY:
|
||||
switch (tense) {
|
||||
case Tense::Preterite:
|
||||
return "Bought";
|
||||
case Tense::Present:
|
||||
default:
|
||||
return "Buy";
|
||||
}
|
||||
case bts::api::SIDE_SELL:
|
||||
switch (tense) {
|
||||
case Tense::Preterite:
|
||||
return "Sold";
|
||||
case Tense::Present:
|
||||
default:
|
||||
return "Sell";
|
||||
}
|
||||
default:
|
||||
return fmt("Side_", static_cast<int>(side));
|
||||
}
|
||||
}
|
||||
|
||||
static std::string to_string(ChildStatus value) {
|
||||
switch (value) {
|
||||
case ChildStatus::WORKING: return "Working";
|
||||
case ChildStatus::COMPLETED: return "Completed";
|
||||
case ChildStatus::CANCELED: return "Canceled";
|
||||
default: return fmt("ChildStatus_", static_cast<int>(value));
|
||||
}
|
||||
}
|
||||
|
||||
static std::string to_string(ChildState value) {
|
||||
std::stringstream ostr;
|
||||
ostr << "[" << to_string(value.status) << "] "
|
||||
<< value.quantity.value() << " @ " << value.price.value()
|
||||
<< "(total filled " << value.total_fills.value() << " @[avg] "
|
||||
<< value.avg_price.value() << ", last filled "
|
||||
<< value.last_fills.value() << " @ "
|
||||
<< value.last_price.value() << ")";
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
static std::string to_string(const Book &book) {
|
||||
return to_string(book, BookFormat::BBO);
|
||||
}
|
||||
|
||||
static std::string to_string(const Book &book, BookFormat format) {
|
||||
// add other impls as needed
|
||||
std::stringstream ostr;
|
||||
switch (format) {
|
||||
case BookFormat::BBO:
|
||||
if (book.bid().size() > 0 && !book.bid().at(0).is_nan()) {
|
||||
auto best_bid = book.bid().at(0);
|
||||
ostr << best_bid.size() << "@" << best_bid.price();
|
||||
} else {
|
||||
ostr << "N/A";
|
||||
}
|
||||
ostr << " <> ";
|
||||
if (book.ask().size() > 0 && !book.ask().at(0).is_nan()) {
|
||||
auto best_ask = book.ask().at(0);
|
||||
ostr << best_ask.size() << "@" << best_ask.price();
|
||||
} else {
|
||||
ostr << "N/A";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
static std::string to_string(api::orderman::OrderError error) {
|
||||
return api::orderman::OrderError_Name(error);
|
||||
}
|
||||
|
||||
static std::string to_string(api::orderman::CustomOrderDefinition::CustomCase algo_type) {
|
||||
switch (algo_type) {
|
||||
case api::orderman::CustomOrderDefinition::CustomCase::kLimit: return "Limit";
|
||||
case api::orderman::CustomOrderDefinition::CustomCase::kOffset: return "Offset";
|
||||
case api::orderman::CustomOrderDefinition::CustomCase::kCover: return "Cover";
|
||||
case api::orderman::CustomOrderDefinition::CustomCase::kIceberg: return "Iceberg";
|
||||
default: return fmt("AlgoType_", static_cast<int>(algo_type));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// << operators
|
||||
inline std::ostream &operator<<(std::ostream &ostr, const api::Side &side) {
|
||||
ostr << Formatter::to_string(side);
|
||||
return ostr;
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &ostr, const ChildStatus &child_status) {
|
||||
ostr << Formatter::to_string(child_status);
|
||||
return ostr;
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &ostr, const ChildState &child_state) {
|
||||
ostr << "[" << child_state.status << "] " << child_state.quantity.value()
|
||||
<< " @ " << child_state.price.value() << "(filled "
|
||||
<< child_state.total_fills.value() << " @avg "
|
||||
<< child_state.avg_price.value() << ")";
|
||||
return ostr;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const bts::Price& price) {
|
||||
ostr << price.value();
|
||||
return ostr;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const bts::Quantity& quantity) {
|
||||
ostr << quantity.value();
|
||||
return ostr;
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "lib/domain_model/Security.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
struct PriceComp {
|
||||
bool operator()(const Price& lhs, const Price& rhs) { return lhs.value() > rhs.value(); }
|
||||
bool operator()(const double& lhs, const double& rhs) { return lhs > rhs; }
|
||||
};
|
||||
|
||||
struct ReversePriceComp {
|
||||
bool operator()(const Price& lhs, const Price& rhs) { return lhs.value() < rhs.value(); }
|
||||
bool operator()(const double& lhs, const double& rhs) { return lhs < rhs; }
|
||||
};
|
||||
|
||||
struct SidePriceComp {
|
||||
SidePriceComp(api::Side side) : _side(side) {}
|
||||
|
||||
api::Side side() const { return _side; }
|
||||
|
||||
bool operator()(const Price& lhs, const Price& rhs) {
|
||||
return _side == api::SIDE_SELL ? lhs.value() < rhs.value() : lhs.value() > rhs.value();
|
||||
}
|
||||
|
||||
bool operator()(const double& lhs, const double& rhs) {
|
||||
return _side == api::SIDE_SELL ? lhs < rhs : lhs > rhs;
|
||||
}
|
||||
private:
|
||||
api::Side _side;
|
||||
};
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
@@ -190,21 +190,18 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
|
||||
return;
|
||||
}
|
||||
|
||||
// bail if the session we used for the order is no longer valid
|
||||
auto const &session = native_state.routing_record().session();
|
||||
if (!serv.order_router.is_valid_session(session)) {
|
||||
LOG(OrderMan, Error,
|
||||
"Unknown JetFIX session {} referenced by recovered "
|
||||
"order {}. This is a serious "
|
||||
"problem which has to be corrected manually. ",
|
||||
session, rec.key());
|
||||
auto rq = serv.order_router.get_routing_record(
|
||||
UserID(def.user_id()), AccountID(def.account_id()), *me->sec);
|
||||
if (!rq.has_route) {
|
||||
auto msg = fmt::format("Could not route order: {}", rq.error_msg);
|
||||
LOG(OrderMan, Error, "Unable to route order {} recovered from disk. Message: {}",
|
||||
rec.key(), msg);
|
||||
transition_to_error(NativeOrderState::SUB_ERROR_UNKNOWN_FIX_SESSION,
|
||||
fmt::format("Unknown FIX session: {}", session));
|
||||
"Could not route order");
|
||||
return;
|
||||
}
|
||||
|
||||
// if we made it this far we are good.
|
||||
me->routing_record = native_state.routing_record();
|
||||
me->routing_record = rq.routing_record;
|
||||
if (me->state.status() != ORDER_STATUS_PENDING_WORKING)
|
||||
me->exchange_order_id = native_state.exchange_order_id();
|
||||
init_fix();
|
||||
|
||||
@@ -56,11 +56,12 @@ struct AlgoWrapperOrder::PImpl {
|
||||
OrderState const &state() const { return _state; }
|
||||
|
||||
Map<SecurityKey, OrderServices::BookSubID> book_subs;
|
||||
Map<SecurityKey, OrderServices::SettlementSubID> settlement_subs;
|
||||
Map<SecurityKey, OrderServices::TickerSubID> ticker_subs;
|
||||
Map<SecurityKey, OrderServices::StatusSubID> status_subs;
|
||||
Map<OrderKey, ChildRecord> children;
|
||||
bool has_timer_sub = false;
|
||||
|
||||
bool has_position_sub = false;
|
||||
bool is_state_dirty = false;
|
||||
|
||||
Map<Edge, Action> transition_actions;
|
||||
@@ -118,6 +119,13 @@ struct AlgoWrapperOrder::PImpl {
|
||||
: OrderState::HIDDEN);
|
||||
}
|
||||
|
||||
void update_custom_state(api::orderman::DynamicOrderState const&cstate) {
|
||||
is_state_dirty = true;
|
||||
bts_assert(definition.has_custom_def());
|
||||
bts_assert(definition.custom_def().has_dynamic());
|
||||
*_state.mutable_dynamic() = cstate;
|
||||
}
|
||||
|
||||
void set_key(OrderKey k) { _my_key = k; }
|
||||
|
||||
private:
|
||||
@@ -382,6 +390,10 @@ api::Security const *AlgoWrapperOrder::lookup_security(SecurityKey k) const {
|
||||
return me->security_db->resolve(k);
|
||||
}
|
||||
|
||||
std::vector<api::Security const *> AlgoWrapperOrder::known_securities() const {
|
||||
return me->security_db->get_securities();
|
||||
}
|
||||
|
||||
Price AlgoWrapperOrder::price() const { return Price(me->state().price()); }
|
||||
|
||||
Quantity AlgoWrapperOrder::quantity() const {
|
||||
@@ -396,6 +408,10 @@ bool AlgoWrapperOrder::is_shutting_down() const {
|
||||
return me->status == Status::SHUTTING_DOWN;
|
||||
}
|
||||
|
||||
std::vector<PositionUpdate> const&AlgoWrapperOrder::current_positions() const {
|
||||
return me->services->current_positions();
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::subscribe_book(SecurityKey k) {
|
||||
if (!is_working())
|
||||
return;
|
||||
@@ -416,6 +432,26 @@ void AlgoWrapperOrder::unsubscribe_book(SecurityKey k) {
|
||||
me->services->unsubscribe_from_book(it->second);
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::subscribe_settlement(SecurityKey k) {
|
||||
if (!is_working())
|
||||
return;
|
||||
bts_assert(me->settlement_subs.find(k) == me->settlement_subs.end());
|
||||
|
||||
me->settlement_subs[k] =
|
||||
me->services->subscribe_to_settlements(k, [this, k](auto const &ev) {
|
||||
ReportStateGuard _(*this);
|
||||
me->algo->on_settlement(k, Price(ev.settlement_price.price()));
|
||||
});
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::unsubscribe_settlement(SecurityKey k) {
|
||||
if (!is_working())
|
||||
return;
|
||||
bts_assert(me->settlement_subs.find(k) != me->settlement_subs.end());
|
||||
if (auto it = me->settlement_subs.find(k); it != me->settlement_subs.end())
|
||||
me->services->unsubscribe_from_settlements(it->second);
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::subscribe_ticker(SecurityKey k) {
|
||||
if (!is_working())
|
||||
return;
|
||||
@@ -497,6 +533,25 @@ void AlgoWrapperOrder::unsubscribe_timer() {
|
||||
me->has_timer_sub = false;
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::subscribe_position() {
|
||||
if (!is_working())
|
||||
return;
|
||||
bts_assert(!me->has_position_sub);
|
||||
me->has_position_sub = true;
|
||||
me->services->subscribe_to_position_updates([this](auto const &ev) {
|
||||
ReportStateGuard _(*this);
|
||||
me->algo->on_position({ev.security,ev.account_id,ev.long_quantity, ev.short_quantity});
|
||||
});
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::unsubscribe_position() {
|
||||
if (!is_working())
|
||||
return;
|
||||
bts_assert(me->has_position_sub);
|
||||
me->services->unsubscribe_from_position_updates();
|
||||
me->has_position_sub = false;
|
||||
}
|
||||
|
||||
bool AlgoWrapperOrder::is_attaching() const {
|
||||
return me->attach_target != null_order_key();
|
||||
}
|
||||
@@ -610,6 +665,12 @@ void AlgoWrapperOrder::set_ladder_visibility(bool is_visible) {
|
||||
me->set_ladder_visibility(is_visible);
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::update_custom_state(api::orderman::DynamicOrderState const&cstate) {
|
||||
if (!is_working())
|
||||
return;
|
||||
me->update_custom_state(cstate);
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::cancel(std::string const &msg) {
|
||||
if (!is_working())
|
||||
return;
|
||||
@@ -841,6 +902,8 @@ void AlgoWrapperOrder::check_invariants() {
|
||||
bts_assert(is_attaching());
|
||||
bts_assert(me->external_status() == ORDER_STATUS_WORKING);
|
||||
bts_assert(me->book_subs.empty());
|
||||
bts_assert(me->settlement_subs.empty());
|
||||
bts_assert(me->ticker_subs.empty());
|
||||
bts_assert(me->children.empty());
|
||||
bts_assert(!me->has_timer_sub);
|
||||
bts_assert(!me->pending_terminate.has_value());
|
||||
@@ -860,6 +923,8 @@ void AlgoWrapperOrder::check_invariants() {
|
||||
bts_assert(me->security);
|
||||
bts_assert(me->external_status() == ORDER_STATUS_WORKING);
|
||||
bts_assert(me->book_subs.empty());
|
||||
bts_assert(me->settlement_subs.empty());
|
||||
bts_assert(me->ticker_subs.empty());
|
||||
bts_assert(!me->has_timer_sub);
|
||||
bts_assert(!me->pending_update.has_value());
|
||||
bts_assert(me->pending_terminate.has_value());
|
||||
@@ -873,6 +938,8 @@ void AlgoWrapperOrder::check_invariants() {
|
||||
me->external_status() == ORDER_STATUS_ERROR ||
|
||||
me->external_status() == ORDER_STATUS_REJECTED_BY_SPARK);
|
||||
bts_assert(me->book_subs.empty());
|
||||
bts_assert(me->settlement_subs.empty());
|
||||
bts_assert(me->ticker_subs.empty());
|
||||
bts_assert(!me->has_timer_sub);
|
||||
bts_assert(!me->pending_update.has_value());
|
||||
bts_assert(!me->pending_terminate.has_value());
|
||||
@@ -919,10 +986,18 @@ void AlgoWrapperOrder::shutdown(api::orderman::OrderStatus st,
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::cleanup() {
|
||||
for (auto const &s : me->book_subs) {
|
||||
for (auto const &s : me->book_subs)
|
||||
me->services->unsubscribe_from_book(s.second);
|
||||
me->book_subs.clear();
|
||||
}
|
||||
me->book_subs.clear();
|
||||
|
||||
for (auto const &s : me->settlement_subs)
|
||||
me->services->unsubscribe_from_settlements(s.second);
|
||||
me->settlement_subs.clear();
|
||||
|
||||
for (auto const &s : me->ticker_subs)
|
||||
me->services->unsubscribe_from_ticker(s.second);
|
||||
me->ticker_subs.clear();
|
||||
|
||||
for (auto &c : me->children)
|
||||
c.second.order.cancel();
|
||||
if (me->has_timer_sub)
|
||||
|
||||
@@ -33,6 +33,8 @@ class AlgoWrapperOrder : public Order,
|
||||
OrderKey order_key() const override;
|
||||
OrderKey parent_order_key() const override;
|
||||
api::Security const *lookup_security(SecurityKey k) const override;
|
||||
std::vector<api::Security const *> known_securities() const override;
|
||||
|
||||
bool is_attaching() const override;
|
||||
ChildTemplate &make_child_template(api::orderman::OrderDefinition const &,
|
||||
size_t) override;
|
||||
@@ -41,8 +43,12 @@ class AlgoWrapperOrder : public Order,
|
||||
Quantity quantity() const override;
|
||||
Quantity total_fills() const override;
|
||||
bool is_shutting_down() const override;
|
||||
std::vector<PositionUpdate> const¤t_positions() const override;
|
||||
|
||||
void subscribe_book(SecurityKey) override;
|
||||
void unsubscribe_book(SecurityKey) override;
|
||||
void subscribe_settlement(SecurityKey) override;
|
||||
void unsubscribe_settlement(SecurityKey) override;
|
||||
void subscribe_child_change(OrderKey) override;
|
||||
void unsubscribe_child_change(OrderKey) override;
|
||||
void subscribe_timer() override;
|
||||
@@ -51,6 +57,8 @@ class AlgoWrapperOrder : public Order,
|
||||
void unsubscribe_ticker(SecurityKey) override;
|
||||
void subscribe_status(SecurityKey) override;
|
||||
void unsubscribe_status(SecurityKey) override;
|
||||
void subscribe_position() override;
|
||||
void unsubscribe_position() override;
|
||||
|
||||
OrderKey launch_child(ChildTemplate &, Price, Quantity,
|
||||
bool subscribe = true) override;
|
||||
@@ -64,6 +72,7 @@ class AlgoWrapperOrder : public Order,
|
||||
void terminate_error(std::string const &msg) override;
|
||||
void complete() override;
|
||||
void update_fill_state(Quantity total_fills, Price last_price) override;
|
||||
void update_custom_state(api::orderman::DynamicOrderState const&) override;
|
||||
void set_ladder_visibility (bool is_visibile) override;
|
||||
|
||||
// Private
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
IAlgoOrder::~IAlgoOrder() {}
|
||||
|
||||
OrderOpResult IAlgoOrder::do_before_launch(IBeforeLaunchAlgoServices &) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "lib/orderman/orders/Order.h"
|
||||
#include "lib/orderman/events/TickerUpdateEvent.h"
|
||||
#include "lib/orderman/orders/algo/PositionUpdate.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace bts {
|
||||
@@ -44,6 +46,12 @@ class IAlgoOrder {
|
||||
// The trading status for the specified security has changed
|
||||
virtual void on_status(SecurityKey, co2::TradingStatus) {};
|
||||
|
||||
// The settlement price for the specified security has changed
|
||||
virtual void on_settlement(SecurityKey, Price) {};
|
||||
|
||||
// The current position for the security, account combination
|
||||
virtual void on_position(PositionUpdate const&) {};
|
||||
|
||||
// A periodic timer has fired
|
||||
virtual void on_timer() {}
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/orderman/orders/algo/IBeforeLaunchAlgoServices.h"
|
||||
#include "lib/orderman/orders/algo/PositionUpdate.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
|
||||
// Services to an algo *after* it has been launched. See
|
||||
// IBeforeLaunchAlgoServices for services available before launch,
|
||||
// that is outside of the latency sensitive path.
|
||||
class IAlgoServices : public IBeforeLaunchAlgoServices {
|
||||
public:
|
||||
// --------------------------------------------------------------
|
||||
@@ -26,6 +31,13 @@ class IAlgoServices : public IBeforeLaunchAlgoServices {
|
||||
// cancel child orders will be ignored.
|
||||
virtual bool is_shutting_down() const = 0;
|
||||
|
||||
// Returns the current position per security and account. The
|
||||
// returned reference is only valid immediately after the
|
||||
// call. Callers should make a copy if they wish to use it beyond
|
||||
// that. Callers can keep track of changes to the position by
|
||||
// subscribing to the position update events below.
|
||||
virtual std::vector<PositionUpdate> const¤t_positions() const = 0;
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Event Subscriptions
|
||||
// --------------------------------------------------------------
|
||||
@@ -34,6 +46,10 @@ class IAlgoServices : public IBeforeLaunchAlgoServices {
|
||||
virtual void subscribe_book(SecurityKey) = 0;
|
||||
virtual void unsubscribe_book(SecurityKey) = 0;
|
||||
|
||||
// Subscribe to settlement prices for the specified security
|
||||
virtual void subscribe_settlement(SecurityKey) = 0;
|
||||
virtual void unsubscribe_settlement(SecurityKey) = 0;
|
||||
|
||||
// Subscribe to child order updates for the specified child
|
||||
// order. This is rarely needed since launch_child() by default
|
||||
// automatically subscribes to new orders.
|
||||
@@ -52,6 +68,10 @@ class IAlgoServices : public IBeforeLaunchAlgoServices {
|
||||
virtual void subscribe_status(SecurityKey) = 0;
|
||||
virtual void unsubscribe_status(SecurityKey) = 0;
|
||||
|
||||
// Subscribe to position updates
|
||||
virtual void subscribe_position() = 0;
|
||||
virtual void unsubscribe_position() = 0;
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Child management
|
||||
// --------------------------------------------------------------
|
||||
@@ -102,6 +122,9 @@ class IAlgoServices : public IBeforeLaunchAlgoServices {
|
||||
// Set wheter this algo instance should be displayed in the Spark UI ladder
|
||||
virtual void set_ladder_visibility(bool is_visible) = 0;
|
||||
|
||||
// Report custom algo state for display in the Spark UI
|
||||
virtual void update_custom_state(api::orderman::DynamicOrderState const&) = 0;
|
||||
|
||||
// Update this algos's filled quantity and last price
|
||||
virtual void update_fill_state(Quantity total_fills, Price last_price) = 0;
|
||||
};
|
||||
|
||||
@@ -31,6 +31,9 @@ class IBeforeLaunchAlgoServices {
|
||||
// The security identified by k or nullptr if no such security exists
|
||||
virtual api::Security const *lookup_security(SecurityKey k) const = 0;
|
||||
|
||||
// Known active securities
|
||||
virtual std::vector<api::Security const*> known_securities() const = 0;
|
||||
|
||||
// Whether this algo is being attached to an existing order
|
||||
// If an algo does not support attaching it can return an error
|
||||
// if is_attaching() == true from do_before_launch.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "PositionUpdate.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
std::ostream& operator<<(std::ostream&out,const PositionUpdate &update) {
|
||||
out << "Security: " << update.security->id()
|
||||
<< " Account: " << update.account_id.value()
|
||||
<< " Long Qty: " << update.long_quantity.value()
|
||||
<< " Short Qty: " << update.short_quantity.value();
|
||||
return out;
|
||||
}
|
||||
|
||||
}}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/domain_model/common.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
|
||||
namespace bts {
|
||||
namespace api { class Security;}
|
||||
|
||||
namespace orderman {
|
||||
|
||||
struct PositionUpdate {
|
||||
api::Security const *security;
|
||||
AccountID account_id;
|
||||
Quantity long_quantity;
|
||||
Quantity short_quantity;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&,const PositionUpdate &);
|
||||
|
||||
}}
|
||||
|
||||
@@ -18,6 +18,7 @@ struct CoverAlgo::PImpl {
|
||||
Price price_improvement_delta;
|
||||
bool cancel_on_external_hedge_cancel = false;
|
||||
bool cancel_hedges_on_cancel = false;
|
||||
bool detach_hedges = false;
|
||||
ChildTemplate *child_template = nullptr;
|
||||
ChildTemplate *hedge_template = nullptr;
|
||||
Quantity hedged_fills = Quantity(0);
|
||||
@@ -101,8 +102,12 @@ void CoverAlgo::on_covered_change(ChildState const &st) {
|
||||
me->price_improvement_delta);
|
||||
|
||||
auto hedge =
|
||||
algo().launch_child(*me->hedge_template, hedge_price, hedge_qty);
|
||||
me->hedges.push_back(hedge);
|
||||
algo().launch_child(*me->hedge_template, hedge_price, hedge_qty,
|
||||
!me->detach_hedges);
|
||||
if (me->detach_hedges)
|
||||
algo().detach_child(hedge);
|
||||
else
|
||||
me->hedges.push_back(hedge);
|
||||
|
||||
me->hedged_fills =
|
||||
Quantity(hedge_qty.value() + me->hedged_fills.value());
|
||||
@@ -247,6 +252,7 @@ void CoverAlgo::update_definition(
|
||||
me->price_improvement_delta = Price(
|
||||
securities::tick_size(serv.security()).value() * cover.hedge_ticks());
|
||||
me->cancel_hedges_on_cancel = cover.cancel_hedges_on_cancel();
|
||||
me->detach_hedges = cover.detach_hedges();
|
||||
me->cancel_on_external_hedge_cancel =
|
||||
cover.cancel_on_external_hedge_cancel();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/DeleteAndCoverAlgo.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/utils/PBUtils.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
using namespace api::orderman;
|
||||
|
||||
struct DeleteAndCoverAlgo::PImpl{
|
||||
IAlgoServices* algo_services = nullptr;
|
||||
OrderDefinition order_def;
|
||||
ChildTemplate* child_template = nullptr;
|
||||
OrderKey child_key;
|
||||
};
|
||||
|
||||
void copy_lean (LeanOrderDefinition& lean,
|
||||
const DeleteAndCoverDefinition& dc){
|
||||
lean.mutable_cross_threshold()->CopyFrom(dc.cross_threshold());
|
||||
lean.set_max_chase_ticks(dc.max_chase_ticks());
|
||||
switch(dc.trade_toward_behavior()){
|
||||
case LeanOnToward::NO_CHANGE:
|
||||
lean.set_trade_toward_behavior(LeanOrderDefinition::NO_CHANGE);
|
||||
break;
|
||||
case LeanOnToward::CANCEL:
|
||||
lean.set_trade_toward_behavior(LeanOrderDefinition::CANCEL);
|
||||
break;
|
||||
case LeanOnToward::DETACH:
|
||||
lean.set_trade_toward_behavior(LeanOrderDefinition::DETACH);
|
||||
break;
|
||||
case LeanOnToward::REPRICE:
|
||||
lean.set_trade_toward_behavior(LeanOrderDefinition::REPRICE);
|
||||
break;
|
||||
default:
|
||||
bts_assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void copy_top(TopOrderDefinition& top,
|
||||
const DeleteAndCoverDefinition& dc){
|
||||
top.mutable_top_threshold()->CopyFrom(dc.top_threshold());
|
||||
top.set_ticks_off(dc.ticks_off());
|
||||
}
|
||||
|
||||
void copy_cover(CoverOrderDefinition& cover,
|
||||
const DeleteAndCoverDefinition& dc){
|
||||
cover.set_detach_hedges(true);
|
||||
cover.set_hedge_ticks(dc.hedge_ticks());
|
||||
cover.set_cancel_on_external_hedge_cancel(dc.cancel_on_external_hedge_cancel());
|
||||
cover.set_cancel_hedges_on_cancel(dc.cancel_hedges_on_cancel());
|
||||
}
|
||||
|
||||
DeleteAndCoverAlgo::DeleteAndCoverAlgo() : me(new PImpl()) {}
|
||||
DeleteAndCoverAlgo::~DeleteAndCoverAlgo() {}
|
||||
IAlgoServices &DeleteAndCoverAlgo::algo() const { return *me->algo_services; }
|
||||
|
||||
// initialization /////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
OrderOpResult DeleteAndCoverAlgo::do_before_launch(IBeforeLaunchAlgoServices& serv) {
|
||||
auto &def = serv.definition();
|
||||
if(def.has_custom_def() && def.custom_def().has_delete_and_cover()){
|
||||
const auto& dc = def.custom_def().delete_and_cover();
|
||||
LOG(OrderMan, Info, "DeleteAndCoverAlgo do_before_launch args: {}",
|
||||
utils::pb::to_string(dc));
|
||||
// init child temp
|
||||
me->order_def = serv.definition();
|
||||
me->order_def.set_attach_target_order_key(null_order_key().value());
|
||||
// Lean
|
||||
LeanOrderDefinition lean;
|
||||
copy_lean(lean,dc);
|
||||
lean.mutable_child()->mutable_limit()->MergeFrom (
|
||||
LimitOrderDefinition());
|
||||
// Top
|
||||
TopOrderDefinition top;
|
||||
copy_top(top,dc);
|
||||
top.mutable_child()->mutable_limit()->MergeFrom (
|
||||
LimitOrderDefinition());
|
||||
// Cover
|
||||
CoverOrderDefinition cover;
|
||||
copy_cover(cover,dc);
|
||||
cover.mutable_child()->mutable_top()->MergeFrom (top);
|
||||
cover.mutable_hedge()->mutable_lean()->CopyFrom(lean);
|
||||
// put it all together
|
||||
me->order_def.mutable_custom_def()->mutable_cover()->MergeFrom (cover);
|
||||
me->child_template = &(serv.make_child_template(me->order_def));
|
||||
return {};
|
||||
}
|
||||
else
|
||||
return {OrderError::ORDER_ERROR_INVALID_PARAMETER,
|
||||
std::string("Parameters are not well formed")};
|
||||
}
|
||||
void DeleteAndCoverAlgo::do_launch(IAlgoServices& algo) {
|
||||
me->algo_services = &algo;
|
||||
bts_assert(me->child_template);
|
||||
me->child_key = algo.launch_child(*me->child_template, algo.price(), algo.quantity());
|
||||
}
|
||||
void DeleteAndCoverAlgo::do_launch_attached(IAlgoServices& algo,
|
||||
OrderKey k,
|
||||
ChildState st) {
|
||||
me->algo_services = &algo;
|
||||
me->child_key = k;
|
||||
on_child_change(k, st);
|
||||
}
|
||||
// updates ////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void DeleteAndCoverAlgo::do_update(const api::orderman::Update& u) {
|
||||
LOG(OrderMan, Info, "DeleteAndCoverAlgo order {} receiving update: {}",
|
||||
algo().order_key().value(), utils::pb::to_string(u));
|
||||
if (u.has_custom() && u.custom().has_delete_and_cover()){
|
||||
const auto& dc = u.custom().delete_and_cover();
|
||||
copy_cover(*me->order_def.mutable_custom_def()->mutable_cover(),dc);
|
||||
copy_lean(*me->order_def.mutable_custom_def()->mutable_cover()->mutable_hedge()->mutable_lean(),dc);
|
||||
copy_top(*me->order_def.mutable_custom_def()->mutable_cover()->mutable_child()->mutable_top(),dc);
|
||||
Update child_u = u;
|
||||
child_u.mutable_custom()->CopyFrom(me->order_def.custom_def());
|
||||
child_u.set_order_key (me->child_key.value());// is this needed?
|
||||
algo().update_child(me->child_key,child_u);
|
||||
}
|
||||
else
|
||||
LOG(OrderMan, Error, "DeleteAndCoverAlgo order {} Invalid update: {}",
|
||||
algo().order_key().value(), utils::pb::to_string(u));
|
||||
}
|
||||
// callbacks //////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void DeleteAndCoverAlgo::on_child_change(OrderKey, const ChildState &st) {
|
||||
algo().update_fill_state(st.total_fills,st.last_price);
|
||||
switch (st.status){
|
||||
case ChildStatus::WORKING:
|
||||
break;
|
||||
case ChildStatus::CANCELED:
|
||||
algo().cancel();
|
||||
break;
|
||||
case ChildStatus::COMPLETED:
|
||||
algo().complete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class ChildTemplate;
|
||||
|
||||
class DeleteAndCoverAlgo : public IAlgoOrder {
|
||||
public:
|
||||
DeleteAndCoverAlgo();
|
||||
~DeleteAndCoverAlgo();
|
||||
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_child_change(OrderKey, ChildState const &) override;
|
||||
private:
|
||||
IAlgoServices &algo() const ;
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
};
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,401 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/LimitCoverAlgo.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/orderman/orders/algo/IAlgoServices.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "lib/orderman/orders/algo/utils/DynamicOrderAdapter.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverDefinition.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
|
||||
#include <map>
|
||||
#include <limits>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#define DEFAULT_LOG_LEVEL Info
|
||||
|
||||
#define SUMMARIZE_TRANSACTION_BEGIN(id, type, ...) Summary __LOG_TRANS(this, id, Formatter::fmt(__VA_ARGS__), type, DEFAULT_LOG_LEVEL)
|
||||
|
||||
#define SUMMARIZE_TRANSACTION(id, type) Summary __LOG_TRANS(this, id, "", type, DEFAULT_LOG_LEVEL)
|
||||
#define SUMMARIZE SUMMARIZE_TRANSACTION(__FUNCTION__, SummaryType::StatusChange)
|
||||
|
||||
// define 'PRINT_SUMMARIES' to print all logs to the console
|
||||
// (assuming logs are made with the MSG/MSG_LEVEL macros)
|
||||
#ifdef PRINT_SUMMARIES
|
||||
#define _PRINT_MSG(x) std::cout << x << std::endl;
|
||||
#else
|
||||
#define _PRINT_MSG(x)
|
||||
#endif
|
||||
|
||||
#define _MSG(level, msg) \
|
||||
_PRINT_MSG(msg); \
|
||||
LOG(OrderMan, level, "{}", msg)
|
||||
|
||||
#define MSG_LEVEL(level, ...) _MSG(level, (Formatter::fmt(__VA_ARGS__)))
|
||||
// these automatically prepend log_header() to the args
|
||||
#define MSG(...) MSG_LEVEL(DEFAULT_LOG_LEVEL, log_header(), " ", __VA_ARGS__)
|
||||
#define DIAG(...) MSG_LEVEL(Diag, log_header(), " ", __VA_ARGS__)
|
||||
#define INFO(...) MSG_LEVEL(Info, log_header(), " ", __VA_ARGS__)
|
||||
#define WARN(...) MSG_LEVEL(Warn, log_header(), " ", __VA_ARGS__)
|
||||
#define ERROR(...) MSG_LEVEL(Error, log_header(), " ", __VA_ARGS__)
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
using namespace api::orderman;
|
||||
using namespace bts::orderman::limit_cover::utils;
|
||||
using Adapter = bts::orderman::LimitCoverAdapter;
|
||||
|
||||
namespace limit_cover {
|
||||
namespace utils {
|
||||
void init_delete_and_cover(OrderDefinition& target, const DeleteAndCoverDefinition& def) {
|
||||
if (!target.has_custom_def())
|
||||
*target.mutable_custom_def() = CustomOrderDefinition();
|
||||
init_delete_and_cover(*target.mutable_custom_def(), def);
|
||||
}
|
||||
|
||||
void init_delete_and_cover(CustomOrderDefinition& target, const DeleteAndCoverDefinition& def) {
|
||||
if (!target.has_delete_and_cover()) {
|
||||
*target.mutable_delete_and_cover() = DeleteAndCoverDefinition(def);
|
||||
} else {
|
||||
target.mutable_delete_and_cover()->CopyFrom(def);
|
||||
}
|
||||
}
|
||||
|
||||
void init_child_update(Update& target, uint32_t qty, double price, const DeleteAndCoverDefinition& def) {
|
||||
target.set_quantity(qty);
|
||||
target.set_price(price);
|
||||
init_delete_and_cover(*target.mutable_custom(), def);
|
||||
}
|
||||
|
||||
#define INVALID_DEF(x) OrderOpResult(api::orderman::ORDER_ERROR_INVALID_ORDER_DEFINITON, x)
|
||||
|
||||
OrderOpResult validate_custom_def(const CustomOrderDefinition& custom_def) {
|
||||
if (!custom_def.has_dynamic())
|
||||
return INVALID_DEF("DynamicOrderDefinition is undefined");
|
||||
return Adapter::validate_proto(custom_def.dynamic());
|
||||
}
|
||||
OrderOpResult validate_def(const OrderDefinition& def) {
|
||||
if (def.attach_target_order_key() != 0) {
|
||||
return INVALID_DEF("Attach not supported.");
|
||||
} else if (def.has_custom_def()) {
|
||||
return validate_custom_def(def.custom_def());
|
||||
} else {
|
||||
return INVALID_DEF("CustomOrderDefinition is undefined.");
|
||||
}
|
||||
}
|
||||
|
||||
OrderOpResult validate_update(const Update& upd) {
|
||||
return upd.has_custom() ?
|
||||
validate_custom_def(upd.custom()) : OrderOpResult();
|
||||
}
|
||||
} // namespace utils
|
||||
} // namespace limit_cover
|
||||
|
||||
struct LimitCoverAlgo::PImpl {
|
||||
IAlgoServices *algo = nullptr;
|
||||
Status status = Status::WAITING_TO_DEPLOY;
|
||||
OrderKey child_key = null_order_key();
|
||||
double child_price = os::nan();
|
||||
uint32_t child_qty = 0;
|
||||
Book book;
|
||||
uint32_t limit_volume = 0;
|
||||
std::map<uint32_t, LimitCoverGate> gates;
|
||||
double settlement = os::nan();
|
||||
double price_offset_from_settle = 0;
|
||||
double limit_price = os::nan();
|
||||
};
|
||||
|
||||
struct LimitCoverAlgo::Summary {
|
||||
Summary(LimitCoverAlgo* owner, std::string transaction_id, std::string begin_tag = {}, SummaryType type = SummaryType::StatusChange, log::Level level = DEFAULT_LOG_LEVEL)
|
||||
: _owner(owner), _transaction_id(transaction_id), _original_status(owner->me->status), _type(type), _level(level) {
|
||||
if (_owner != nullptr && !_transaction_id.empty()) {
|
||||
std::stringstream str;
|
||||
str << owner->log_header() << " begin " << transaction_id << ".";
|
||||
if (!begin_tag.empty()) {
|
||||
str << " " << begin_tag << ".";
|
||||
}
|
||||
if (type == SummaryType::FullState) {
|
||||
str << " Algo State: " << _owner->fmt_state() << ".";
|
||||
}
|
||||
MSG_LEVEL(level, str.str());
|
||||
}
|
||||
}
|
||||
|
||||
~Summary() {
|
||||
std::stringstream msg_str;
|
||||
if (!_transaction_id.empty()) {
|
||||
msg_str << " end " << _transaction_id << ".";
|
||||
}
|
||||
switch (_type) {
|
||||
case SummaryType::StatusChange:
|
||||
if (_owner->me->status != _original_status)
|
||||
msg_str << " Status changed from " << _original_status << " to " << _owner->me->status << ".";
|
||||
break;
|
||||
case SummaryType::FullState:
|
||||
msg_str << " Algo State: " << _owner->fmt_state() << ".";
|
||||
break;
|
||||
}
|
||||
auto msg = msg_str.str();
|
||||
if (!msg.empty()) {
|
||||
MSG_LEVEL(_level, _owner->log_header(), msg);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
LimitCoverAlgo *_owner;
|
||||
std::string _transaction_id;
|
||||
Status _original_status;
|
||||
SummaryType _type;
|
||||
log::Level _level;
|
||||
};
|
||||
|
||||
LimitCoverAlgo::LimitCoverAlgo() : me(new PImpl()) {}
|
||||
LimitCoverAlgo::~LimitCoverAlgo() {}
|
||||
|
||||
OrderOpResult
|
||||
LimitCoverAlgo::do_before_launch(IBeforeLaunchAlgoServices &balgo) {
|
||||
SUMMARIZE;
|
||||
bts_assert(me->status == Status::WAITING_TO_DEPLOY);
|
||||
bts_assert(me->gates.empty());
|
||||
auto vd = validate_def(balgo.definition());
|
||||
if (vd.ok()) {
|
||||
auto lcdef = Adapter::from_proto(balgo.definition());
|
||||
me->price_offset_from_settle = lcdef.price_offset();
|
||||
me->gates = lcdef.normal_gates();
|
||||
}
|
||||
return vd;
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::do_launch(IAlgoServices& algo) {
|
||||
SUMMARIZE;
|
||||
bts_assert(me->status == Status::WAITING_TO_DEPLOY);
|
||||
me->algo = &algo;
|
||||
me->child_qty = algo.quantity().value();
|
||||
algo.subscribe_book(SecurityKey(algo.security().id()));
|
||||
algo.subscribe_settlement(SecurityKey(algo.security().id()));
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::do_update(Update const &upd) {
|
||||
SUMMARIZE_TRANSACTION_BEGIN(__FUNCTION__, SummaryType::StatusChange, utils::pb::to_string(upd));
|
||||
auto vup = validate_update(upd);
|
||||
if (!vup.ok()) {
|
||||
me->status = Status::DONE;
|
||||
algo().terminate_error(Formatter::fmt("Invalid update: ", vup.error_msg, "."));
|
||||
return;
|
||||
}
|
||||
if (upd.has_custom()) {
|
||||
auto lcdef = Adapter::from_proto(upd.custom());
|
||||
if (me->status == Status::WAITING_TO_DEPLOY) {
|
||||
me->price_offset_from_settle = lcdef.price_offset();
|
||||
recompute_limit_price();
|
||||
recompute_limit_volume();
|
||||
}
|
||||
me->gates = lcdef.normal_gates();
|
||||
if (me->status == Status::WORKING)
|
||||
recompute_gate();
|
||||
}
|
||||
switch (me->status) {
|
||||
case Status::WAITING_TO_DEPLOY:
|
||||
try_deploy();
|
||||
break;
|
||||
case Status::WORKING: {
|
||||
// don't change the child price; the price param is irrelevant to this algo.
|
||||
if (upd.has_custom() || me->child_qty != upd.quantity())
|
||||
update_child(upd.quantity());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::on_book(SecurityKey, Book const &b) {
|
||||
SUMMARIZE_TRANSACTION_BEGIN(__FUNCTION__, SummaryType::StatusChange, "BBO: ", Formatter::to_string(b));
|
||||
me->book = b;
|
||||
recompute_limit_volume();
|
||||
switch (me->status) {
|
||||
case Status::WAITING_TO_DEPLOY:
|
||||
try_deploy();
|
||||
break;
|
||||
case Status::WORKING: {
|
||||
if (recompute_gate())
|
||||
update_child(me->child_qty);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::update_child(uint32_t qty) {
|
||||
bts_assert(me->algo != nullptr);
|
||||
bts_assert(me->status == Status::WORKING);
|
||||
me->child_qty = qty;
|
||||
Update upd;
|
||||
init_child_update(upd, qty, me->child_price, curr_gate().definition());
|
||||
MSG("updating algo child: ", utils::pb::to_string(upd));
|
||||
me->algo->update_child(me->child_key, upd);
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::on_child_change(OrderKey key, ChildState const& state) {
|
||||
bts_assert(key == me->child_key);
|
||||
SUMMARIZE_TRANSACTION_BEGIN(__FUNCTION__, SummaryType::FullState, "Child State: ", Formatter::to_string(state));
|
||||
algo().update_fill_state(state.total_fills, state.last_price);
|
||||
me->child_price = state.price.value();
|
||||
switch (me->status) {
|
||||
case Status::WORKING:
|
||||
switch (state.status) {
|
||||
case ChildStatus::WORKING:
|
||||
break;
|
||||
case ChildStatus::CANCELED: {
|
||||
auto msg = Formatter::fmt("main child (", key, ") canceled unexpectedly.");
|
||||
MSG(msg, ". ", fmt_state());
|
||||
me->status = Status::DONE;
|
||||
algo().terminate_error(msg);
|
||||
break;
|
||||
}
|
||||
case ChildStatus::COMPLETED:
|
||||
me->status = Status::DONE;
|
||||
algo().complete();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Status::DONE:
|
||||
break;
|
||||
default:
|
||||
bts_assert_msg(false, Formatter::fmt(log_header(), " unexpected status: ", me->status));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::on_settlement(SecurityKey, Price settlement) {
|
||||
if (me->status == Status::WAITING_TO_DEPLOY) {
|
||||
MSG("updating settlement from ", me->settlement, " to ", settlement);
|
||||
me->settlement = settlement.value();
|
||||
DIAG("recomputing limit price...");
|
||||
recompute_limit_price();
|
||||
DIAG("limit price is ", me->limit_price);
|
||||
DIAG("recomputing limit volume...");
|
||||
recompute_limit_volume();
|
||||
DIAG("limit volume is ", me->limit_volume);
|
||||
try_deploy();
|
||||
} else {
|
||||
MSG("ignored settlement update to ", settlement,
|
||||
": settlement updates are only processed while the algo is ", Status::WAITING_TO_DEPLOY,
|
||||
". Settlement at time of deploy was ", me->settlement);
|
||||
}
|
||||
}
|
||||
|
||||
void LimitCoverAlgo::try_deploy() {
|
||||
MSG("trying to deploy. ", fmt_state());
|
||||
bts_assert(me->status == Status::WAITING_TO_DEPLOY);
|
||||
bts_assert(me->algo != nullptr);
|
||||
// this is probably redundant (better safe than sorry tho).
|
||||
bts_assert(me->child_key == null_order_key());
|
||||
if (os::is_nan(me->settlement)) {
|
||||
MSG("still waiting for settle.");
|
||||
} else if (me->limit_volume < activation_threshold()) {
|
||||
MSG("still waiting for limit volume (", me->limit_volume, ") to breech activation threshold (", activation_threshold(), ").");
|
||||
} else {
|
||||
bts_assert(!os::is_nan(me->limit_price));
|
||||
recompute_gate();
|
||||
OrderDefinition child_def;
|
||||
bts_assert(!me->gates.empty());
|
||||
child_def.CopyFrom(algo().definition());
|
||||
init_delete_and_cover(child_def, curr_gate().definition());
|
||||
bts_assert(child_def.custom_def().has_delete_and_cover());
|
||||
MSG("deploying child order: ", utils::pb::to_string(child_def));
|
||||
me->child_key = algo().launch_child(child_def, Price(me->limit_price), algo().quantity());
|
||||
me->status = Status::WORKING;
|
||||
algo().set_ladder_visibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
bool did_change(T& backing, T new_value) {
|
||||
if (backing == new_value)
|
||||
return false;
|
||||
backing = new_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LimitCoverAlgo::recompute_gate() {
|
||||
if (me->gates.empty())
|
||||
return false;
|
||||
auto orig_sz = me->gates.size();
|
||||
auto it = me->gates.lower_bound(me->limit_volume);
|
||||
if (it == me->gates.end() || (it != me->gates.begin() && it->first > me->limit_volume))
|
||||
it--;
|
||||
me->gates.erase(me->gates.begin(), it);
|
||||
return me->gates.size() < orig_sz;
|
||||
}
|
||||
|
||||
// limit price
|
||||
double LimitCoverAlgo::compute_limit_price() const {
|
||||
return os::is_nan(me->settlement) ? os::nan() :
|
||||
securities::improve_price(side(), Price(me->settlement), Price(me->price_offset_from_settle))
|
||||
.value();
|
||||
}
|
||||
bool LimitCoverAlgo::recompute_limit_price() {
|
||||
return did_change(me->limit_price, compute_limit_price());
|
||||
}
|
||||
|
||||
// limit volume
|
||||
uint32_t LimitCoverAlgo::compute_limit_volume() const {
|
||||
return me->algo == nullptr ? 0 :
|
||||
BookUtils::volume_at_or_better(me->book, side(), me->limit_price);
|
||||
}
|
||||
bool LimitCoverAlgo::recompute_limit_volume() {
|
||||
return did_change(me->limit_volume, compute_limit_volume());
|
||||
}
|
||||
|
||||
// pseudo properties (const state accessors)
|
||||
IAlgoServices &LimitCoverAlgo::algo() const { return *me->algo; }
|
||||
|
||||
api::Side LimitCoverAlgo::side() const {
|
||||
return me->algo == nullptr ? api::SIDE_UNDEFINED : me->algo->definition().side();
|
||||
}
|
||||
|
||||
const LimitCoverGate& LimitCoverAlgo::curr_gate() const {
|
||||
bts_assert(!me->gates.empty());
|
||||
return me->gates.begin()->second;
|
||||
}
|
||||
|
||||
uint32_t LimitCoverAlgo::activation_threshold() const {
|
||||
return me->gates.empty() ? std::numeric_limits<uint32_t>::max() : me->gates.begin()->first;
|
||||
}
|
||||
|
||||
std::string LimitCoverAlgo::log_header() const {
|
||||
std::stringstream ostr;
|
||||
ostr << "LimitCoverAlgo";
|
||||
if (me->algo != nullptr) {
|
||||
ostr << "(" << me->algo->order_key().value() << ")";
|
||||
}
|
||||
ostr << "[" << fmt_status(me->status) << "]";
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
std::string LimitCoverAlgo::fmt_state() const {
|
||||
std::stringstream ostr;
|
||||
if (me->algo != nullptr) {
|
||||
ostr << " [" << fmt_status(me->status) << "]"
|
||||
<< Formatter::to_string(side()) << " " << me->algo->quantity().value()
|
||||
<< " @ " << me->algo->price().value()
|
||||
<< " (" << me->price_offset_from_settle
|
||||
<< " away from settle).";
|
||||
if (me->child_key != null_order_key()) {
|
||||
ostr << " Main child key: " << me->child_key << ", ";
|
||||
}
|
||||
ostr << " Settle: " << me->settlement
|
||||
<< ", Limit Price: " << me->limit_price
|
||||
<< ", BBO: " << Formatter::to_string(me->book);
|
||||
} else {
|
||||
ostr << "(initializing)";
|
||||
}
|
||||
return ostr.str();
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
#include "lib/orderman/orderman_utils/BookUtils.h"
|
||||
#include "lib/orderman/orderman_utils/Formatter.h"
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverDefinition.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverGate.h"
|
||||
#include <map>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
class LimitCoverAlgo : public IAlgoOrder {
|
||||
public:
|
||||
LimitCoverAlgo();
|
||||
~LimitCoverAlgo();
|
||||
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void on_settlement(SecurityKey, Price settlement) override;
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
|
||||
enum class Status {
|
||||
WAITING_TO_DEPLOY,
|
||||
WORKING,
|
||||
DONE
|
||||
};
|
||||
using Status = bts::orderman::LimitCoverAlgo::Status;
|
||||
|
||||
enum SummaryType {
|
||||
StatusChange,
|
||||
FullState
|
||||
};
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
struct Summary;
|
||||
std::unique_ptr<PImpl> me;
|
||||
|
||||
IAlgoServices &algo() const;
|
||||
api::Side side() const;
|
||||
std::string log_header() const;
|
||||
std::string fmt_state() const;
|
||||
// returns the first gate in gates (asserts that gates isn't empty).
|
||||
// Note: after the child is deployed, gates with lower thresholds are
|
||||
// removed when the limit volume passes the next gates' thresh.
|
||||
const LimitCoverGate& curr_gate() const;
|
||||
/// On the compute/recompute methods:
|
||||
/// compute_blah just returns the result (doesn't mutate any state),
|
||||
/// while recompute_blah assigns the result to the appropriate field,
|
||||
/// and returns a bool indicating whether or not the value changed.
|
||||
// limit price
|
||||
double compute_limit_price() const;
|
||||
bool recompute_limit_price();
|
||||
// limit volume
|
||||
uint32_t compute_limit_volume() const;
|
||||
bool recompute_limit_volume();
|
||||
// recomputes the current gate (i.e., the one with the
|
||||
// highest activation thresh that is still less than
|
||||
// the limit volume) then removes all gates with lower threshs.
|
||||
bool recompute_gate();
|
||||
uint32_t activation_threshold() const;
|
||||
//
|
||||
void try_deploy();
|
||||
void update_child(uint32_t qty);
|
||||
};
|
||||
|
||||
namespace limit_cover {
|
||||
namespace utils {
|
||||
inline std::string fmt_status(LimitCoverAlgo::Status status) {
|
||||
switch (status) {
|
||||
case LimitCoverAlgo::Status::WAITING_TO_DEPLOY: return "WAITING_TO_DEPLOY";
|
||||
case LimitCoverAlgo::Status::WORKING: return "WORKING";
|
||||
case LimitCoverAlgo::Status::DONE: return "DONE";
|
||||
default: return Formatter::fmt("STATUS_", static_cast<int>(status));
|
||||
}
|
||||
}
|
||||
void init_delete_and_cover(api::orderman::OrderDefinition& target, const api::orderman::DeleteAndCoverDefinition& def);
|
||||
void init_delete_and_cover(api::orderman::CustomOrderDefinition& target, const api::orderman::DeleteAndCoverDefinition& def);
|
||||
void init_child_update(api::orderman::Update& target, uint32_t qty, double price, const api::orderman::DeleteAndCoverDefinition& def);
|
||||
OrderOpResult validate_def(const api::orderman::OrderDefinition&);
|
||||
OrderOpResult validate_custom_def(const api::orderman::CustomOrderDefinition&);
|
||||
OrderOpResult validate_update(const api::orderman::Update&);
|
||||
} // namespace utils
|
||||
} // namespace limit_cover
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const LimitCoverAlgo::Status& status) {
|
||||
ostr << limit_cover::utils::fmt_status(status);
|
||||
return ostr;
|
||||
}
|
||||
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -3,11 +3,14 @@
|
||||
#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"
|
||||
|
||||
using namespace bts;
|
||||
using namespace api::orderman;
|
||||
@@ -148,6 +151,45 @@ extern "C" void init_pyramid_plugin(bts::SparkServices& srv) {
|
||||
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;
|
||||
|
||||
@@ -163,6 +205,13 @@ extern "C" void init_tas_plugin(bts::SparkServices& srv) {
|
||||
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_spark_plugin(bts::SparkServices &srv) {
|
||||
auto& dyn_algo_config = config::get<config::toggles::DynamicAlgos>();
|
||||
if(dyn_algo_config.enable_dorman())
|
||||
@@ -171,6 +220,10 @@ extern "C" void init_spark_plugin(bts::SparkServices &srv) {
|
||||
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);
|
||||
}
|
||||
|
||||
}}}
|
||||
}}}
|
||||
@@ -0,0 +1,490 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/TASCounterAlgo.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/orderman/orders/algo/IAlgoServices.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <google/protobuf/timestamp.pb.h>
|
||||
#include "lib/orderman/actors/SODPosition.h"
|
||||
#include <google/protobuf/util/time_util.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
using namespace api::orderman;
|
||||
using namespace google::protobuf;
|
||||
|
||||
enum class TASCounterAlgo::Status { WORKING, DONE };
|
||||
|
||||
// book and trade data for each of the 6 contracts as well as the TAS contracts
|
||||
// (the vwap of the TAS contracts is not used, but for uniformity is kept in the
|
||||
// the same structure)
|
||||
struct ContractMarketData{
|
||||
uint32_t tot_vol = 0;
|
||||
double running_sum = 0;
|
||||
Book book;
|
||||
double estimated_settle = 0.0;
|
||||
void on_trade (const TickerUpdateEvent::Trade& t){
|
||||
tot_vol += t.quantity.value();
|
||||
running_sum += t.price.value()*t.quantity.value();
|
||||
}
|
||||
const double vwap(){
|
||||
return tot_vol == 0 ? os::nan() : running_sum/tot_vol;
|
||||
}
|
||||
};
|
||||
// the input for the t1 front, second and third orders, as well as the template
|
||||
// and working order key are kept here
|
||||
struct ContractAutoOrder{
|
||||
// inputs
|
||||
uint32_t n_secs = 0;
|
||||
uint32_t qty = 0;
|
||||
bool enabled = false;
|
||||
// dont know yet which side will be sent
|
||||
ChildTemplate* buy_ord = nullptr;
|
||||
ChildTemplate* sell_ord = nullptr;
|
||||
// computed input
|
||||
// the is AlgoData::settle_stop - n_secs
|
||||
Timestamp algo_start;
|
||||
// output
|
||||
std::optional<OrderKey> tas_order = std::nullopt;
|
||||
};
|
||||
|
||||
struct AlgoData{
|
||||
double tick_size;
|
||||
Timestamp settle_start;
|
||||
Timestamp settle_stop;
|
||||
ContractMarketData t1_front,
|
||||
t1_second,
|
||||
t1_third,
|
||||
t2_front,
|
||||
t2_second,
|
||||
t3_front;
|
||||
ContractMarketData t1_front_tas,
|
||||
t1_second_tas,
|
||||
t1_third_tas;
|
||||
ContractAutoOrder t1_front_order,
|
||||
t1_second_order,
|
||||
t1_third_order;
|
||||
DynamicOrderState out_state;
|
||||
};
|
||||
|
||||
struct TASCounterAlgo::PImpl {
|
||||
// reporting
|
||||
uint16_t reporting_period_msecs = 500;
|
||||
bts::datetime::PTime last_report_time = bts::datetime::now();
|
||||
// immutable state
|
||||
IAlgoServices* algo_services = nullptr;
|
||||
// mutable state
|
||||
AlgoData data;
|
||||
// to avoid lookups on md and ticker callbacks
|
||||
std::map<SecurityKey,ContractMarketData*> md_map;
|
||||
Status state = Status::WORKING;
|
||||
};
|
||||
|
||||
//fwd decls
|
||||
void _copy_output(AlgoData&);
|
||||
bool _copy_settle_times (AlgoData&,const Map<std::string,DynamicOrderParam>&);
|
||||
void _copy_order_data (AlgoData&,const Map<std::string,DynamicOrderParam>&);
|
||||
bool _setup_data(IBeforeLaunchAlgoServices&, AlgoData&,
|
||||
std::map<SecurityKey,ContractMarketData*>&);
|
||||
double round_to_nearest(double price, double tick_size);
|
||||
long long t1_minus_t2_usecs (const Timestamp&, const Timestamp&);
|
||||
long long t_minus_now_usecs (const google::protobuf::Timestamp&);
|
||||
bool trade_within_time_win (const Timestamp&, const Timestamp&,
|
||||
const TickerUpdateEvent::Trade& t);
|
||||
bool now_within_time_win (const Timestamp&, const Timestamp&);
|
||||
|
||||
|
||||
TASCounterAlgo::TASCounterAlgo() : me(new PImpl()) {}
|
||||
TASCounterAlgo::~TASCounterAlgo() {}
|
||||
// initialization /////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
OrderOpResult TASCounterAlgo::do_before_launch(IBeforeLaunchAlgoServices& serv) {
|
||||
if(_setup_data(serv,me->data,me->md_map))
|
||||
return {};
|
||||
return {OrderError::ORDER_ERROR_INVALID_PARAMETER,
|
||||
std::string("Parameters are not well formed")};
|
||||
}
|
||||
void TASCounterAlgo::do_launch(IAlgoServices& algo_services) {
|
||||
me->algo_services = &algo_services;
|
||||
algo_services.subscribe_timer();
|
||||
for (const auto& kp : me->md_map){
|
||||
algo_services.subscribe_book(kp.first);
|
||||
algo_services.subscribe_ticker(kp.first);
|
||||
}
|
||||
}
|
||||
void TASCounterAlgo::do_launch_attached(
|
||||
IAlgoServices& algo_services,
|
||||
OrderKey,
|
||||
ChildState) {
|
||||
// not allowed
|
||||
me->algo_services = &algo_services;
|
||||
}
|
||||
// updates ////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void TASCounterAlgo::do_update(const api::orderman::Update& u) {
|
||||
if(me->state == Status::DONE)
|
||||
return;
|
||||
const auto& p = u.custom().dynamic().order_params();
|
||||
// only order data change is allowed
|
||||
_copy_order_data(me->data,p);
|
||||
_check_and_send_order();
|
||||
}
|
||||
// callbacks //////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void TASCounterAlgo::on_book(SecurityKey k, const Book& b) {
|
||||
if(me->state == Status::DONE)
|
||||
return;
|
||||
const auto& it = me->md_map.find(k);
|
||||
if(it != me->md_map.end()){
|
||||
it->second->book = b;
|
||||
_check_and_send_order();
|
||||
}
|
||||
}
|
||||
void TASCounterAlgo::on_ticker(SecurityKey k, const TradeVector& v){
|
||||
if(me->state == Status::DONE)
|
||||
return;
|
||||
const auto& it = me->md_map.find(k);
|
||||
if(it != me->md_map.end()){
|
||||
auto contract_md = it->second;
|
||||
for (const auto& t : v)
|
||||
if (_within_settle()){
|
||||
contract_md->on_trade(t);
|
||||
}
|
||||
_check_and_send_order();
|
||||
}
|
||||
}
|
||||
|
||||
// this only gets called on an explicit algo cancel
|
||||
// from the outside
|
||||
TASCounterAlgo::OnCancelResponse TASCounterAlgo::on_cancel(){
|
||||
return IAlgoOrder::OnCancelResponse::HONOR;
|
||||
}
|
||||
|
||||
void TASCounterAlgo::on_timer(){
|
||||
if(me->state == Status::DONE)
|
||||
return;
|
||||
_check_and_send_order();
|
||||
if(bts::datetime::msecs_since(me->last_report_time) >=
|
||||
me->reporting_period_msecs){
|
||||
me->last_report_time = bts::datetime::now();
|
||||
_report_state();
|
||||
}
|
||||
// check for end and exit
|
||||
if (t1_minus_t2_usecs(util::TimeUtil::GetCurrentTime(),
|
||||
me->data.settle_stop) > 0){
|
||||
std::string msg = "session over";
|
||||
_cxl_and_end(false,msg);
|
||||
}
|
||||
}
|
||||
|
||||
IAlgoServices& TASCounterAlgo::_algo() const { return *me->algo_services; }
|
||||
|
||||
void TASCounterAlgo::_switch_state(TASCounterAlgo::Status new_state){
|
||||
switch (new_state){
|
||||
case (TASCounterAlgo::Status::WORKING):
|
||||
bts_assert(false);
|
||||
break;
|
||||
case (TASCounterAlgo::Status::DONE):
|
||||
bts_assert(me->state == TASCounterAlgo::Status::WORKING);
|
||||
break;
|
||||
}
|
||||
LOG(OrderMan, Info,"TASCounterAlgo::_switch_state key:{} {}->{} ",
|
||||
_algo().order_key(),
|
||||
static_cast<int>(me->state),
|
||||
static_cast<int>(new_state));
|
||||
me->state = new_state;
|
||||
}
|
||||
|
||||
void TASCounterAlgo::_cxl_and_end (bool is_error, const std::string& err_msg){
|
||||
if(me->state == Status::DONE)
|
||||
return;
|
||||
if(is_error)
|
||||
_algo().terminate_error(err_msg);
|
||||
else
|
||||
_algo().complete();
|
||||
_switch_state(TASCounterAlgo::Status::DONE);
|
||||
}
|
||||
|
||||
void TASCounterAlgo::_compute_settles(){
|
||||
AlgoData& d = me->data;
|
||||
d.t1_front.estimated_settle = d.t1_front.vwap();
|
||||
d.t1_second.estimated_settle = d.t2_front.vwap() - d.t1_front.vwap();
|
||||
d.t1_third.estimated_settle = d.t3_front.vwap() - d.t1_front.vwap() -
|
||||
d.t1_second.vwap();
|
||||
// round to closest value
|
||||
d.t1_front.estimated_settle = round_to_nearest(
|
||||
d.t1_front.estimated_settle,me->data.tick_size);
|
||||
d.t1_second.estimated_settle = round_to_nearest(
|
||||
d.t1_second.estimated_settle,me->data.tick_size);
|
||||
d.t1_third.estimated_settle = round_to_nearest(
|
||||
d.t1_third.estimated_settle,me->data.tick_size);
|
||||
}
|
||||
|
||||
// the decision to send an order can be caused by
|
||||
// 1. change in settings (=> time window, enabling orders etc)
|
||||
// 2. book update (=> TAS book can result in order)
|
||||
// 3. ticker (=> new estimated settlement)
|
||||
// 4. time passing (=> in the window now)
|
||||
void TASCounterAlgo::_check_and_send_order(){
|
||||
_compute_settles();
|
||||
auto should_send_tas_order =
|
||||
[](const ContractMarketData& c,
|
||||
const ContractAutoOrder& o,
|
||||
const ContractMarketData& tas_c,
|
||||
bool is_buy,
|
||||
double& out_price,
|
||||
uint32_t& out_qty) -> bool {
|
||||
// buying the tas at the offer or selling it at the bid
|
||||
const auto& tas_opp_top = is_buy ? tas_c.book.ask()[0] : tas_c.book.bid()[0];
|
||||
const auto& c_same_top = is_buy ? c.book.bid()[0] : c.book.ask()[0];
|
||||
if(tas_opp_top.size() > 0 && c_same_top.size() > 0){
|
||||
// delivery price as defined by where the TAS order is traded
|
||||
double tas_delv_price = c.estimated_settle + tas_opp_top.price();
|
||||
// if the delv price for the buy order is <= contract bid or
|
||||
// or the delv price for the sell order is >= contract offer
|
||||
// then we are buying at the bid or selling at the offer
|
||||
double c_price = c_same_top.price();
|
||||
if((is_buy && tas_delv_price <= c_price) ||
|
||||
(!is_buy && tas_delv_price >= c_price)){
|
||||
out_qty = std::min(o.qty,tas_opp_top.size());
|
||||
out_price = tas_opp_top.price();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto _send_order_tn = [&](
|
||||
const ContractMarketData& c,
|
||||
ContractAutoOrder& o,
|
||||
const ContractMarketData& tas_c) -> void {
|
||||
if(o.enabled && o.n_secs > 0 && o.qty > 0 &&
|
||||
!o.tas_order.has_value() &&
|
||||
now_within_time_win(o.algo_start,me->data.settle_stop)){
|
||||
double price;
|
||||
uint32_t qty;
|
||||
if(should_send_tas_order(c,o,tas_c,true,price,qty)){
|
||||
o.tas_order = _algo().launch_child(*o.buy_ord,
|
||||
Price(price),
|
||||
Quantity(qty));
|
||||
}
|
||||
else if(should_send_tas_order(c,o,tas_c,false,price,qty)){
|
||||
o.tas_order = _algo().launch_child(*o.sell_ord,
|
||||
Price(price),
|
||||
Quantity(qty));
|
||||
}
|
||||
}
|
||||
};
|
||||
_send_order_tn(me->data.t1_front,me->data.t1_front_order,me->data.t1_front_tas);
|
||||
_send_order_tn(me->data.t1_second,me->data.t1_second_order,me->data.t1_second_tas);
|
||||
_send_order_tn(me->data.t1_third,me->data.t1_third_order,me->data.t1_third_tas);
|
||||
}
|
||||
|
||||
void TASCounterAlgo::_report_state(){
|
||||
_compute_settles();
|
||||
_copy_output(me->data);
|
||||
_algo().update_custom_state(me->data.out_state);
|
||||
}
|
||||
|
||||
bool TASCounterAlgo::_within_settle() const{
|
||||
return now_within_time_win(me->data.settle_start,me->data.settle_stop);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// stateless conversion ///////////////////////////////////////////////////////
|
||||
|
||||
// stateless helpers
|
||||
ChildTemplate* _create_order_template (IBeforeLaunchAlgoServices& serv,
|
||||
SecurityKey key,
|
||||
api::Side side){
|
||||
OrderDefinition order_def = serv.definition();
|
||||
order_def.set_security_key (key.value());
|
||||
order_def.set_side(side);
|
||||
order_def.mutable_custom_def()->mutable_limit()->MergeFrom (
|
||||
api::orderman::LimitOrderDefinition());
|
||||
return &(serv.make_child_template(order_def));
|
||||
}
|
||||
// copies output into the dynamic state for publishing
|
||||
void _copy_output(AlgoData& d){
|
||||
auto& m = *(d.out_state.mutable_custom_state());
|
||||
// we can safely assume its within int32
|
||||
auto secs_to_settle = static_cast<int>(t_minus_now_usecs(d.settle_start)/1000000);
|
||||
// if its in settle (-ve value), its set to 0
|
||||
secs_to_settle = std::max(secs_to_settle,0);
|
||||
m["Seconds To Settle"].set_as_int32(secs_to_settle);
|
||||
m["VWAP T1-Front"].set_as_double(d.t1_front.vwap());
|
||||
m["VWAP T1-Second"].set_as_double(d.t1_second.vwap());
|
||||
m["VWAP T1-Third"].set_as_double(d.t1_third.vwap());
|
||||
m["VWAP T2-Front"].set_as_double(d.t2_front.vwap());
|
||||
m["VWAP T2-Second"].set_as_double(d.t2_second.vwap());
|
||||
m["VWAP T3-Front"].set_as_double(d.t3_front.vwap());
|
||||
m["T1-Front Projected Settle"].set_as_double(d.t1_front.estimated_settle);
|
||||
m["T1-Second Projected Settle"].set_as_double(d.t1_second.estimated_settle);
|
||||
m["T1-Third Projected Settle"].set_as_double(d.t1_third.estimated_settle);
|
||||
}
|
||||
// copies the settle time/period part of the input data
|
||||
bool _copy_settle_times (AlgoData& d,
|
||||
const Map<std::string,DynamicOrderParam>& params){
|
||||
auto hh = get_if<uint32_t>(params, "Settlement Start Hour");
|
||||
auto mm = get_if<uint32_t>(params, "Settlement Start Minutes");
|
||||
auto ss = get_if<uint32_t>(params, "Settlement Start Seconds");
|
||||
auto settle_period_secs = get_if<uint32_t>(params, "Settlement Period (seconds)");
|
||||
if (hh.has_value() && mm.has_value() &&
|
||||
ss.has_value() && settle_period_secs.has_value() &&
|
||||
hh.value() <= 23 && mm.value() <= 59 && ss.value() <= 59){
|
||||
d.settle_start = SODPosition::today_local_time_to_utc_timestamp(
|
||||
hh.value(),
|
||||
mm.value(),
|
||||
ss.value(),
|
||||
true,
|
||||
false);
|
||||
d.settle_stop = SODPosition::add_seconds(d.settle_start,
|
||||
settle_period_secs.value());
|
||||
auto settle_start_txt = util::TimeUtil::ToString(d.settle_start);
|
||||
auto settle_stop_txt = util::TimeUtil::ToString(d.settle_stop);
|
||||
LOG(OrderMan, Info, "TASCounterAlgo::_copy_settle_times "
|
||||
"settle_start:{} settle_stop:{} ",
|
||||
settle_start_txt,
|
||||
settle_stop_txt);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// copies the order execution part of the input data
|
||||
void _copy_order_data (AlgoData& out_d,
|
||||
const Map<std::string,DynamicOrderParam>& params){
|
||||
auto copy_o = [&](ContractAutoOrder& out_order,
|
||||
std::string time_name,
|
||||
std::string qty_name,
|
||||
std::string enabled_name
|
||||
) -> bool {
|
||||
out_order.enabled = false;
|
||||
auto time = get_if<uint32_t>(params, time_name);
|
||||
auto qty = get_if<uint32_t>(params, qty_name);
|
||||
auto enabled = get_if<bool>(params, enabled_name);
|
||||
if(time.has_value() && qty.has_value() && enabled.has_value()){
|
||||
out_order.n_secs = time.value();
|
||||
out_order.qty = qty.value();
|
||||
out_order.enabled = enabled.value();
|
||||
out_order.algo_start = SODPosition::add_seconds(out_d.settle_stop,
|
||||
-1 * out_order.n_secs);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
copy_o(out_d.t1_front_order,
|
||||
"T1 Front Time",
|
||||
"T1 Front Qty",
|
||||
"T1 Front Order Enabled");
|
||||
copy_o(out_d.t1_second_order,
|
||||
"T1 Second Time",
|
||||
"T1 Second Qty",
|
||||
"T1 Second Order Enabled");
|
||||
copy_o(out_d.t1_third_order,
|
||||
"T1 Third Time",
|
||||
"T1 Third Qty",
|
||||
"T1 Third Order Enabled");
|
||||
}
|
||||
// copies all of the input params (in the algo) into proper structures
|
||||
bool _setup_data(IBeforeLaunchAlgoServices& serv,
|
||||
AlgoData& out_d,
|
||||
std::map<SecurityKey,ContractMarketData*>& out_md_map){
|
||||
auto& def = serv.definition();
|
||||
// [ captures ] (parameters) -> returnTypesDeclaration { lambdaStatements; }
|
||||
auto init_contract_bk = [&out_md_map](auto& out_contract_md,
|
||||
const auto& params,
|
||||
auto param_name) -> void {
|
||||
auto k = get_if<uint32_t>(params, param_name);
|
||||
if(k.has_value()){
|
||||
out_contract_md.book = Book(SecurityKey(k.value()));
|
||||
out_md_map[out_contract_md.book.security_key()] = &out_contract_md;
|
||||
}
|
||||
};
|
||||
if (def.has_custom_def() && def.custom_def().has_dynamic()){
|
||||
out_d.tick_size = serv.security().contract_spec().low_tick();
|
||||
const auto& p = def.custom_def().dynamic().order_params();
|
||||
// settlement time
|
||||
if(!_copy_settle_times(out_d,p))
|
||||
return false;
|
||||
// main contracts
|
||||
init_contract_bk(out_d.t1_front, p,"T1 Front");
|
||||
init_contract_bk(out_d.t1_second, p,"T1 Second");
|
||||
init_contract_bk(out_d.t1_third, p,"T1 Third");
|
||||
init_contract_bk(out_d.t2_front, p,"T2 Front");
|
||||
init_contract_bk(out_d.t2_second, p,"T2 Second");
|
||||
init_contract_bk(out_d.t3_front, p,"T3 Front");
|
||||
// TAS contracts
|
||||
init_contract_bk(out_d.t1_front_tas, p,"T1 Front TAS");
|
||||
init_contract_bk(out_d.t1_second_tas, p,"T1 Second TAS");
|
||||
init_contract_bk(out_d.t1_third_tas, p,"T1 Third TAS");
|
||||
// auto orders
|
||||
_copy_order_data(out_d,p);
|
||||
// order template
|
||||
out_d.t1_front_order.buy_ord = _create_order_template(serv,
|
||||
out_d.t1_front_tas.book.security_key(),api::Side::SIDE_BUY);
|
||||
out_d.t1_front_order.sell_ord = _create_order_template(serv,
|
||||
out_d.t1_front_tas.book.security_key(),api::Side::SIDE_SELL);
|
||||
out_d.t1_second_order.buy_ord = _create_order_template(serv,
|
||||
out_d.t1_second_tas.book.security_key(),api::Side::SIDE_BUY);
|
||||
out_d.t1_second_order.sell_ord = _create_order_template(serv,
|
||||
out_d.t1_second_tas.book.security_key(),api::Side::SIDE_SELL);
|
||||
out_d.t1_third_order.buy_ord = _create_order_template(serv,
|
||||
out_d.t1_third_tas.book.security_key(),api::Side::SIDE_BUY);
|
||||
out_d.t1_third_order.sell_ord = _create_order_template(serv,
|
||||
out_d.t1_third_tas.book.security_key(),api::Side::SIDE_SELL);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
double round_to_nearest(double price, double tick_size){
|
||||
if(os::is_nan(price))
|
||||
return price;
|
||||
auto buy_price = bts::securities::raw_round_to_quote_price(
|
||||
price,
|
||||
tick_size,
|
||||
api::Side::SIDE_BUY);
|
||||
auto sell_price = buy_price + tick_size;
|
||||
return std::abs(price-buy_price) < std::abs(sell_price-price) ? buy_price :
|
||||
sell_price;
|
||||
}
|
||||
// time helpers
|
||||
// converts std::chrono::system_clock::time_point to google timestamp
|
||||
google::protobuf::Timestamp to_pf_ts (const bts::TimeStamp& ts){
|
||||
auto ts_us = std::chrono::time_point_cast<std::chrono::microseconds>(ts);
|
||||
auto ts_us_val = ts_us.time_since_epoch();
|
||||
auto usecs = ts_us_val.count();
|
||||
return google::protobuf::util::TimeUtil::MicrosecondsToTimestamp(usecs);
|
||||
}
|
||||
long long t1_minus_t2_usecs (const Timestamp& t1,const Timestamp& t2){
|
||||
return util::TimeUtil::TimestampToMicroseconds(t1) -
|
||||
util::TimeUtil::TimestampToMicroseconds(t2);
|
||||
}
|
||||
long long t_minus_now_usecs (const Timestamp& t){
|
||||
auto now = util::TimeUtil::GetCurrentTime();
|
||||
return t1_minus_t2_usecs(t,now);
|
||||
}
|
||||
bool within_time_win (const Timestamp& beg, const Timestamp& end,
|
||||
const Timestamp& ts){
|
||||
return t1_minus_t2_usecs(ts,beg)>=0 &&
|
||||
t1_minus_t2_usecs(ts,end)<=0;
|
||||
}
|
||||
// if current time is within time window
|
||||
bool now_within_time_win (const Timestamp& beg,
|
||||
const Timestamp& end){
|
||||
return within_time_win(beg,end,util::TimeUtil::GetCurrentTime());
|
||||
}
|
||||
bool trade_within_time_win (const Timestamp& beg, const Timestamp& end,
|
||||
const TickerUpdateEvent::Trade& t){
|
||||
auto ts = to_pf_ts(t.ts);
|
||||
return within_time_win(beg,end,ts);
|
||||
}
|
||||
|
||||
} // namespace orderman
|
||||
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class ChildTemplate;
|
||||
|
||||
class TASCounterAlgo : public IAlgoOrder {
|
||||
public:
|
||||
TASCounterAlgo();
|
||||
~TASCounterAlgo();
|
||||
struct TASCounterAlgoParams{
|
||||
};
|
||||
public:
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void on_ticker(SecurityKey, TradeVector const&) override;
|
||||
IAlgoOrder::OnCancelResponse on_cancel() override;
|
||||
void on_timer() override;
|
||||
private:
|
||||
enum class Status;
|
||||
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
private:
|
||||
IAlgoServices& _algo() const ;
|
||||
void _switch_state (Status new_status);
|
||||
void _cxl_and_end (bool is_error, const std::string& err_msg);
|
||||
void _check_and_send_order();
|
||||
void _compute_settles ();
|
||||
void _report_state();
|
||||
bool _within_settle() const;
|
||||
};
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -19,7 +19,7 @@ class TopAlgo : public IAlgoOrder {
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
|
||||
#include "lib/orderman/orders/OrderOpResult.h"
|
||||
#include "lib/orderman/orders/algo/utils/DynamicOrderAdapter.h"
|
||||
#include <utility>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
template<typename T>
|
||||
using Param = bts::orderman::dynamic_adapter::ParamKey<bts::orderman::LimitCoverDefinition, T>;
|
||||
using DynamicOrderEnumerated = bts::api::orderman::DynamicOrderEnumerated;
|
||||
using OnToward = bts::api::orderman::LeanOnToward;
|
||||
using LimitCoverDefinition = bts::orderman::LimitCoverDefinition;
|
||||
using namespace bts::api::orderman;
|
||||
#define ON_TOWARD_NAME_PAIR(x) std::make_pair(x, #x)
|
||||
|
||||
template<uint32_t gate_num>
|
||||
std::string gate_param_name(std::string name) {
|
||||
return Formatter::fmt("Gate ", gate_num, " ", name);
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string gate_param_name<0>(std::string name) {
|
||||
return Formatter::fmt("Initial ", name);
|
||||
}
|
||||
|
||||
struct PriceOffset : public Param<double> {
|
||||
PriceOffset() : Param<double>(std::string("Limit"),
|
||||
[](const LimitCoverDefinition& def) { return def.price_offset(); },
|
||||
[](LimitCoverDefinition& def, double v) { def.price_offset(v); }) {}
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct ActivationThreshold : public Param<uint32_t> {
|
||||
ActivationThreshold() : Param<uint32_t>(gate_param_name<gate_num>("Activation Threshold"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).activation_threshold(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).activation_threshold(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct CoverThreshold : public Param<uint32_t> {
|
||||
CoverThreshold() : Param<uint32_t>(gate_param_name<gate_num>("Cover Threshold"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).cover_threshold(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).cover_threshold(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct DeleteThreshold : public Param<uint32_t> {
|
||||
DeleteThreshold() : Param<uint32_t>(gate_param_name<gate_num>("Delete Threshold"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).delete_threshold(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).delete_threshold(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct HedgeTicks : public Param<uint32_t> {
|
||||
HedgeTicks() : Param<uint32_t>(gate_param_name<gate_num>("Hedge Ticks"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).hedge_ticks(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).hedge_ticks(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct TopTicksOff : public Param<uint32_t> {
|
||||
TopTicksOff() : Param<uint32_t>(gate_param_name<gate_num>("Top Ticks Offset"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).top_ticks_offset(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).top_ticks_offset(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct MaxChaseTicks : public Param<uint32_t> {
|
||||
MaxChaseTicks() : Param<uint32_t>(gate_param_name<gate_num>("Max Chase Ticks"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).max_chase_ticks(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) { def.mgate(gate_num).max_chase_ticks(v); }) { }
|
||||
};
|
||||
|
||||
template<uint32_t gate_num>
|
||||
struct TradeTowardBehavior : public dynamic_adapter::EnumParamKey<LimitCoverDefinition, OnToward> {
|
||||
TradeTowardBehavior() : EnumParamKey<LimitCoverDefinition, OnToward>(
|
||||
gate_param_name<gate_num>("Trade Toward Behavior"), {
|
||||
ON_TOWARD_NAME_PAIR(NO_CHANGE),
|
||||
ON_TOWARD_NAME_PAIR(CANCEL),
|
||||
ON_TOWARD_NAME_PAIR(DETACH),
|
||||
ON_TOWARD_NAME_PAIR(REPRICE)
|
||||
},
|
||||
[](const LimitCoverDefinition& def) { return def.gate(gate_num).on_toward(); },
|
||||
[](LimitCoverDefinition& def, OnToward v) { def.mgate(gate_num).on_toward(v); })
|
||||
{}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ActivationThreshold<0> : public Param<uint32_t> {
|
||||
ActivationThreshold() : Param<uint32_t>(std::string("Entry Threshold"),
|
||||
[](const LimitCoverDefinition& def) { return def.gate(0).activation_threshold(); },
|
||||
[](LimitCoverDefinition& def, uint32_t v) {
|
||||
auto& g = def.mgate(0);
|
||||
g.activation_threshold(v);
|
||||
g.cover_threshold(v);
|
||||
g.delete_threshold(v);
|
||||
}) {}
|
||||
};
|
||||
|
||||
using Adapter = dynamic_adapter::DynamicOrderAdapter<LimitCoverDefinition,
|
||||
PriceOffset,
|
||||
// [gate 0]
|
||||
ActivationThreshold<0>,
|
||||
HedgeTicks<0>,
|
||||
TopTicksOff<0>,
|
||||
MaxChaseTicks<0>,
|
||||
TradeTowardBehavior<0>,
|
||||
// gate 1
|
||||
ActivationThreshold<1>,
|
||||
CoverThreshold<1>,
|
||||
DeleteThreshold<1>,
|
||||
HedgeTicks<1>,
|
||||
TopTicksOff<1>,
|
||||
MaxChaseTicks<1>,
|
||||
TradeTowardBehavior<1>,
|
||||
// gate 2
|
||||
ActivationThreshold<2>,
|
||||
CoverThreshold<2>,
|
||||
DeleteThreshold<2>,
|
||||
HedgeTicks<2>,
|
||||
TopTicksOff<2>,
|
||||
MaxChaseTicks<2>,
|
||||
TradeTowardBehavior<2>>;
|
||||
|
||||
LimitCoverDefinition LimitCoverAdapter::from_proto(const api::orderman::OrderDefinition& order) {
|
||||
if (order.has_custom_def())
|
||||
return from_proto(order.custom_def());
|
||||
return LimitCoverDefinition();
|
||||
}
|
||||
LimitCoverDefinition LimitCoverAdapter::from_proto(const api::orderman::CustomOrderDefinition& custom_def) {
|
||||
LimitCoverDefinition rv;
|
||||
if (custom_def.has_dynamic())
|
||||
Adapter().from_proto(custom_def.dynamic(), rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
#define INVALID_DEF(x) OrderOpResult(api::orderman::ORDER_ERROR_INVALID_ORDER_DEFINITON, x)
|
||||
|
||||
OrderOpResult LimitCoverAdapter::validate_proto(const api::orderman::DynamicOrderDefinition& dyn) {
|
||||
return Adapter().validate_proto(dyn) ?
|
||||
OrderOpResult() :
|
||||
INVALID_DEF("One or more dynamic parameters are missing or invalid.");
|
||||
}
|
||||
void LimitCoverAdapter::init_proto(const LimitCoverDefinition& def, api::orderman::DynamicOrderDefinition& dyn) {
|
||||
Adapter().to_proto(dyn, def);
|
||||
}
|
||||
|
||||
void LimitCoverAdapter::define_proto(api::orderman::DynamicOrderDescriptor& desc) {
|
||||
Adapter().define_proto(desc);
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverDefinition.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
struct LimitCoverAdapter {
|
||||
static LimitCoverDefinition from_proto(const api::orderman::OrderDefinition& order);
|
||||
static LimitCoverDefinition from_proto(const api::orderman::CustomOrderDefinition& custom_def);
|
||||
static OrderOpResult validate_proto(const api::orderman::DynamicOrderDefinition& order);
|
||||
static void init_proto(const LimitCoverDefinition& def, api::orderman::DynamicOrderDefinition& dyn);
|
||||
static void define_proto(api::orderman::DynamicOrderDescriptor& desc);
|
||||
};
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverGate.h"
|
||||
#include "lib/orderman/orderman_utils/Formatter.h"
|
||||
#include <ostream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace limit_cover {
|
||||
// The exclusive maximum number of gates.
|
||||
// Note: the limit cover spec called for 3 gates (2 gates explicitly,
|
||||
// plus an initial activation threshold, which is treated as another gate).
|
||||
const uint32_t max_num_gates = 4;
|
||||
}
|
||||
struct LimitCoverDefinition {
|
||||
// price offset
|
||||
double price_offset() const {
|
||||
return _price_offset;
|
||||
}
|
||||
void price_offset(double value) {
|
||||
_price_offset = value;
|
||||
}
|
||||
|
||||
LimitCoverGate gate(uint32_t index) const {
|
||||
return _gate_or_default(index);
|
||||
}
|
||||
LimitCoverGate& mgate(uint32_t index) {
|
||||
return _ensure_gate(index);
|
||||
}
|
||||
void gate(uint32_t index, LimitCoverGate value) {
|
||||
_ensure_gate(index) = value;
|
||||
}
|
||||
|
||||
// The original LimitAlgo spec called for an initial activation thresh, and 2 gates
|
||||
// (fyi a gate has 3 members: activation thresh, cover thresh, and delete thresh).
|
||||
// The initial activation thresh serves as the initial cover, and delete threshes as well.
|
||||
// In this sense, the activation threshold can be considered as an 'initial gate' itself.
|
||||
// 'normal_gates' does just that: returns a map of both gates plus the 'initial gate.'
|
||||
std::map<uint32_t, LimitCoverGate> normal_gates() const {
|
||||
std::map<uint32_t, LimitCoverGate> m;
|
||||
for (auto g : _gates)
|
||||
m.insert(std::make_pair(g.activation_threshold(), g));
|
||||
return m;
|
||||
}
|
||||
private:
|
||||
double _price_offset;
|
||||
std::vector<LimitCoverGate> _gates;
|
||||
|
||||
LimitCoverGate& _ensure_gate(uint32_t index) {
|
||||
bts_assert(index < limit_cover::max_num_gates);
|
||||
while (_gates.size() <= index)
|
||||
_gates.push_back(LimitCoverGate());
|
||||
return _gates[index];
|
||||
}
|
||||
|
||||
LimitCoverGate _gate_or_default(uint32_t index) const {
|
||||
return _gates.size() > index ? _gates[index] : LimitCoverGate();
|
||||
}
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const LimitCoverDefinition& lcd) {
|
||||
auto ngates = lcd.normal_gates();
|
||||
ostr << "Price Offset: " << lcd.price_offset()
|
||||
<< ", Activation Thresh: " << lcd.gate(0).activation_threshold()
|
||||
<< ", Gates: " << Formatter::fmt_values(ngates);
|
||||
return ostr;
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include <ostream>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
struct LimitCoverGate {
|
||||
LimitCoverGate() : LimitCoverGate(0, 0, 0) {}
|
||||
|
||||
LimitCoverGate(const LimitCoverGate& other)
|
||||
: _activation_threshold(other._activation_threshold), _definition(other._definition) {}
|
||||
|
||||
LimitCoverGate(uint32_t activation_thresh, uint32_t cover_thresh, uint32_t delete_thresh)
|
||||
: _activation_threshold(activation_thresh), _definition() {
|
||||
cover_threshold(cover_thresh);
|
||||
delete_threshold(delete_thresh);
|
||||
}
|
||||
|
||||
// activation threshold
|
||||
uint32_t activation_threshold() const { return _activation_threshold; }
|
||||
void activation_threshold(uint32_t value) { _activation_threshold = value; }
|
||||
|
||||
// cover threshold
|
||||
uint32_t cover_threshold() const {
|
||||
return _definition.has_cross_threshold() ? _get_absolute(_definition.cross_threshold()) : 0;
|
||||
}
|
||||
void cover_threshold(uint32_t value) {
|
||||
if (!_definition.has_cross_threshold()) {
|
||||
*_definition.mutable_cross_threshold() = bts::spark::QuantityMeasure();
|
||||
}
|
||||
_definition.mutable_cross_threshold()->set_absolute(value);
|
||||
}
|
||||
|
||||
// delete threshold
|
||||
uint32_t delete_threshold() const {
|
||||
return _definition.has_top_threshold() ? _get_absolute(_definition.top_threshold()) : 0;
|
||||
}
|
||||
void delete_threshold(uint32_t value) {
|
||||
if (!_definition.has_top_threshold()) {
|
||||
*_definition.mutable_top_threshold() = bts::spark::QuantityMeasure();
|
||||
}
|
||||
_definition.mutable_top_threshold()->set_absolute(value);
|
||||
}
|
||||
|
||||
// hedge ticks
|
||||
uint32_t hedge_ticks() const { return _definition.hedge_ticks(); }
|
||||
void hedge_ticks(uint32_t value) { _definition.set_hedge_ticks(value); }
|
||||
// top ticks offset
|
||||
uint32_t top_ticks_offset() const { return _definition.ticks_off(); }
|
||||
void top_ticks_offset(uint32_t value) { _definition.set_ticks_off(value); }
|
||||
// max chase ticks
|
||||
uint32_t max_chase_ticks() const { return _definition.max_chase_ticks(); }
|
||||
void max_chase_ticks(uint32_t value) { _definition.set_max_chase_ticks(value); }
|
||||
// on toward
|
||||
api::orderman::LeanOnToward on_toward() const { return _definition.trade_toward_behavior(); }
|
||||
void on_toward(api::orderman::LeanOnToward value) { _definition.set_trade_toward_behavior(value); }
|
||||
// definition
|
||||
const api::orderman::DeleteAndCoverDefinition& definition() const { return _definition; }
|
||||
api::orderman::DeleteAndCoverDefinition& mdefinition() { return _definition; }
|
||||
void definition(api::orderman::DeleteAndCoverDefinition value) { _definition = value; }
|
||||
|
||||
api::orderman::CustomOrderDefinition custom_def() const {
|
||||
api::orderman::CustomOrderDefinition cod;
|
||||
*(cod.mutable_delete_and_cover()) = definition();
|
||||
return cod;
|
||||
}
|
||||
void custom_def(const api::orderman::CustomOrderDefinition &cod) {
|
||||
if (cod.has_delete_and_cover())
|
||||
definition(cod.delete_and_cover());
|
||||
}
|
||||
private:
|
||||
uint32_t _activation_threshold;
|
||||
api::orderman::DeleteAndCoverDefinition _definition;
|
||||
|
||||
static inline uint32_t _get_absolute(spark::QuantityMeasure qty_measure, uint32_t fallback_value = 0) {
|
||||
return qty_measure.quantity_case() == spark::QuantityMeasure::kAbsolute ? qty_measure.absolute() : fallback_value;
|
||||
}
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const LimitCoverGate& g) {
|
||||
ostr << "Activation Thresh: " << g.activation_threshold() << ", Definition: " << utils::pb::to_string(g.custom_def());
|
||||
return ostr;
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -56,21 +56,26 @@ struct AlgoTestDriverBase::Impl : public RecursionCounter {
|
||||
api::Security security;
|
||||
OrderKey order_key, parent_key;
|
||||
std::tuple<Quantity, Price> fill_state;
|
||||
|
||||
std::vector<PositionUpdate> positions;
|
||||
AlgoState state;
|
||||
std::vector<ChildOrder> orders;
|
||||
std::vector<ChildOrder> detached_orders;
|
||||
|
||||
std::map<SecurityKey, Book> books;
|
||||
std::map<SecurityKey, Price> settlements;
|
||||
std::map<SecurityKey, bool> book_subs;
|
||||
std::map<SecurityKey, bool> settlement_subs;
|
||||
std::map<SecurityKey, bool> ticker_subs;
|
||||
std::map<SecurityKey, bool> status_subs;
|
||||
bool sub_timer = false;
|
||||
bool sub_position = false;
|
||||
|
||||
// Callbacks
|
||||
std::vector<std::function<void(OrderKey)>> on_child_launching;
|
||||
std::vector<std::function<void(OrderKey, Quantity, Price)>> on_child_launching;
|
||||
std::vector<std::function<void(const ChildOrder&)>> on_child_changing;
|
||||
std::vector<std::function<void(const ChildOrder&, const ChildOrder&)>> on_child_changed;
|
||||
// runs after the child change has been forwarded to the underlying algo.
|
||||
std::vector<std::function<void(const ChildOrder&, const ChildOrder&)>> after_child_changed;
|
||||
bool allow_overfill = false;
|
||||
|
||||
Impl(IAlgoOrder& o) : algo(o) {}
|
||||
@@ -158,6 +163,15 @@ void AlgoTestDriverBase::update_book(const Book& book) {
|
||||
me->algo.on_book(key, book);
|
||||
}
|
||||
}
|
||||
|
||||
void AlgoTestDriverBase::update_settlement(SecurityKey key, Price price) {
|
||||
me->settlements[key] = price;
|
||||
auto iter = me->settlement_subs.find(key);
|
||||
if (iter != me->settlement_subs.end()) {
|
||||
me->algo.on_settlement(key, price);
|
||||
}
|
||||
}
|
||||
|
||||
void AlgoTestDriverBase::update_ticker(SecurityKey key, const TradeVector& data) {
|
||||
auto iter = me->ticker_subs.find(key);
|
||||
if (iter != me->ticker_subs.end() && iter->second) {
|
||||
@@ -194,6 +208,20 @@ const std::vector<ChildOrder>& AlgoTestDriverBase::child_orders() const {
|
||||
return me->orders;
|
||||
}
|
||||
|
||||
// This method asserts that the child exists. If the need arises for an
|
||||
// optional return, leave this method as is, and add a 'try_find_child' method.
|
||||
// These are tests, so it's reasonable to assume the caller is certain about
|
||||
// the existence of the child it seeks.
|
||||
const ChildOrder& AlgoTestDriverBase::find_child(OrderKey key) const {
|
||||
for (auto& c : me->orders) {
|
||||
if (c.key == key)
|
||||
return c;
|
||||
}
|
||||
REQUIRE(false);
|
||||
bts_assert(false);
|
||||
return me->orders.front();
|
||||
}
|
||||
|
||||
const std::vector<ChildOrder>& AlgoTestDriverBase::detached_orders() const {
|
||||
return me->detached_orders;
|
||||
}
|
||||
@@ -225,6 +253,7 @@ void AlgoTestDriverBase::fill(OrderKey k, Price price, Quantity qty) {
|
||||
for (const auto& cb : me->on_child_changed) cb(old, child);
|
||||
// No defer because this is called from outside
|
||||
if (child.subscribed) me->algo.on_child_change(k, child.cstate);
|
||||
for (const auto& cb : me->after_child_changed) cb(old, child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -261,7 +290,7 @@ void AlgoTestDriverBase::fill(Price p, Quantity q) {
|
||||
}
|
||||
}
|
||||
|
||||
void AlgoTestDriverBase::on_child_launching(std::function<void(OrderKey)>&& cb) {
|
||||
void AlgoTestDriverBase::on_child_launching(std::function<void(OrderKey, Quantity, Price)>&& cb) {
|
||||
me->on_child_launching.push_back(cb);
|
||||
}
|
||||
void AlgoTestDriverBase::on_child_changing(std::function<void(const ChildOrder&)>&& cb) {
|
||||
@@ -270,18 +299,21 @@ void AlgoTestDriverBase::on_child_changing(std::function<void(const ChildOrder&)
|
||||
void AlgoTestDriverBase::on_child_changed(std::function<void(const ChildOrder&, const ChildOrder&)>&& cb) {
|
||||
me->on_child_changed.push_back(cb);
|
||||
}
|
||||
|
||||
void AlgoTestDriverBase::after_child_changed(std::function<void(const ChildOrder&, const ChildOrder&)>&& cb) {
|
||||
me->after_child_changed.push_back(cb);
|
||||
}
|
||||
bool AlgoTestDriverBase::allow_overfill() const { return me->allow_overfill; }
|
||||
void AlgoTestDriverBase::allow_overfill(bool value) { me->allow_overfill = value; }
|
||||
|
||||
//
|
||||
// Utility
|
||||
|
||||
ChildOrder mkchild(OrderKey key, Price price, Quantity qty, bool sub) {
|
||||
ChildOrder mkchild(OrderKey key, Price price, Quantity qty, bool sub, ChildStatus status = ChildStatus::WORKING) {
|
||||
ChildOrder result = {};
|
||||
result.key = key;
|
||||
result.cstate.price = price;
|
||||
result.cstate.quantity = qty;
|
||||
result.cstate.status = status;
|
||||
result.subscribed = sub;
|
||||
return result;
|
||||
}
|
||||
@@ -301,6 +333,15 @@ const api::Security* AlgoTestDriverBase::lookup_security(SecurityKey key) const
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<api::Security const *> AlgoTestDriverBase::known_securities() const {
|
||||
std::vector<api::Security const *> result;
|
||||
auto s = get_some_securities();
|
||||
for (auto const &p : s) {
|
||||
result.push_back(&p.second);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AlgoTestDriverBase::is_attaching() const { return has_flag(me->flags, ATDFlags::IS_ATTACHING); }
|
||||
ChildTemplate& AlgoTestDriverBase::make_child_template(const api::orderman::OrderDefinition& def, size_t) {
|
||||
REQUIRE(def.custom_def().custom_case() != CustomOrderDefinition::CUSTOM_NOT_SET);
|
||||
@@ -321,6 +362,10 @@ bool AlgoTestDriverBase::is_shutting_down() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<PositionUpdate> const & AlgoTestDriverBase::current_positions() const {
|
||||
return me->positions;
|
||||
}
|
||||
|
||||
void AlgoTestDriverBase::subscribe_book(SecurityKey key){
|
||||
me->book_subs[key] = true;
|
||||
auto iter = me->books.find(key);
|
||||
@@ -332,6 +377,18 @@ void AlgoTestDriverBase::unsubscribe_book(SecurityKey key) {
|
||||
me->book_subs.erase(key);
|
||||
}
|
||||
|
||||
void AlgoTestDriverBase::subscribe_settlement(SecurityKey key){
|
||||
me->settlement_subs[key] = true;
|
||||
auto iter = me->settlements.find(key);
|
||||
if (iter != me->settlements.end()) {
|
||||
me->algo.on_settlement(key, iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
void AlgoTestDriverBase::unsubscribe_settlement(SecurityKey key) {
|
||||
me->settlement_subs.erase(key);
|
||||
}
|
||||
|
||||
void AlgoTestDriverBase::subscribe_ticker(SecurityKey key) {
|
||||
me->ticker_subs[key] = true;
|
||||
}
|
||||
@@ -363,12 +420,14 @@ void AlgoTestDriverBase::unsubscribe_child_change(OrderKey k) {
|
||||
void AlgoTestDriverBase::subscribe_timer() { me->sub_timer = true; }
|
||||
void AlgoTestDriverBase::unsubscribe_timer() { me->sub_timer = false; }
|
||||
|
||||
void AlgoTestDriverBase::subscribe_position() { me->sub_position= true; }
|
||||
void AlgoTestDriverBase::unsubscribe_position() { me->sub_position = false; }
|
||||
|
||||
OrderKey AlgoTestDriverBase::launch_child(ChildTemplate&, Price price, Quantity qty, bool sub) {
|
||||
OrderKey key = me->next_order();
|
||||
me->orders.push_back(
|
||||
mkchild(key, price, qty, sub));
|
||||
for(const auto& cb : me->on_child_launching) cb(key);
|
||||
for(const auto& cb : me->on_child_launching) cb(key, qty, price);
|
||||
return key;
|
||||
}
|
||||
OrderKey AlgoTestDriverBase::launch_child(api::orderman::OrderDefinition const & def, Price price, Quantity qty, bool sub) {
|
||||
@@ -376,7 +435,7 @@ OrderKey AlgoTestDriverBase::launch_child(api::orderman::OrderDefinition const &
|
||||
me->orders.push_back(
|
||||
mkchild(key, price, qty, sub));
|
||||
me->orders.back().def = {def};
|
||||
for(const auto& cb : me->on_child_launching) cb(key);
|
||||
for(const auto& cb : me->on_child_launching) cb(key, qty, price);
|
||||
return key;
|
||||
}
|
||||
void AlgoTestDriverBase::update_child(OrderKey key, api::orderman::Update const & update) {
|
||||
@@ -451,5 +510,6 @@ void AlgoTestDriverBase::complete() {
|
||||
}
|
||||
void AlgoTestDriverBase::set_ladder_visibility(bool) { /* whatever, ok */ }
|
||||
void AlgoTestDriverBase::update_fill_state(Quantity q, Price p) { me->fill_state = { q, p }; }
|
||||
void AlgoTestDriverBase::update_custom_state(api::orderman::DynamicOrderState const&) {}
|
||||
|
||||
}}}//namespace
|
||||
}}}//namespace
|
||||
@@ -60,7 +60,9 @@ class AlgoTestDriverBase : private IAlgoServices {
|
||||
Quantity quantity() const override;
|
||||
Quantity total_fills() const override;
|
||||
bool is_shutting_down() const override;
|
||||
|
||||
std::vector<PositionUpdate> const & current_positions() const override;
|
||||
void update_child(OrderKey , api::orderman::Update const &) override;
|
||||
void cancel_child(OrderKey) override;
|
||||
//
|
||||
// Change state
|
||||
|
||||
@@ -70,6 +72,7 @@ class AlgoTestDriverBase : private IAlgoServices {
|
||||
void do_update(const api::orderman::Update&);
|
||||
|
||||
void update_book(const Book&);
|
||||
void update_settlement(SecurityKey , Price ) ;
|
||||
void update_ticker(SecurityKey, const TradeVector&);
|
||||
void update_status(SecurityKey, co2::TradingStatus);
|
||||
void advance_time(Duration, uint32_t steps=1);
|
||||
@@ -78,6 +81,7 @@ class AlgoTestDriverBase : private IAlgoServices {
|
||||
//
|
||||
// Inspection methods
|
||||
const std::vector<ChildOrder>& child_orders() const;
|
||||
const ChildOrder& find_child(OrderKey) const;
|
||||
const std::vector<ChildOrder>& detached_orders() const;
|
||||
AlgoState state() const;
|
||||
|
||||
@@ -86,10 +90,11 @@ class AlgoTestDriverBase : private IAlgoServices {
|
||||
// Fill this quantity, start with the largest child order
|
||||
void fill(Price, Quantity);
|
||||
|
||||
void on_child_launching(std::function<void(OrderKey)>&&);
|
||||
void on_child_launching(std::function<void(OrderKey, Quantity, Price)>&&);
|
||||
void on_child_changing(std::function<void(const ChildOrder&)>&&);
|
||||
void on_child_changed(std::function<void(const ChildOrder&, const ChildOrder&)>&&);
|
||||
|
||||
void after_child_changed(std::function<void(const ChildOrder&, const ChildOrder&)>&&);
|
||||
|
||||
//
|
||||
// Tweak internal REQUIRE
|
||||
bool allow_overfill() const;
|
||||
@@ -104,6 +109,7 @@ private:
|
||||
OrderKey order_key() const override;
|
||||
OrderKey parent_order_key() const override;
|
||||
const api::Security* lookup_security(SecurityKey) const override;
|
||||
std::vector<api::Security const *> known_securities() const override;
|
||||
bool is_attaching() const override;
|
||||
ChildTemplate& make_child_template(const api::orderman::OrderDefinition& child_def, size_t reserve = 1) override;
|
||||
TimeStamp get_time() const override;
|
||||
@@ -112,6 +118,8 @@ private:
|
||||
// IAlgoServices
|
||||
void subscribe_book(SecurityKey)override;
|
||||
void unsubscribe_book(SecurityKey) override;
|
||||
void subscribe_settlement(SecurityKey)override;
|
||||
void unsubscribe_settlement(SecurityKey) override;
|
||||
void subscribe_child_change(OrderKey) override;
|
||||
void unsubscribe_child_change(OrderKey) override;
|
||||
void subscribe_timer() override;
|
||||
@@ -120,12 +128,12 @@ private:
|
||||
void unsubscribe_ticker(SecurityKey) override;
|
||||
void subscribe_status(SecurityKey) override;
|
||||
void unsubscribe_status(SecurityKey) override;
|
||||
void subscribe_position() override;
|
||||
void unsubscribe_position() override;
|
||||
|
||||
OrderKey launch_child(ChildTemplate &, Price, Quantity, bool) override;
|
||||
OrderKey launch_child(api::orderman::OrderDefinition const &, Price,
|
||||
Quantity, bool) override;
|
||||
void update_child(OrderKey , api::orderman::Update const &) override;
|
||||
void cancel_child(OrderKey) override;
|
||||
void detach_child(OrderKey) override;
|
||||
void detach_all_children() override;
|
||||
|
||||
@@ -134,7 +142,7 @@ private:
|
||||
void complete() override;
|
||||
void set_ladder_visibility(bool) override;
|
||||
void update_fill_state(Quantity , Price) override;
|
||||
|
||||
void update_custom_state(api::orderman::DynamicOrderState const&) override;
|
||||
};
|
||||
|
||||
template <class Algo>
|
||||
@@ -151,4 +159,17 @@ struct AlgoTestDriver : public AlgoTestDriverBase
|
||||
const Algo& algo() const { return _algo; }
|
||||
};
|
||||
|
||||
}}}//namespace
|
||||
inline std::ostream& operator <<(std::ostream& ostr, const AlgoState& state) {
|
||||
switch (state) {
|
||||
case AlgoState::CREATED: ostr << "CREATED"; break;
|
||||
case AlgoState::READY: ostr << "READY"; break;
|
||||
case AlgoState::RUNNING: ostr << "RUNNING"; break;
|
||||
case AlgoState::CANCELED: ostr << "CANCELED"; break;
|
||||
case AlgoState::TERMINATED: ostr << "TERMINATED"; break;
|
||||
case AlgoState::COMPLETED: ostr << "COMPLETED"; break;
|
||||
default: ostr << "AlgoState_" << static_cast<int>(state); break;
|
||||
}
|
||||
return ostr;
|
||||
}
|
||||
|
||||
}}}//namespace
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/LimitCoverAlgo.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/AlgoTestDriver.h"
|
||||
#include "catch/catch.hpp"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orderman_utils/BookUtils.h"
|
||||
#include "lib/orderman/orderman_utils/Formatter.h"
|
||||
#include "lib/orderman/orderman_utils/PriceComp.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverState.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverTestUtils.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/TestSteps.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <array>
|
||||
#include <random>
|
||||
#include <variant>
|
||||
#include <cmath>
|
||||
|
||||
// for some reason, intellisense poops its pants without 'using namespace std.'
|
||||
// std::optional<T>, and std::unordered_map<T>, in particular, break intellisense for the whole document.
|
||||
using namespace std;
|
||||
using namespace bts::api::orderman;
|
||||
using namespace bts::orderman::limit_cover::utils;
|
||||
//#define PRINT_SUMMARIES
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
// Glossary:
|
||||
// * limit-price:
|
||||
// The price at which the main child is deployed (computed as a tick offset from the settlement).
|
||||
// * limit-volume:
|
||||
// The same-side market volume at or better than the limit price.
|
||||
// This is recomputed whenever the book changes (obviously).
|
||||
// * volume-gate:
|
||||
// * volume-gate: a simple struct with 3 fields:
|
||||
// 1) activation_threshold (uint)
|
||||
// 2) cover_threshold (uint) must be <= activation_thresh
|
||||
// 3) delete_threshold (uint) must be <= activation_thresh
|
||||
// * current-volume-gate:
|
||||
// The volume gate with the highest activation threshold that is still
|
||||
// <= the limit-volume. This is recomputed whenever the book changes
|
||||
// (obviously).
|
||||
// * volume-gates:
|
||||
// A series of volume gates, sorted by 'activation_threshold.'
|
||||
// Initially, the current-volume-gate is one with the lowest activation threshold.
|
||||
// The main child is not deployed until the limit-volume reaches that threshold.
|
||||
// After that, the algo continues to observe the limit-volume, removing gates with lower
|
||||
// thresholds when the volume breeches the next gate's thresh.
|
||||
|
||||
// Test Assertions:
|
||||
// * child is not launched until the limit-volume reaches the initial activation threshold.
|
||||
// * limit-volume is always correct (verify whenever the book changes).
|
||||
// * current-volume-gate is always correct (verify whenever the book changes).
|
||||
|
||||
// Test Design:
|
||||
// * expectations of the limit cover algo's state are stored in an instance of 'LimitCoverState.'
|
||||
// The actual LimitCoverAlgo does not use this class; the tests just use it as a means
|
||||
// to validate the actual LimitCoverAlgo's state, and to wrap calls to the LimitCoverAlgo
|
||||
// with validation checks.
|
||||
|
||||
// * each test implementation should be executed 6 times:
|
||||
// 2 x 3 = test both sides, with each of the following termination steps:
|
||||
// 1) Fill to completion
|
||||
// 2) Cancel the algo
|
||||
// 3) Cancel the child
|
||||
|
||||
// * Restrictions on the parameters:
|
||||
// - the spec explicitly called for 3 volume gates, and specified that the 3 thresholds
|
||||
// (activation, cover, and delete) be the same for the first gate
|
||||
// (technically, the spec described 2 gates and an 'entry threshold').
|
||||
// - the algo should immediately cancel itself if these parameters are not the case.
|
||||
// while it would most likely not cause a problem, these assumptions allow the tests to focus
|
||||
// on a narrow range of inputs (and of course, such restrictions naturally decrease the
|
||||
// chance of a bug slipping through).
|
||||
|
||||
LimitCoverState init_state(
|
||||
OrderDefinition& mdef,
|
||||
Side side,
|
||||
uint32_t quantity,
|
||||
double price,
|
||||
double price_offset,
|
||||
std::vector<LimitCoverGate> gates,
|
||||
bool cancel_hedges_on_cancel = true,
|
||||
bool cancel_on_external_hedge_cancel = false) {
|
||||
//
|
||||
REQUIRE(!gates.empty());
|
||||
for (auto& g : gates) {
|
||||
g.mdefinition().set_cancel_hedges_on_cancel(cancel_hedges_on_cancel);
|
||||
g.mdefinition().set_cancel_on_external_hedge_cancel(cancel_on_external_hedge_cancel);
|
||||
}
|
||||
LimitCoverDefinition lc_def;
|
||||
lc_def.price_offset(price_offset);
|
||||
uint32_t gate_pos = 0;
|
||||
for (auto g : gates) {
|
||||
lc_def.gate(gate_pos++, g);
|
||||
}
|
||||
DynamicOrderDefinition dyn_def;
|
||||
LimitCoverAdapter::init_proto(lc_def, dyn_def);
|
||||
*(mdef.mutable_custom_def()->mutable_dynamic()) = dyn_def;
|
||||
mdef.set_side(side);
|
||||
return LimitCoverState(mdef, quantity, price);
|
||||
}
|
||||
|
||||
template<typename... Steps>
|
||||
inline void test_impl(Side side, uint32_t qty, double price_offset, double settle, std::vector<LimitCoverGate> gates, Steps... steps) {
|
||||
std::cout << std::endl << std::endl;
|
||||
std::cout << "*************************************************************************************************************************" << std::endl;
|
||||
std::cout << "=========================================================================================================================" << std::endl;
|
||||
int step_count = 1;
|
||||
std::cout << "Test " << "[" << Formatter::to_string(side) << " Order]" << std::endl << "\t1. "
|
||||
<< Formatter::join([&]() {
|
||||
return Formatter::fmt("\n\t", ++step_count, ". ");
|
||||
}, steps...)
|
||||
<< std::endl;
|
||||
std::cout << "=========================================================================================================================" << std::endl;
|
||||
std::cout << "*************************************************************************************************************************" << std::endl;
|
||||
std::cout << std::endl;
|
||||
OrderDefinition def;
|
||||
// price is irrelevant
|
||||
auto state = init_state(def, side, qty, -99999, price_offset, gates);
|
||||
state.settlement(settle);
|
||||
(steps.execute(state), ...);
|
||||
}
|
||||
|
||||
template<typename... Steps>
|
||||
inline void test_permutations(
|
||||
uint32_t qty,
|
||||
double settle,
|
||||
std::vector<LimitCoverGate> gates,
|
||||
Steps... steps) {
|
||||
std::vector<double> price_offsets { 0.0, 0.75, 1.25, -0.75, -1.25 };
|
||||
for (auto po : price_offsets) {
|
||||
// complete
|
||||
test_impl(api::SIDE_BUY, qty, po, settle, gates, steps..., test_steps::Complete());
|
||||
test_impl(api::SIDE_SELL, qty, po, settle, gates, steps..., test_steps::Complete());
|
||||
// cancel child
|
||||
test_impl(api::SIDE_BUY, qty, po, settle, gates, steps..., test_steps::CancelChild());
|
||||
test_impl(api::SIDE_SELL, qty, po, settle, gates, steps..., test_steps::CancelChild());
|
||||
// cancel algo
|
||||
test_impl(api::SIDE_BUY, qty, po, settle, gates, steps..., test_steps::CancelAlgo());
|
||||
test_impl(api::SIDE_SELL, qty, po, settle, gates, steps..., test_steps::CancelAlgo());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("test_activation_thresholds.", "[algo][limit_cover]") {
|
||||
SECTION("Activate 1st gate.") {
|
||||
test_permutations(25, 100, {
|
||||
LimitCoverGate(4, 4, 4),
|
||||
LimitCoverGate(10, 7, 5),
|
||||
LimitCoverGate(15, 12, 11),
|
||||
},
|
||||
// test steps
|
||||
test_steps::BeginLaunch(),
|
||||
test_steps::Launch(),
|
||||
test_steps::BreechActivationThresh());
|
||||
}
|
||||
SECTION("Activate 1st then 2nd gate.") {
|
||||
test_permutations(25, 100, {
|
||||
LimitCoverGate(4, 4, 4),
|
||||
LimitCoverGate(10, 7, 5),
|
||||
LimitCoverGate(15, 12, 11),
|
||||
},
|
||||
// test steps
|
||||
test_steps::BeginLaunch(),
|
||||
test_steps::Launch(),
|
||||
test_steps::BreechActivationThresh(),
|
||||
test_steps::Fill([](const auto& state) { return test_params::fills::child(state, 8); }),
|
||||
test_steps::BreechActivationThresh(),
|
||||
test_steps::Fill([](const auto& state) { return test_params::fills::child(state, 2); }),
|
||||
test_steps::BreechActivationThresh());
|
||||
}
|
||||
SECTION("Activate 2nd gate.") {
|
||||
test_permutations(25, 100, {
|
||||
LimitCoverGate(4, 4, 4),
|
||||
LimitCoverGate(10, 7, 5),
|
||||
LimitCoverGate(15, 12, 11),
|
||||
},
|
||||
// test steps
|
||||
test_steps::BeginLaunch(),
|
||||
test_steps::Launch(),
|
||||
test_steps::BreechActivationThresh(1, 1),
|
||||
test_steps::Fill([](const auto& state) { return test_params::fills::child(state, 8); }));
|
||||
}
|
||||
}
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,199 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverState.h"
|
||||
#include "catch/catch.hpp"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/md/IntPrice.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orderman_utils/BookUtils.h"
|
||||
#include "lib/orderman/orderman_utils/Formatter.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverAdapter.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverTestUtils.h"
|
||||
|
||||
#define VALIDATE Validation __VALIDATION(this)
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
using namespace std;
|
||||
using namespace bts::api::orderman;
|
||||
using namespace bts::orderman::limit_cover::utils;
|
||||
using Size = bts::md::Size;
|
||||
using Side = bts::api::Side;
|
||||
|
||||
LimitCoverState::LimitCoverState(
|
||||
const OrderDefinition& def,
|
||||
uint32_t quantity,
|
||||
double price)
|
||||
: driver(def, int_price(price), Quantity(quantity)), _definition(def), _gates(), _book(SecurityKey(security().id())) {
|
||||
auto ngs = limit_cover().normal_gates();
|
||||
_gates.insert(ngs.begin(), ngs.end());
|
||||
// attach callbacks
|
||||
driver.on_child_launching([&](OrderKey key, Quantity child_qty, Price child_price) {
|
||||
// should never deploy twice
|
||||
REQUIRE(_child == null_order_key());
|
||||
REQUIRE(key != null_order_key());
|
||||
_child = key;
|
||||
// qty should match the algo's, but price must be the limit price
|
||||
// (the algo's price is irrelevant)
|
||||
REQUIRE(child_qty.value() == driver.quantity().value());
|
||||
REQUIRE(child_price.value() == limit_price());
|
||||
// todo: verify delete and cover def matches the current gate
|
||||
//REQUIRE(child_def.custom_def().delete_and_cover())
|
||||
REQUIRE(limit_volume() >= activation_threshold());
|
||||
});
|
||||
driver.on_child_changed([&](const ChildOrder&, const ChildOrder& new_state) {
|
||||
REQUIRE(new_state.key == _child);
|
||||
REQUIRE(new_state.def.has_value());
|
||||
REQUIRE(new_state.def.value().has_custom_def());
|
||||
REQUIRE(new_state.def.value().custom_def().has_delete_and_cover());
|
||||
});
|
||||
driver.after_child_changed([&](const ChildOrder&, const ChildOrder& new_state) {
|
||||
REQUIRE(driver.total_fills().value() == new_state.cstate.total_fills.value());
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LimitCoverState::quantity() const {
|
||||
return driver.quantity().value();
|
||||
}
|
||||
double LimitCoverState::price() const {
|
||||
return driver.price().value();
|
||||
}
|
||||
const api::Security& LimitCoverState::security() const {
|
||||
return driver.security();
|
||||
}
|
||||
const bts::api::orderman::OrderDefinition& LimitCoverState::def() const {
|
||||
return _definition;
|
||||
}
|
||||
LimitCoverDefinition LimitCoverState::limit_cover() const {
|
||||
return LimitCoverAdapter::from_proto(def());
|
||||
}
|
||||
uint32_t LimitCoverState::limit_volume() const {
|
||||
return _limit_volume;
|
||||
}
|
||||
double LimitCoverState::limit_price() const {
|
||||
return _limit_price;
|
||||
}
|
||||
OrderKey LimitCoverState::child() const {
|
||||
return _child;
|
||||
}
|
||||
optional<ChildOrder> LimitCoverState::child_state() const {
|
||||
if (has_been_deployed()) {
|
||||
return ChildOrder(*driver.child_orders().begin());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
void LimitCoverState::update_child(const Update& u) {
|
||||
driver.update_child(child(), u);
|
||||
}
|
||||
bool LimitCoverState::has_been_deployed() const {
|
||||
return _child != null_order_key();
|
||||
}
|
||||
bool LimitCoverState::is_terminated() const {
|
||||
switch (driver.state()) {
|
||||
case AlgoState::CANCELED:
|
||||
case AlgoState::TERMINATED:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const std::map<uint32_t, LimitCoverGate>& LimitCoverState::gates() const {
|
||||
return _gates;
|
||||
}
|
||||
const LimitCoverGate& LimitCoverState::curr_gate() const {
|
||||
REQUIRE(!gates().empty());
|
||||
return gates().begin()->second;
|
||||
}
|
||||
uint32_t LimitCoverState::activation_threshold() const {
|
||||
return gates().empty() ? std::numeric_limits<uint32_t>::max() : gates().begin()->first;
|
||||
}
|
||||
// settlement
|
||||
double LimitCoverState::settlement() const { return _settlement; }
|
||||
void LimitCoverState::settlement(double value) {
|
||||
VALIDATE;
|
||||
// the algo is not supposed to disregard settlement changes
|
||||
// after the child has been deployed
|
||||
if (!has_been_deployed()) {
|
||||
_settlement = value;
|
||||
recompute_all();
|
||||
}
|
||||
driver.update_settlement(SecurityKey(security().id()), Price(value));
|
||||
}
|
||||
|
||||
// book
|
||||
const Book& LimitCoverState::book() const { return _book; }
|
||||
void LimitCoverState::book(Book value) {
|
||||
VALIDATE;
|
||||
_book = value;
|
||||
recompute_all();
|
||||
driver.update_book(value);
|
||||
}
|
||||
|
||||
OrderOpResult LimitCoverState::before_launch() {
|
||||
_gates.clear();
|
||||
auto ngs = limit_cover().normal_gates();
|
||||
_gates.insert(ngs.begin(), ngs.end());
|
||||
return driver.before_launch();
|
||||
}
|
||||
void LimitCoverState::launch() {
|
||||
VALIDATE;
|
||||
_is_initialized = true;
|
||||
recompute_all();
|
||||
driver.launch();
|
||||
}
|
||||
void LimitCoverState::do_update(const Update& upd) {
|
||||
REQUIRE(!is_terminated());
|
||||
// updates should not affect the child's price (after it has been launched).
|
||||
// this is bc the literal 'price' parameter is irrelevant to this algo,
|
||||
// for all intents and purposes, its price is the tick offset from settlement.
|
||||
auto upd_valid = validate_update(upd);
|
||||
// keep definition in sync with update
|
||||
if (upd.has_custom() && upd_valid.ok()) {
|
||||
*(_definition.mutable_custom_def()) = upd.custom();
|
||||
}
|
||||
driver.do_update(upd);
|
||||
if (upd_valid.ok()) {
|
||||
recompute_all();
|
||||
} else {
|
||||
REQUIRE(driver.state() == AlgoState::TERMINATED);
|
||||
}
|
||||
}
|
||||
|
||||
void LimitCoverState::recompute_limit_price() {
|
||||
_limit_price = os::is_nan(settlement()) ? os::nan() :
|
||||
securities::improve_price(side(), Price(settlement()), Price(limit_cover().price_offset()))
|
||||
.value();
|
||||
}
|
||||
|
||||
void LimitCoverState::recompute_limit_volume() {
|
||||
_limit_volume = os::is_nan(settlement()) ? os::nan() :
|
||||
BookUtils::volume_at_or_better(_book, side(), limit_price());
|
||||
}
|
||||
|
||||
void LimitCoverState::recompute_all() {
|
||||
recompute_limit_price();
|
||||
recompute_limit_volume();
|
||||
if (_is_initialized)
|
||||
recompute_gate();
|
||||
}
|
||||
|
||||
void LimitCoverState::recompute_gate() {
|
||||
if (gates().empty())
|
||||
return;
|
||||
auto it = _gates.lower_bound(limit_volume());
|
||||
if (it->first > limit_volume() && it != _gates.begin())
|
||||
it--;
|
||||
_gates.erase(_gates.begin(), it);
|
||||
}
|
||||
|
||||
LimitCoverState::ValidateDeployment::ValidateDeployment(LimitCoverState* state) :
|
||||
_state(state), _had_been_deployed(state->has_been_deployed()) { }
|
||||
|
||||
LimitCoverState::ValidateDeployment::~ValidateDeployment() {
|
||||
if (!_had_been_deployed && _state->limit_volume() >= _state->activation_threshold())
|
||||
REQUIRE(_state->has_been_deployed());
|
||||
}
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/LimitCoverAlgo.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/limit_cover/LimitCoverDefinition.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/AlgoTestDriver.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
struct LimitCoverState {
|
||||
AlgoTestDriver<LimitCoverAlgo> driver;
|
||||
|
||||
LimitCoverState(
|
||||
const api::orderman::OrderDefinition& def,
|
||||
uint32_t quantity,
|
||||
double price);
|
||||
|
||||
const api::orderman::OrderDefinition& def() const;
|
||||
uint32_t quantity() const;
|
||||
double price() const;
|
||||
const api::Security& security() const;
|
||||
LimitCoverDefinition limit_cover() const;
|
||||
api::Side side() const { return def().side(); }
|
||||
uint32_t activation_threshold() const;
|
||||
// main child
|
||||
OrderKey child() const;
|
||||
std::optional<ChildOrder> child_state() const;
|
||||
// gates/curr gate
|
||||
const std::map<uint32_t, LimitCoverGate>& gates() const;
|
||||
const LimitCoverGate& curr_gate() const;
|
||||
// book
|
||||
const Book& book() const;
|
||||
void book(Book new_book);
|
||||
//
|
||||
bool has_been_deployed() const;
|
||||
// settle
|
||||
double settlement() const;
|
||||
void settlement(double value);
|
||||
// limit price
|
||||
double limit_price() const;
|
||||
// limit volume
|
||||
uint32_t limit_volume() const;
|
||||
bool is_terminated() const;
|
||||
OrderOpResult before_launch();
|
||||
void launch();
|
||||
void do_update(const api::orderman::Update&);
|
||||
void update_child(const api::orderman::Update&);
|
||||
|
||||
private:
|
||||
api::orderman::OrderDefinition _definition;
|
||||
uint32_t _limit_volume = 0;
|
||||
OrderKey _child;
|
||||
double _limit_price = os::nan();
|
||||
double _settlement = os::nan();
|
||||
bool _is_initialized = false;
|
||||
std::map<uint32_t, LimitCoverGate> _gates;
|
||||
Book _book;
|
||||
|
||||
void recompute_limit_price();
|
||||
void recompute_limit_volume();
|
||||
void recompute_all();
|
||||
void recompute_gate();
|
||||
|
||||
struct ValidateDeployment {
|
||||
ValidateDeployment(LimitCoverState* state);
|
||||
~ValidateDeployment();
|
||||
|
||||
private:
|
||||
LimitCoverState* _state;
|
||||
bool _had_been_deployed;
|
||||
};
|
||||
|
||||
struct Validation : public ValidateDeployment {
|
||||
Validation(LimitCoverState* state)
|
||||
: ValidateDeployment(state) {}
|
||||
|
||||
~Validation() {}
|
||||
};
|
||||
};
|
||||
|
||||
// custom formatting uses 1 line (unlike pb::to_string).
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const bts::spark::QuantityMeasure& qm) {
|
||||
switch (qm.quantity_case()) {
|
||||
case bts::spark::QuantityMeasure::kAbsolute:
|
||||
ostr << "[absolute] " << qm.absolute();
|
||||
break;
|
||||
case bts::spark::QuantityMeasure::kRatio:
|
||||
ostr << "[ratio] " << qm.ratio();
|
||||
break;
|
||||
default:
|
||||
ostr << "[quantity_case_" << static_cast<int>(qm.quantity_case()) << "]";
|
||||
break;
|
||||
}
|
||||
return ostr;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& ostr, const LimitCoverState& lcs) {
|
||||
ostr << "qty: " << lcs.quantity() << std::endl;
|
||||
ostr << "limit price: " << lcs.limit_price() << std::endl;
|
||||
ostr << "limit volume: " << lcs.limit_volume() << std::endl;
|
||||
ostr << "activation/curr gate thresh: " << lcs.activation_threshold() << std::endl;
|
||||
ostr << "settlement: " << lcs.settlement() << std::endl;
|
||||
ostr << "bbo: " << Formatter::to_string(lcs.book()) << std::endl;
|
||||
ostr << "has been deployed: " << lcs.has_been_deployed() << std::endl;
|
||||
ostr << "status: " << lcs.driver.state() << std::endl;
|
||||
ostr << "child: " << lcs.child() << std::endl;
|
||||
ostr << "child qty: " << (lcs.child_state().has_value() ? lcs.child_state().value().cstate.quantity.value() : 0) << std::endl;
|
||||
ostr << "filled qty: " << lcs.driver.total_fills().value() << std::endl;
|
||||
return ostr;
|
||||
}
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/md/IntPrice.h"
|
||||
#include "lib/orderman/orderman_utils/Formatter.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include <iostream>
|
||||
|
||||
#define SUMMARIZE(...) Summary __summary__ (Formatter::fmt(__VA_ARGS__))
|
||||
|
||||
using Side = bts::api::Side;
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
using namespace std;
|
||||
inline bts::md::IntPrice int_price(double value) {
|
||||
return bts::md::IntPrice_from_double<8>(value);
|
||||
}
|
||||
inline bts::md::PriceSize price_size(double price, uint32_t size) {
|
||||
return bts::md::PriceSize(int_price(price), size);
|
||||
}
|
||||
inline bts::spark::QuantityMeasure qty_measure(uint32_t abs) {
|
||||
bts::spark::QuantityMeasure qm;
|
||||
qm.set_absolute(abs);
|
||||
return qm;
|
||||
}
|
||||
|
||||
struct Summary {
|
||||
Summary(std::string header)
|
||||
: _header(header) {
|
||||
std::cout << "Begin " << header << std::endl;
|
||||
}
|
||||
~Summary() {
|
||||
std::cout << "End " << _header << std::endl;
|
||||
}
|
||||
private:
|
||||
std::string _header;
|
||||
};
|
||||
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,221 @@
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/TestSteps.h"
|
||||
#include "catch/catch.hpp"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverTestUtils.h"
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
|
||||
std::string ITestStep::name() const { return "step with no name"; }
|
||||
void ITestStep::execute(LimitCoverState &state) const {
|
||||
SUMMARIZE(name());
|
||||
assert_pre_conditions(state);
|
||||
_execute(state);
|
||||
assert_post_conditions(state);
|
||||
}
|
||||
void ITestStep::assert_pre_conditions_core(const LimitCoverState &) const {}
|
||||
void ITestStep::execute_core(LimitCoverState &) const { bts_assert(false); }
|
||||
void ITestStep::assert_post_conditions_core(const LimitCoverState &) const {}
|
||||
void ITestStep::assert_pre_conditions(const LimitCoverState &state) const {
|
||||
// SUMMARIZE("Asserting Pre-conditions (", name(), ")...");
|
||||
assert_pre_conditions_core(state);
|
||||
}
|
||||
void ITestStep::assert_post_conditions(const LimitCoverState &state) const {
|
||||
// SUMMARIZE("Asserting Post-conditions (", name(), ")...");
|
||||
assert_post_conditions_core(state);
|
||||
}
|
||||
void ITestStep::_execute(LimitCoverState &state) const {
|
||||
// SUMMARIZE("Executing (", name(), ")...");
|
||||
execute_core(state);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& ostr, const ITestStep& ts) {
|
||||
ostr << ts.name();
|
||||
return ostr;
|
||||
}
|
||||
|
||||
LimitCoverState& operator<<(LimitCoverState& state, const ITestStep& ts) {
|
||||
SUMMARIZE(ts.name());
|
||||
ts.execute(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// Test Params
|
||||
/////////////////////
|
||||
namespace test_params {
|
||||
namespace fills {
|
||||
Fill child(const LimitCoverState& state, std::optional<uint32_t> qty, std::optional<double> price) {
|
||||
if (!qty.has_value()) {
|
||||
REQUIRE(state.child_state().value().active_qty() > 0);
|
||||
}
|
||||
if (!price.has_value()) {
|
||||
REQUIRE(state.child_state().has_value());
|
||||
}
|
||||
return test_params::Fill(
|
||||
qty.has_value() ? qty.value() : state.child_state().value().active_qty(),
|
||||
price.has_value() ? price.value() : state.child_state().value().cstate.price.value());
|
||||
}
|
||||
} // namespace fills
|
||||
} // namespace test_params
|
||||
|
||||
/////////////////////
|
||||
// Order Selectors
|
||||
/////////////////////
|
||||
namespace order_selectors {
|
||||
std::vector<OrderKey> child(const LimitCoverState& state) {
|
||||
std::vector<OrderKey> rv;
|
||||
if (state.has_been_deployed()) {
|
||||
rv.push_back(state.child());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
std::vector<OrderKey> self(const LimitCoverState&) {
|
||||
std::vector<OrderKey> rv;
|
||||
rv.push_back(null_order_key());
|
||||
return rv;
|
||||
}
|
||||
} // namespace order_selectors
|
||||
|
||||
/////////////////////////
|
||||
// Order Ops (step impls)
|
||||
/////////////////////////
|
||||
namespace ops {
|
||||
void begin_launch(LimitCoverState& state) {
|
||||
REQUIRE(state.driver.before_launch().ok());
|
||||
}
|
||||
|
||||
void launch(LimitCoverState& state) {
|
||||
state.driver.launch();
|
||||
}
|
||||
|
||||
void breech_activation_thresh(LimitCoverState& state, uint32_t thresh_count, int offset) {
|
||||
auto book = state.book();
|
||||
REQUIRE(!os::is_nan(state.limit_price()));
|
||||
auto gate_it = state.gates().upper_bound(state.limit_volume());
|
||||
REQUIRE(gate_it != state.gates().end());
|
||||
while (thresh_count-- > 0 && gate_it != state.gates().end())
|
||||
gate_it++;
|
||||
auto thresh = gate_it->first + offset;
|
||||
int gi = 1;
|
||||
auto& bs = book.side(state.side());
|
||||
if (bs[0].size() == 0) {
|
||||
bs[0] = price_size(state.limit_price(), thresh);
|
||||
} else if (BookUtils::price_comp(state.side())(state.limit_price(), bs[0].price())) {
|
||||
// shift existing levels down 1 index,
|
||||
// put the limit volume at the top
|
||||
for (int i = bs.size() - 1; i > 0; i--) {
|
||||
bs[i] = bs[i - 1];
|
||||
}
|
||||
bs[0] = price_size(state.limit_price(), thresh);
|
||||
} else {
|
||||
// add the difference to the qty for the best price
|
||||
bs[0] = price_size(bs[0].price(), bs[0].size() + thresh - state.limit_volume());
|
||||
}
|
||||
state.book(book);
|
||||
}
|
||||
|
||||
void fill_child(LimitCoverState& state, uint32_t qty, double price, bool allow_overfill) {
|
||||
SUMMARIZE("filling child: ", Formatter::fmt_order(state.side(), qty, price));
|
||||
bts_assert(os::is_finite(price));
|
||||
bts_assert(qty > 0);
|
||||
REQUIRE(state.has_been_deployed());
|
||||
REQUIRE(!state.is_terminated());
|
||||
uint32_t orig_filled_qty = state.driver.total_fills().value();
|
||||
if (state.child_state().has_value()) {
|
||||
auto cs = state.child_state().value();
|
||||
REQUIRE(((cs.cstate.total_fills.value() - cs.cstate.fills_at_attach_time.value()) == orig_filled_qty));
|
||||
if (!allow_overfill) {
|
||||
REQUIRE(cs.active_qty() >= qty);
|
||||
}
|
||||
} else {
|
||||
REQUIRE(orig_filled_qty == 0);
|
||||
}
|
||||
state.driver.fill(state.child(), Price(price), Quantity(qty));
|
||||
}
|
||||
|
||||
void fill_child(LimitCoverState& lcs, uint32_t qty, bool allow_overfill) {
|
||||
fill_child(lcs, qty, lcs.limit_price(), allow_overfill);
|
||||
}
|
||||
|
||||
void cancel_child(LimitCoverState& state) {
|
||||
REQUIRE(state.has_been_deployed());
|
||||
REQUIRE(!state.is_terminated());
|
||||
state.driver.cancel_child(state.child());
|
||||
REQUIRE(state.driver.state() == AlgoState::TERMINATED);
|
||||
}
|
||||
|
||||
void complete(LimitCoverState& state) {
|
||||
REQUIRE(state.has_been_deployed());
|
||||
REQUIRE(!state.is_terminated());
|
||||
REQUIRE(state.driver.quantity().value() > state.driver.total_fills().value());
|
||||
fill_child(state, state.driver.quantity().value() - state.driver.total_fills().value());
|
||||
REQUIRE(state.driver.state() == AlgoState::COMPLETED);
|
||||
}
|
||||
} // namespace ops
|
||||
|
||||
/////////////////////
|
||||
// Test Steps
|
||||
/////////////////////
|
||||
namespace test_steps {
|
||||
// Begin Launch
|
||||
std::string BeginLaunch::name() const { return "Begin Launch"; }
|
||||
void BeginLaunch::execute_core(LimitCoverState &state) const { ops::begin_launch(state); }
|
||||
|
||||
// Launch
|
||||
std::string Launch::name() const { return "Launch"; }
|
||||
void Launch::execute_core(LimitCoverState &state) const { ops::launch(state); }
|
||||
|
||||
// Breech Activation Threshold
|
||||
std::string BreechActivationThresh::name() const {
|
||||
std::stringstream title;
|
||||
title << "Breech Activation Threshold " << (thresh_count + 1);
|
||||
if (offset > 0) {
|
||||
title << " (+" << offset << ")";
|
||||
} else if (offset < 0) {
|
||||
title << " (" << offset << ")";
|
||||
}
|
||||
return title.str();
|
||||
}
|
||||
void BreechActivationThresh::execute_core(LimitCoverState &state) const {
|
||||
ops::breech_activation_thresh(state, thresh_count, offset);
|
||||
}
|
||||
|
||||
// Fill
|
||||
std::string Fill::name() const { return "Fill"; }
|
||||
void Fill::execute_core(LimitCoverState &state) const {
|
||||
auto p = get_params(state);
|
||||
ops::fill_child(state, p.quantity, p.price);
|
||||
}
|
||||
|
||||
// Complete
|
||||
std::string Complete::name() const { return "Complete"; }
|
||||
void Complete::execute_core(LimitCoverState &state) const {
|
||||
ops::complete(state);
|
||||
}
|
||||
|
||||
// Cancel Child
|
||||
std::string CancelChild::name() const { return "Cancel Child"; }
|
||||
void CancelChild::execute_core(LimitCoverState& state) const {
|
||||
ops::cancel_child(state);
|
||||
}
|
||||
|
||||
// Cancel Algo
|
||||
std::string CancelAlgo::name() const { return "Cancel Algo"; }
|
||||
void CancelAlgo::execute_core(LimitCoverState& state) const {
|
||||
state.driver.cancel();
|
||||
REQUIRE(state.driver.state() == AlgoState::CANCELED);
|
||||
}
|
||||
} // namespace test_steps
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
#include "catch/catch.hpp"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/orders/algo/algo_orders/tests/limit_cover/LimitCoverState.h"
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace test {
|
||||
namespace limit_cover {
|
||||
// In Visual Studio, intellisense and syntax highlighting don't work (at all)
|
||||
// when <optional> is included, unless 'using namespace std;' is specified.
|
||||
using namespace std;
|
||||
|
||||
struct ITestStep {
|
||||
virtual std::string name() const;
|
||||
void execute(LimitCoverState& state) const;
|
||||
protected:
|
||||
virtual void assert_pre_conditions_core(const LimitCoverState&) const;
|
||||
virtual void execute_core(LimitCoverState&) const;
|
||||
virtual void assert_post_conditions_core(const LimitCoverState&) const;
|
||||
private:
|
||||
void assert_pre_conditions(const LimitCoverState& state) const;
|
||||
void assert_post_conditions(const LimitCoverState& state) const;
|
||||
void _execute(LimitCoverState& state) const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& ostr, const ITestStep& ts);
|
||||
LimitCoverState& operator<<(LimitCoverState& state, const ITestStep& ts);
|
||||
|
||||
namespace order_selectors {
|
||||
std::vector<OrderKey> child(const LimitCoverState &lcs);
|
||||
std::vector<OrderKey> self(const LimitCoverState &);
|
||||
} // namespace order_selectors
|
||||
|
||||
namespace ops {
|
||||
void begin_launch(LimitCoverState& lcs);
|
||||
void launch(LimitCoverState& lcs);
|
||||
void breech_activation_thresh(LimitCoverState& lcs, uint32_t thresh_count = 0, int offset = 0);
|
||||
|
||||
void fill_child(LimitCoverState& lcs, uint32_t qty, double price, bool allow_overfill = false);
|
||||
void fill_child(LimitCoverState& lcs, uint32_t qty, bool allow_overfill = false);
|
||||
|
||||
void cancel_child(LimitCoverState& state);
|
||||
void complete(LimitCoverState& lcs);
|
||||
}
|
||||
|
||||
namespace test_params {
|
||||
struct Fill {
|
||||
uint32_t quantity;
|
||||
double price;
|
||||
|
||||
Fill(uint32_t quantity, double price)
|
||||
: quantity(quantity), price(price) {}
|
||||
};
|
||||
|
||||
namespace fills {
|
||||
Fill child(const LimitCoverState& state, optional<uint32_t> qty = {}, optional<double> price = {});
|
||||
} // namespace fills
|
||||
} // namespace test_params
|
||||
namespace test_steps {
|
||||
struct BeginLaunch : public ITestStep {
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct Launch : public ITestStep {
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct BreechActivationThresh : public ITestStep {
|
||||
uint32_t thresh_count;
|
||||
int offset;
|
||||
|
||||
BreechActivationThresh(uint32_t thresh_count = 0, int offset = 0)
|
||||
: thresh_count(thresh_count), offset(offset) { }
|
||||
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct Fill : public ITestStep {
|
||||
std::function<test_params::Fill(const LimitCoverState&)> get_params;
|
||||
|
||||
Fill(std::function<test_params::Fill(const LimitCoverState&)> get_params)
|
||||
: get_params(get_params) {}
|
||||
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct Complete : public ITestStep {
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct CancelChild : public ITestStep {
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct CancelAlgo : public ITestStep {
|
||||
std::string name() const override;
|
||||
void execute_core(LimitCoverState& state) const override;
|
||||
};
|
||||
|
||||
struct Custom : public ITestStep {
|
||||
Custom(std::string name, std::function<void(LimitCoverState&)> impl)
|
||||
: _name(name), _impl(impl) { }
|
||||
|
||||
std::string name() const override { return _name; }
|
||||
void execute_core(LimitCoverState& state) const override { _impl(state); }
|
||||
private:
|
||||
std::string _name;
|
||||
std::function<void(LimitCoverState&)> _impl;
|
||||
};
|
||||
} // namespace test_steps
|
||||
} // namespace limit_cover
|
||||
} // namespace test
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -577,7 +577,7 @@ TEST_CASE("Increase then decrease", "[algo][splitter]") {
|
||||
|
||||
// Set up some instant fills
|
||||
bool applied_second_update = false;
|
||||
state.on_child_launching([&, update](const auto &) mutable {
|
||||
state.on_child_launching([&, update](const auto &, auto, auto) mutable {
|
||||
if (!applied_second_update) {
|
||||
applied_second_update = true;
|
||||
update.mutable_custom()->mutable_splitter()->set_exposed_qty(350);
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "lib/plugin/SparkServices.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <cstdarg>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
namespace dynamic_adapter {
|
||||
#define NOT_IMPLEMENTED bts_assert_msg(false, "Not implemented.")
|
||||
#define _CONCAT_(x,y) x##y
|
||||
#define VALUE_CASE(x) bts::api::orderman::DynamicOrderParam::_CONCAT_(kAs,x)
|
||||
|
||||
#define VALIDATE_PROTO(x,y)\
|
||||
template<> inline bool validate_proto<x>(const bts::api::orderman::DynamicOrderParam& p) { return p.value_case() == VALUE_CASE(y); }
|
||||
|
||||
#define _ASSIGN_TO_PROTO_(x,y)\
|
||||
template<> inline void assign_to_proto<x>(bts::api::orderman::DynamicOrderParam& param, x value) { param.y(value); }
|
||||
#define ASSIGN_TO_PROTO(x,y) _ASSIGN_TO_PROTO_(x,_CONCAT_(set_as_,y))
|
||||
|
||||
#define _GET_FROM_PROTO_(x,y)\
|
||||
template<> inline x get_from_proto<x>(const bts::api::orderman::DynamicOrderParam& param) { return param.y(); }
|
||||
#define GET_FROM_PROTO(x,y) _GET_FROM_PROTO_(x,_CONCAT_(as_,y))
|
||||
|
||||
#define _DEFINE_PARAM_(x,y)\
|
||||
template<> inline void define_param<x>(bts::api::orderman::DynamicOrderDescriptor& desc, std::string name) {\
|
||||
*desc.add_param_desc() = y(name);\
|
||||
}
|
||||
#define DEFINE_PARAM(x,y) _DEFINE_PARAM_(x,_CONCAT_(param_,y))
|
||||
|
||||
#define PRIMITIVE_PROTO_IMPL(x,y,z)\
|
||||
VALIDATE_PROTO(x,z)\
|
||||
ASSIGN_TO_PROTO(x,y)\
|
||||
GET_FROM_PROTO(x,y)\
|
||||
DEFINE_PARAM(x,y)
|
||||
|
||||
using CustomOrderDefinition = bts::api::orderman::CustomOrderDefinition;
|
||||
using DynamicOrderParam = bts::api::orderman::DynamicOrderParam;
|
||||
using DynamicOrderEnumerated = bts::api::orderman::DynamicOrderEnumerated;
|
||||
|
||||
template<class T> inline bool validate_proto(const DynamicOrderParam&) { NOT_IMPLEMENTED; return false; }
|
||||
template<class T> inline void assign_to_proto(DynamicOrderParam&, T) { NOT_IMPLEMENTED; }
|
||||
template<class T> inline T get_from_proto(const api::orderman::DynamicOrderParam&) { NOT_IMPLEMENTED; return T(); }
|
||||
template<class T> inline void define_param(bts::api::orderman::DynamicOrderDescriptor&, std::string) { NOT_IMPLEMENTED; }
|
||||
|
||||
// primitives
|
||||
PRIMITIVE_PROTO_IMPL(int, int32, Int32)
|
||||
PRIMITIVE_PROTO_IMPL(uint32_t, uint32, Uint32)
|
||||
PRIMITIVE_PROTO_IMPL(double, double, Double)
|
||||
PRIMITIVE_PROTO_IMPL(bool, bool, Bool)
|
||||
|
||||
// order def
|
||||
VALIDATE_PROTO(bts::api::orderman::CustomOrderDefinition, OrderDef)
|
||||
GET_FROM_PROTO(bts::api::orderman::CustomOrderDefinition, order_def)
|
||||
template<> inline void assign_to_proto<CustomOrderDefinition>(DynamicOrderParam& param, CustomOrderDefinition value) {
|
||||
*(param.mutable_as_order_def()) = value;
|
||||
}
|
||||
template<> inline void define_param<CustomOrderDefinition>(api::orderman::DynamicOrderDescriptor& desc, std::string name) {
|
||||
*desc.add_param_desc() = param_order(name);
|
||||
}
|
||||
|
||||
// enumerated
|
||||
VALIDATE_PROTO(bts::api::orderman::DynamicOrderEnumerated, Enumerated)
|
||||
GET_FROM_PROTO(bts::api::orderman::DynamicOrderEnumerated, enumerated)
|
||||
template<> inline void assign_to_proto<DynamicOrderEnumerated>(DynamicOrderParam& param, DynamicOrderEnumerated value) {
|
||||
*(param.mutable_as_enumerated()) = value;
|
||||
}
|
||||
|
||||
template<class TDef, class TValue>
|
||||
struct ParamKey {
|
||||
ParamKey(std::string name, std::function<TValue(const TDef&)> get, std::function<void(TDef&, TValue)> set)
|
||||
: _name(name), _get(get), _set(set) { }
|
||||
|
||||
const std::string& name() const { return _name; }
|
||||
virtual void assign_to_def(const api::orderman::DynamicOrderParam& param, TDef& def) {
|
||||
if (dynamic_adapter::validate_proto<TValue>(param))
|
||||
_set(def, get_from_proto<TValue>(param));
|
||||
}
|
||||
virtual void assign_to_proto(const TDef& def, api::orderman::DynamicOrderParam& param) {
|
||||
dynamic_adapter::assign_to_proto<TValue>(param, _get(def));
|
||||
}
|
||||
virtual void assign_to_proto(const TDef& def, api::orderman::DynamicOrderDefinition& dyn) {
|
||||
api::orderman::DynamicOrderParam dop;
|
||||
assign_to_proto(def, dop);
|
||||
(*dyn.mutable_order_params())[name()] = dop;
|
||||
}
|
||||
virtual void define_param(api::orderman::DynamicOrderDescriptor& desc) {
|
||||
dynamic_adapter::define_param<TValue>(desc, name());
|
||||
}
|
||||
virtual bool validate_proto(const api::orderman::DynamicOrderDefinition& dyn) {
|
||||
auto it = dyn.order_params().find(name());
|
||||
return it != dyn.order_params().end() && dynamic_adapter::validate_proto<TValue>(it->second);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _name;
|
||||
std::function<TValue(const TDef&)> _get;
|
||||
std::function<void(TDef&, TValue)> _set;
|
||||
};
|
||||
|
||||
template<class TDef, class TValue>
|
||||
struct EnumParamKey : public ParamKey<TDef, TValue> {
|
||||
EnumParamKey(std::string name, std::vector<std::pair<TValue, std::string>> named_values, std::function<TValue(const TDef&)> get, std::function<void(TDef&, TValue)> set)
|
||||
: ParamKey<TDef, TValue>(name, get, set), _name(name), _named_values(named_values), _get(get), _set(set) {
|
||||
}
|
||||
|
||||
void assign_to_def(const api::orderman::DynamicOrderParam& param, TDef& def) override {
|
||||
if (dynamic_adapter::validate_proto<DynamicOrderEnumerated>(param))
|
||||
_set(def, _from_proto(param.as_enumerated()));
|
||||
}
|
||||
|
||||
void assign_to_proto(const TDef& def, api::orderman::DynamicOrderParam& param) override {
|
||||
dynamic_adapter::assign_to_proto(param, _to_proto(_get(def)));
|
||||
}
|
||||
|
||||
void assign_to_proto(const TDef& def, api::orderman::DynamicOrderDefinition& dyn) override {
|
||||
api::orderman::DynamicOrderParam param;
|
||||
assign_to_proto(def, param);
|
||||
(*dyn.mutable_order_params())[_name] = param;
|
||||
}
|
||||
|
||||
void define_param(api::orderman::DynamicOrderDescriptor& order_desc) override {
|
||||
// enum desc
|
||||
api::orderman::DynamicOrderEnumeratedDescriptor enum_desc;
|
||||
for (auto& p : _named_values) {
|
||||
enum_desc.add_element(p.second);
|
||||
}
|
||||
// param type desc
|
||||
api::orderman::DynamicOrderTypeDescriptor param_type_desc;
|
||||
*param_type_desc.mutable_enumerated() = enum_desc;
|
||||
// param desc
|
||||
api::orderman::DynamicOrderParamDescriptor param_desc;
|
||||
param_desc.set_param_name(_name);
|
||||
*param_desc.mutable_param_type() = param_type_desc;
|
||||
// order desc
|
||||
*order_desc.add_param_desc() = param_desc;
|
||||
}
|
||||
|
||||
bool validate_proto(const api::orderman::DynamicOrderDefinition& dyn) override {
|
||||
auto it = dyn.order_params().find(_name);
|
||||
return it != dyn.order_params().end() && it->second.value_case() == VALUE_CASE(Enumerated);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _name;
|
||||
std::vector<std::pair<TValue, std::string>> _named_values;
|
||||
|
||||
DynamicOrderEnumerated _to_proto(TValue v) {
|
||||
DynamicOrderEnumerated deo;
|
||||
for (auto p : _named_values) {
|
||||
if (p.first == v) {
|
||||
deo.set_enum_value(p.second);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return deo;
|
||||
}
|
||||
|
||||
TValue _from_proto(const std::string& enum_name) {
|
||||
for (auto p : _named_values) {
|
||||
if (p.second == enum_name)
|
||||
return p.first;
|
||||
}
|
||||
bts_assert_msg(false, "Failed to match enum name to enum value.");
|
||||
return TValue();
|
||||
}
|
||||
TValue _from_proto(DynamicOrderEnumerated deo) {
|
||||
return _from_proto(deo.enum_value());
|
||||
}
|
||||
|
||||
std::function<TValue(const TDef&)> _get;
|
||||
std::function<void(TDef&, TValue)> _set;
|
||||
};
|
||||
|
||||
template<class TParam, class... TNextParam>
|
||||
inline void define_proto(api::orderman::DynamicOrderDescriptor& desc, TParam param, TNextParam... next_param) {
|
||||
param.define_param(desc);
|
||||
(define_proto(desc, next_param), ...);
|
||||
}
|
||||
|
||||
template<class TDef, class TParam, class... TNextParam>
|
||||
inline void from_proto(const google::protobuf::Map<std::string, api::orderman::DynamicOrderParam>& map, TDef& def, TParam param, TNextParam... next_param) {
|
||||
auto pit = map.find(param.name());
|
||||
if (pit != map.end()) {
|
||||
param.assign_to_def(pit->second, def);
|
||||
}
|
||||
(from_proto(map, def, next_param), ...);
|
||||
}
|
||||
|
||||
template<class TDef, class TParam, class... TNextParam>
|
||||
inline void to_proto(api::orderman::DynamicOrderDefinition& dyn, const TDef& def, TParam param_key, TNextParam... next_param) {
|
||||
param_key.assign_to_proto(def, dyn);
|
||||
(to_proto(dyn, def, next_param), ...);
|
||||
}
|
||||
|
||||
template<class TParam, class... TNextParam>
|
||||
inline bool validate_proto(const api::orderman::DynamicOrderDefinition& dyn, TParam param, TNextParam... next_param) {
|
||||
return param.validate_proto(dyn) && validate_proto(dyn, next_param...);
|
||||
}
|
||||
|
||||
template<typename TDef, typename... T>
|
||||
struct DynamicOrderAdapter {
|
||||
void define_proto(api::orderman::DynamicOrderDescriptor& desc) { (dynamic_adapter::define_proto(desc, T()), ...); }
|
||||
void from_proto(const api::orderman::DynamicOrderDefinition& dyn, TDef& def) { (dynamic_adapter::from_proto(dyn.order_params(), def, T()), ...); }
|
||||
void to_proto(api::orderman::DynamicOrderDefinition& dyn, const TDef& def) { (dynamic_adapter::to_proto(dyn, def, T()), ...); }
|
||||
bool validate_proto(const api::orderman::DynamicOrderDefinition& dyn) { return (T().validate_proto(dyn), ...); }
|
||||
};
|
||||
} // namespace dynamic_adapter
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user