Compare commits

...
78 Commits
Author SHA1 Message Date
Taha Afzal 888b4e22c0 Ignoring strips with > 512 components 2020-06-05 14:11:34 -04:00
Taha Afzal 534ce5a8ec strips are XL too 2020-06-05 13:12:57 -04:00
Taha Afzal c2b9ac57dc Fixing build 2020-06-05 11:55:54 -04:00
Taha Afzal 2783361325 Merge branch 'master' into DJ_ICE 2020-06-05 10:50:42 -04:00
Taha Afzal 46d35131d7 Overridable cpps 2020-06-05 10:01:18 -04:00
Pedro Pinto 3b95154c04 Updated ICE Onixs 2020-06-05 09:11:03 -04:00
Pedro Pinto 1a6b918853 Updated docker 2020-06-04 13:43:25 -04:00
Pedro Pinto bc22605a25 Added +x 2020-06-04 13:43:00 -04:00
Pedro Pinto f3d28add1c Added publishing of new routing info when orders are recorevered from disk 2020-06-04 13:41:34 -04:00
Taha Afzal 63d260a6f8 Security formatting for ICE 2020-06-03 09:42:44 -04:00
Pedro Pinto a502c365e5 Build tweaks 2020-05-28 12:15:16 -04:00
Pedro Pinto fdacd8b543 Added -f when removing simlinks in build setup. 2020-05-28 12:02:46 -04:00
John Colvin 3d1de9a0c7 nuget_restore.cmd uses externals package source (builds offline) 2020-05-28 12:02:31 -04:00
John Colvin b2b3fa5949 update jenkinsbuild.cmd to use externals nuget source (for windows) 2020-05-28 12:02:17 -04:00
Pedro Pinto 86ecf69820 Updated external subrepos 2020-05-28 11:53:51 -04:00
Pedro Pinto 8f537a8fda Merged in build_improvements (pull request #414)
Build improvements
2020-05-26 16:32:43 +00:00
Pedro Pinto 01b9ff6b47 Dropped celoxica 2020-05-26 12:28:35 -04:00
Pedro Pinto 2609663d3e Updated setup_builds to work even if its run outside the tree. 2020-05-26 12:06:08 -04:00
Pedro Pinto fd89241611 Updated externals pointer 2020-05-26 11:49:29 -04:00
John Colvin a69118c5d3 fix line endings in release_notes 2020-05-22 23:42:27 -05:00
John Colvin 5accd0e2b4 Merge branch 'master' into prod 2020-05-22 19:14:16 -05:00
John Colvin df5ad61cd2 release notes for synthetic gui integer tick sizes 2020-05-22 19:12:29 -05:00
John Colvin 4a7674eb29 synthetic gui supports integer tick sizes 2020-05-22 18:15:53 -05:00
Eric Harding 5a458287f4 update license search info in disable_onixs.py 2020-05-22 15:18:46 -04:00
John Colvin 5921f9f6b5 Merge branch 'master' into prod 2020-05-22 12:34:18 -05:00
John Colvin 4c73a63ae8 release notes for bug fix (unsorted custom state grid params in orderbook) 2020-05-22 12:30:53 -05:00
John Colvin 42b1c921a8 OrderBook custom state grid params are sorted alphabetically (again) 2020-05-22 12:24:42 -05:00
Pedro Pinto fab3334b1e Updated release notes 2020-05-22 11:23:03 -04:00
Pedro Pinto 7239dd3ade Merge branch 'master' into prod 2020-05-22 11:20:17 -04:00
John Colvin dd4c8eb649 Merged csharp-api-examples-sln into master 2020-05-22 10:19:13 -05:00
John Colvin 76a29bdc8a client ui supports all integer tick sizes (not just 1) 2020-05-21 15:26:50 -05:00
John Colvin 06d708f3ea new csharp api examples 2020-05-21 15:17:32 -05:00
John Colvin e2f70cbc19 minor config tweaks needed after upgrading VisualStudio 2020-05-21 13:04:00 -05:00
Pedro Pinto 9d54e47a03 Update CMakeDeps 2020-05-19 17:00:24 -04:00
John Colvin faccdf581e Merged in limit-algo-clean-history (pull request #413)
Limit algo clean history
2020-05-19 13:53:07 +00:00
John Colvin 1a64714a54 always log errors observed in LadderGridHost._volume_from_row (not just when it fails after max_num_tries) 2020-05-18 17:03:39 -05:00
John Colvin 3ba6792a85 wrap LadderGridHost._volume_from_row in try/catch (addresses Pure client crash) 2020-05-18 16:46:23 -05:00
John Colvin 10e75f30e6 fix algo params grid enum template for GenericOrderVMs 2020-05-15 13:16:25 -05:00
John Colvin 20eac19c4f GenericOrderVM - normalize white space 2020-05-15 13:16:25 -05:00
John Colvin 8c0765a2c7 fix build 2020-05-15 10:34:59 -05:00
Taha Afzal 7a1f88e813 Merge branch 'limit-algo-clean-history' of bitbucket.org:bluetradesys/spark into limit-algo-clean-history 2020-05-14 22:01:32 -04:00
Taha Afzal 3420f14c8a DeleteAndCover detached hedges by default: 2020-05-14 22:01:22 -04:00
John Colvin 86277dfbfe config toggle for new 'specific' CustomOrderDefinition params dialog (disabled by default) 2020-05-14 20:14:51 -05:00
John Colvin 4839e464c6 added dialog to edit CustomOrderDefinition params of a specific algo node 2020-05-14 20:02:14 -05:00
John Colvin dc2ad071a9 GenericOrderHelper should use 'FirstOrDefault' (not 'First') 2020-05-14 19:59:06 -05:00
John Colvin 65e49992f9 LimitCoverAlgo.do_update - remove redundant conversion from DynamicOrderDefinition to LimitCoverDefinition 2020-05-14 17:08:01 -05:00
John Colvin afbb9ae92d LimitCover algo - 'Hedge Ticks' and 'Top Ticks Offset' params actually assigned to DeleteAndCover child 2020-05-14 12:29:29 -05:00
John Colvin ff088142d9 fix custom state output grid 2020-05-14 12:29:29 -05:00
Pedro Pinto 93b7cf346b Added an option for the Cover algo to detach hedges. 2020-05-14 10:20:46 -04:00
Taha Afzal 8ee9b84488 Merge branch 'limit-algo-clean-history' of bitbucket.org:bluetradesys/spark into limit-algo-clean-history 2020-05-13 20:39:13 -04:00
Taha Afzal e3648f5373 Better Logging 2020-05-13 20:39:07 -04:00
John Colvin facd3a9d81 LimitCoverAlgo - remove unnecessary else block in 'do_before_launch' 2020-05-13 17:26:13 -05:00
John Colvin 84969cb481 LimitCoverAlgo - dont update me->child_qty in the child_changed callback, since its async 2020-05-13 17:21:32 -05:00
John Colvin 6d521e77db LimitCoverAlgo - don't override do_launch_attached (it's not supported) 2020-05-13 17:15:30 -05:00
John Colvin 82c49f8f49 remove redundant methods: tick_up/less, payup/less 2020-05-13 17:15:30 -05:00
John Colvin e074309da1 typo broke build (missed constructor in 'TickOffset' -> 'PriceOffset' refactor) 2020-05-13 17:15:30 -05:00
John Colvin 2d622a5f39 LimitCover algo - convert 'ticks_offset' param be in price terms 2020-05-13 16:55:01 -05:00
John Colvin d51d20e84c LimitCover algo - remove cancel_on_blah params, add hedge_ticks 2020-05-13 16:18:46 -05:00
John Colvin 2ccdef9dc5 fix clang build errors 2020-05-13 15:59:45 -05:00
John Colvin 42e62a30bf to_string(OrderError) uses generated protobuf enum Name method 2020-05-13 15:32:27 -05:00
John Colvin c233c607fa Formatter.h - pass strings/templated args by const ref instead of value 2020-05-13 13:40:07 -05:00
John Colvin 462cb930f7 dont redefine ChildTemplate 2020-05-13 13:20:55 -05:00
John Colvin 550a4e8d50 add config toggle to enable/disable LimitCover algo 2020-05-12 19:11:16 -05:00
John Colvin 99b8221efc LimitCoverAlgo unit tests 2020-05-12 19:11:16 -05:00
John Colvin 19e74c30f8 AlgoTestDriverBase - AlgoState << operator 2020-05-12 19:03:53 -05:00
John Colvin 26693a4c98 AlgoTestDriverBase - after_child_changed callback, and find_child method 2020-05-12 19:03:53 -05:00
John Colvin d8bdb69a7c AlgoTestDriverBase - ChildTemplate impl, added OrderDefinition param to child_launching callback 2020-05-12 19:03:53 -05:00
John Colvin 6d357a74e1 AlgoTestDriverBase - expose cancel/cancel_child methods 2020-05-12 19:03:53 -05:00
John Colvin 73f4e4814c LimitCover algo for pure 2020-05-12 19:03:53 -05:00
John Colvin 312bc4be15 add Formatter.h (defines commonly needed << operators for algos, basic string formatting utils) 2020-05-12 18:55:37 -05:00
John Colvin a0c3ba449a add DynamicOrderAdapter (util to keep param proto conversion code out of the algo) 2020-05-12 18:55:37 -05:00
John Colvin d5a2a7f3f0 orderman_utils.h pragma once 2020-05-12 18:55:37 -05:00
John Colvin eda8c94def replicate_to_linux_vm.py - ignore some dirs generated by visual studio 2020-05-12 18:55:37 -05:00
John Colvin 9a8e80e149 add payup/less price calc utils (Security.h)' 2020-05-12 18:55:37 -05:00
John Colvin 6a7e3d739d fix typo 'epislon' -> 'epsilon' 2020-05-12 15:58:00 -05:00
Taha Afzal e6b8274813 DeletAndCoverOrderDefinition does not contain the nested enum anymore 2020-05-12 15:58:00 -05:00
Taha Afzal 9783520c62 DeleteAndCover completed 2020-05-12 15:58:00 -05:00
Taha Afzal 7e7f0bc37f Adding DeleteAndCover proto 2020-05-12 15:58:00 -05:00
99 changed files with 4087 additions and 828 deletions

No files matched your search

+1 -1
View File
@@ -55,7 +55,7 @@ else ()
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0 ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
COMMENT "Creating server runtime layout"
+15 -1
View File
@@ -1,9 +1,22 @@
+++
+++
title = "Release notes"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.16.2
### User Visible Change
- Synthetic creation dialog has a "fractional" checkbox next to the tick settings. Users may uncheck this box to enter integer tick sizes, so the synthetic security's prices will be displayed without decimals, or fractional units.
## 2.16.1
### Bug Fixes
- OrderBook's algo state parameters (in the panel on the right side) are sorted alphabetically
## 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
@@ -715,3 +728,4 @@ insert_anchor_links = "right"
- Start, Day and Total PnL columns are painted with a red or green background color based on if the pnl value is negative or positive respectively
- Corrected cash per point values, used in pnl calculations, for a number of products
- PnL report will now remember any customizations you make to filtering and/or sorting criteria
+6
View File
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "2.1.508",
"rollForward": "latestMajor"
}
}
+4
View File
@@ -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>
@@ -549,24 +549,48 @@
</u:BindingUpdateGroupCollection>
</u:Behavior.BindingUpdateGroups>
</TextBox>
<TextBlock
<StackPanel
x:Name="PART_TickFactors_Label"
Grid.Column="2"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Ticks"
IsEnabled="True"
Orientation="Horizontal"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<c:TickFactorsPicker
>
<TextBlock
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Ticks ("
/>
<CheckBox
Margin="1,0,0,0"
IsChecked="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Padding="0"
>
fractional
</CheckBox>
<TextBlock VerticalAlignment="Center" Margin="1,0,0,0">)</TextBlock>
</StackPanel>
<Grid
x:Name="PART_TickFactors_Picker"
Grid.Column="3"
Margin="{StaticResource Margin.LHeaderPanelItem}"
HorizontalAlignment="Stretch"
IsEnabled="True"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
>
<c:TickFactorsPicker
HorizontalAlignment="Stretch"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static cv:BooleanToVisibilityConverter.HiddenInstance}}"
/>
<c:TickUpDownInt
HorizontalAlignment="Stretch"
Minimum="1"
Value="{Binding IntegerTickSize, RelativeSource={RelativeSource TemplatedParent}}"
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource FalseVisibilityConverter}}"
/>
</Grid>
<TextBlock
x:Name="PART_Pricing_Label"
Grid.Column="4"
@@ -52,6 +52,26 @@ namespace mbts.ui.spark {
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
#endregion
#region UseFractionalTicks
public static readonly DependencyProperty UseFractionalTicksProperty = WPFHelper.create_dp<bool, ExecutionStrategyEditor>(
nameof(UseFractionalTicks), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, true);
public bool UseFractionalTicks {
get => (bool)GetValue(UseFractionalTicksProperty);
set => SetValue(UseFractionalTicksProperty, value);
}
#endregion
#region IntegerTickSize
public static readonly DependencyProperty IntegerTickSizeProperty = WPFHelper.create_dp<int, ExecutionStrategyEditor>(
nameof(IntegerTickSize), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 1);
public int IntegerTickSize {
get => (int)GetValue(IntegerTickSizeProperty);
set => SetValue(IntegerTickSizeProperty, value);
}
#endregion
#region IsNew
public static readonly DependencyProperty IsNewProperty =
DependencyProperty.Register(
@@ -36,6 +36,8 @@
ContractPickerModel="{Binding contract_picker_model}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
UseFractionalTicks="{Binding use_fractional_ticks}"
IntegerTickSize="{Binding integer_tick_size}"
/>
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
</Grid>
@@ -2,7 +2,6 @@
using bts.collections;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
@@ -30,8 +29,16 @@ namespace mbts.spark {
readonly Property<bool> _has_working_orders, _can_add_leg;
Security _synthetic;
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
bool
_set_name_automatically,
_set_ticks_automatically,
_set_quantities_automatically,
_set_price_mults_automatically,
_is_setting_quantities,
_is_setting_price_mults,
_use_fractional_ticks = true;
TickFactors _tick_factors;
int _integer_tick_size = 1;
ExecutionStrategyImmutable _strategy;
HedgePricing _hedge_pricing;
@@ -44,6 +51,10 @@ namespace mbts.spark {
: this(synthetic, strategy, false) {
ExceptionAssert.InvalidOperation.That(synthetic.Id != 0, "This constructor may only be used for already-existing synthetic securities (id must be non-zero).");
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
if (TickUtils.is_integer_tick(synthetic.ContractSpec)) {
_use_fractional_ticks = false;
_integer_tick_size = DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0;
}
title = $"Edit '{synthetic.Synthetic.Name}'";
icon = Icons.ModifySynthetic;
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
@@ -131,14 +142,14 @@ namespace mbts.spark {
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
valid = false;
}
if (_tick_factors == null || !_tick_factors.is_valid()) {
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
if (use_fractional_ticks ? (_tick_factors == null || !_tick_factors.is_valid()) : integer_tick_size == 0) {
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, use_fractional_ticks ? nameof(tick_factors) : nameof(integer_tick_size)).ignore();
valid = false;
}
if (valid) {
_synthetic.Synthetic.Legs.SetItems(leg_tuples.Select(l => l.leg));
_synthetic.Symbol = _deduce_symbol(_synthetic);
_synthetic.ContractSpec = _tick_factors.to_spec();
_synthetic.ContractSpec = use_fractional_ticks ? _tick_factors.to_spec() : TickUtils.create_integer_tick_spec(integer_tick_size);
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
if (id.HasValue && id != 0) {
if (await model.save_strategy(id.Value, get_strategy())) {
@@ -295,6 +306,20 @@ namespace mbts.spark {
public bool has_working_orders => _has_working_orders.Value;
public bool is_new { get; }
public bool is_new_leg_popup_open { get; set; }
public bool use_fractional_ticks {
get => _use_fractional_ticks;
set {
if (Set(ref _use_fractional_ticks, value))
_set_ticks_automatically = false;
}
}
public int integer_tick_size {
get => _integer_tick_size;
set {
if (Set(ref _integer_tick_size, value))
_set_ticks_automatically = false;
}
}
public bool can_add_leg => _can_add_leg.Value;
public SyntheticContract.Types.PriceCalc price_calc { get => _synthetic.Synthetic.PriceCalc; set => _synthetic.Synthetic.PriceCalc = value; }
public ObservableCollection<LegSpreaderParamsTuple> leg_tuples { get; } = new ObservableCollection<LegSpreaderParamsTuple>();
@@ -323,6 +348,16 @@ namespace mbts.spark {
_strategy = strategy;
hedge_pricing = strategy.HedgePricing;
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
if (synthetic.HasContractSpec) {
/// assign the fields <see cref="_use_fractional_ticks"/> and <see cref="_integer_tick_size"/>,
/// rather than the properties <see cref="use_fractional_ticks"/> and <see cref="integer_tick_size"/>
/// because the setters of the former flip <see cref="_set_ticks_automatically"/> to false if the value changes.
/// This behavior causes the ticks to be automatically deduced as legs are added/removed,
/// until the user explicitly enters a different value (as the properties are bound to tick input controls in the view).
Set(ref _use_fractional_ticks, !TickUtils.is_integer_tick(synthetic.ContractSpec), nameof(use_fractional_ticks));
if (!use_fractional_ticks)
Set(ref _integer_tick_size, DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0, nameof(integer_tick_size));
}
RaisePropertyChanged(nameof(name), nameof(tick_factors));
}
@@ -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;
}
@@ -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,56 +191,7 @@ namespace mbts.spark {
security_plurality = p;
RaisePropertyChanged(nameof(security_plurality));
}),
Observable.Create<OrderBookRowVM>(o => {
OrderBookRowVM last_fired = null;
selected_order_changed += on_selected_order_changed;
var sub = _current_row.Subscribe(ch => notify());
return Disposable.Create(dispose);
// local methods
void on_selected_order_changed(OrderRecordImmutable? _) =>
notify();
void notify() {
var cr = current_row_if_distinct_root_selected();
if (!cr.safe_equals(last_fired))
o.OnNext(last_fired = cr);
}
void dispose() {
Disposable.dispose(ref sub);
selected_order_changed -= on_selected_order_changed;
}
})
.SubscribeEach(r => {
if (r == null) {
current_algo_custom_state.Clear();
return Disposable.empty;
}
r.updated += on_updated;
refresh_custom_state();
return Disposable.Create(() => r.updated -= on_updated);
void on_updated((OrderSecurityRootPair oldval, OrderSecurityRootPair newval) _) =>
refresh_custom_state();
void refresh_custom_state() {
current_algo_custom_state.Clear();
var dcs = r.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 child_rows = _row_collection.get_descendent_rows(r.order_key);
if (_row_collection.filter != null) {
child_rows = child_rows.Where(cr => _row_collection.filter(cr.order_tuple));
}
foreach (var dr in child_rows) {
dcs = dr.order.dynamic_custom_state();
if (!dcs.is_null_or_empty())
break;
}
}
foreach (var p in dcs.empty_if_null().OrderBy(kv => kv.Key))
current_algo_custom_state.Add(new NamedValueVM(p.Key, DynamicOrderHelper.fmt_param(p.Value)));
}
}),
_current_row.Subscribe(_ => _custom_state_sync.sync()),
orders_source.OnDispatcher().Subscribe(t => {
if (_selected_rows.Count == 0) {
_cached_orders = t.state;
@@ -257,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);
@@ -402,15 +358,6 @@ namespace mbts.spark {
}
void on_end_attaching() => Disposable.dispose(ref attach_vm_hooks);
// the output grid (custom state) in the right panel
// will only display info for the 'current_row'
// if all selected rows are of the same root order.
OrderBookRowVM current_row_if_distinct_root_selected() =>
current_row != null &&
selected_rows.Select(r => r.order_tuple.root.order.Key).Distinct().TryGetSingle(out uint droot) &&
current_row.order_tuple.root.order.Key == droot ?
current_row : null;
IDisposable connect_order_mod_vm() =>
Disposable.Create(
_selected_params_mon.subscribe_algos().OnDispatcher().Subscribe(order_mod_vm.algo_vm.load_many),
@@ -434,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; }
@@ -534,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 {
@@ -563,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}");
@@ -783,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.empty_if_null().OrderBy(kv => kv.Key))
_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;
}
}
}
}
@@ -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);
}
}
}
}
}
}
+1
View File
@@ -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>
@@ -63,5 +63,20 @@ namespace mbts {
public static double? nan_nullable(double value) =>
double.IsNaN(value) ? (double?)null : value;
public static int? to_optional_int32(double value) {
if (double.IsNaN(value) || double.IsInfinity(value))
return null;
if (value > 0) {
value = Math.Floor(value);
if (value <= int.MaxValue)
return (int)value;
} else if (value < 0) {
value = Math.Ceiling(value);
if (value >= int.MinValue)
return (int)value;
}
return null;
}
}
}
@@ -1005,6 +1005,8 @@
ContractPickerModel="{Binding contract_picker_model}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
UseFractionalTicks="{Binding use_fractional_ticks}"
IntegerTickSize="{Binding integer_tick_size}"
/>
<c:DialogSkirt Grid.Row="1" Margin="-4" VerticalAlignment="Top" RightItems="{Binding skirt_items}"/>
</Grid>
@@ -12,6 +12,9 @@ namespace mbts.ui.converters
public Visibility VisibilityForFalse { get { return _visfalse; } set { _visfalse = value; } }
public static BooleanToVisibilityConverter Instance => new BooleanToVisibilityConverter();
public static BooleanToVisibilityConverter HiddenInstance => new BooleanToVisibilityConverter {
VisibilityForFalse = Visibility.Hidden
};
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
bool flag = false;
+23 -1
View File
@@ -49,6 +49,28 @@ namespace mbts.spark {
public static double parse(this ContractSpecificationImmutable? spec, string text, double display_multiplier, string nan_text) =>
spec.spec_or_default().parse(text, display_multiplier, nan_text);
public static bool is_integer_tick(ContractSpecificationImmutable spec) =>
spec.MainFraction == 0 &&
spec.PriceDisplayFormat == 0 &&
spec.LowTick > 0 &&
MathUtils.is_int(spec.LowTick) &&
spec.HighTick > 0 &&
MathUtils.is_int(spec.HighTick);
public static ContractSpecification create_integer_tick_spec(int low_tick) =>
create_integer_tick_spec(low_tick, low_tick);
public static ContractSpecification create_integer_tick_spec(int low_tick, int high_tick) {
var rv = SecurityDataHelper.Defaults.ContractSpecification;
rv.MainFraction = 0;
rv.SubFraction = 0;
rv.PriceDisplayFormat = 0;
rv.PriceBreak = 0;
rv.LowTick = low_tick;
rv.HighTick = high_tick;
return rv;
}
public static double low_tick(NativePriceSize[] bids, NativePriceSize[] asks) {
double low = bids[bids.Length - 1].price;
if (double.IsNaN(low))
@@ -326,7 +348,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) {
@@ -39,13 +39,18 @@ namespace bts.api {
static string _format(SecurityImmutable? security) {
switch (security?.ContractCase) {
case ContractOneofCase.Future: return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
case ContractOneofCase.Future:
if(security?.HasIce == true)
return _format_ICE(security);
return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
case ContractOneofCase.Spread:
if (security?.HasGlbx == true) {
// In case we ever bring it back, pretty printed spread names can be found here:
// GlbxSecurityFormatter.FormatSpread(security, out string result)
return security?.Glbx.SecurityDesc;
}
else if(security?.HasIce == true)
return _format_ICE(security);
return string.Join("", _format_future_legs(SecurityHelper.get_flat_future_legs(security?.Spread.Legs)));
case ContractOneofCase.Synthetic: return security?.Synthetic.Name;
default: return "Unknown-security";
@@ -79,5 +84,26 @@ namespace bts.api {
default: return $"{fl.quantity:+0;-0}{sym_exp_name}";
}
}
static string _format_ICE (SecurityImmutable? security){
string symbol = security?.Symbol,
strip = security?.Ice.StripName,
md_desc = security?.Ice.MarketDescription,
type_append = "";
if(md_desc.Contains("25k ICE Lots")){
if(md_desc.Contains("TAS"))
type_append = "TAS";
}
else if(md_desc.Contains("ICE Lots")){
if(md_desc.Contains("TAS"))
type_append = "ICE Lots TAS";
else
type_append = "ICE Lots";
}
type_append = string.IsNullOrEmpty(type_append) ?
" " : $" {type_append} ";
return $"{symbol}{type_append}{strip}";
}
}
}
+1 -1
View File
@@ -1 +1 @@
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/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
+23
View File
@@ -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;
+23 -2
View File
@@ -105,14 +105,35 @@ message BasePositionBatch {
google.protobuf.Timestamp current_as_of = 10; // UTC
}
// security overrides /////////////////////////////////////
message CppOverride{
double value = 1;
}
message SecurityParameters{
uint32 security_key = 1;
CppOverride cpp_override = 2;
// other overrides
}
message SecurityParametersBatchOp{
repeated SecurityParameters op_params = 1;
}
message SecurityParametersBatchOpResponse {
bool ok = 1;
string msg = 2;
}
service PositionService {
option (bts.api.service_description) = "Provides Position and PNL, per security, per account";
rpc create_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
rpc update_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
rpc remove_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
rpc set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
rpc set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
rpc update_security_parameters (SecurityParametersBatchOp) returns (SecurityParametersBatchOpResponse);
// subscription
rpc manual_fills(bts.api.VoidMessage) returns (stream ManualFill);
rpc position_data(bts.api.VoidMessage) returns (stream PositionUnit);
+1
View File
@@ -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 {
+5
View File
@@ -59,6 +59,10 @@ message EnsureOnScreenWithWorkAreaRect {
optional bool enabled = 1 [default = false];
}
message CustomFormatIceSecurities{
optional bool enabled = 1 [default = true];
}
message IgnoreNansInDayPnL {
optional bool enabled = 1 [default = true];
}
@@ -68,4 +72,5 @@ message DynamicAlgos{
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];
}
+6 -6
View File
@@ -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
@@ -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
+283
View File
@@ -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>
+3 -1
View File
@@ -170,7 +170,7 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
me->sec = serv.sec_db.resolve(SecurityKey(def.security_key()));
me->previous_working_position = Quantity(0);
auto const &native_state = get_native_state(me->state);
auto &native_state = get_native_state(me->state);
switch (me->state.status()) {
case ORDER_STATUS_WORKING:
@@ -202,6 +202,8 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
}
// if we made it this far we are good.
me->routing_record = rq.routing_record;
*native_state.mutable_routing_record() = rq.routing_record;
if (me->state.status() != ORDER_STATUS_PENDING_WORKING)
me->exchange_order_id = native_state.exchange_order_id();
init_fix();
@@ -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,6 +3,8 @@
#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"
@@ -203,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())
@@ -213,6 +222,8 @@ extern "C" void init_spark_plugin(bts::SparkServices &srv) {
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);
}
}}}
}}}
@@ -1,16 +1,4 @@
// TODO
// put settle time data in the input (X)
// better names (X)
// at end of settle, order should dettach and complete (X)
// order going out logic (X)
// settlement compute logic (X)
// actual publish (X)
// no PTime
// check settle calc (X)
// check order shooting logic
#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"
@@ -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
@@ -71,9 +71,11 @@ struct AlgoTestDriverBase::Impl : public RecursionCounter {
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) {}
@@ -206,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;
}
@@ -237,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;
}
}
@@ -273,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) {
@@ -282,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;
}
@@ -407,7 +427,7 @@ OrderKey AlgoTestDriverBase::launch_child(ChildTemplate&, Price price, Quantity
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) {
@@ -415,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) {
@@ -61,6 +61,8 @@ class AlgoTestDriverBase : private IAlgoServices {
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
@@ -79,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;
@@ -87,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;
@@ -130,8 +134,6 @@ private:
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;
@@ -157,4 +159,17 @@ struct AlgoTestDriver : public AlgoTestDriverBase
const Algo& algo() const { return _algo; }
};
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
+1 -1
View File
@@ -2,7 +2,7 @@
import sys
f = open(sys.argv[1], 'rb+')
license = '5E50A1DACD94D7C1BECD316EEAE5AB02'
license = '1C9210495AA3C9C884B7A37A4C7AD8F9'
data = f.read()
match = data.find(license)
if match > 0:
@@ -0,0 +1,14 @@
<Project>
<PropertyGroup>
<CSharpAPIRoot>$(MSBuildThisFileDirectory)</CSharpAPIRoot>
<Configuration Condition="'$(configuration)'==''">Debug</Configuration>
</PropertyGroup>
<PropertyGroup Label="Output">
<BaseOutputPath>$(CSharpAPIRoot)\build\$(Configuration)</BaseOutputPath>
<OutputPath>$(BaseOutputPath)\bin</OutputPath>
<BaseIntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</IntermediateOutputPath>
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
</PropertyGroup>
</Project>
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30104.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "bts.api.csharp", "src/bts.api.csharp.csproj", "{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FC6CE0CA-B0CB-44E3-8176-1D759D05AAD4}
EndGlobalSection
EndGlobal
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="spark_sdk" Version="2.15.10" />
<PackageReference Include="System.Reactive" Version="4.4.1" />
<PackageReference Include="System.Reactive.Core" Version="4.4.1" />
<PackageReference Include="System.Reactive.Linq" Version="4.4.1" />
</ItemGroup>
</Project>
@@ -0,0 +1,23 @@
using System;
namespace bts.api.csharp {
readonly struct NameIDPair {
public NameIDPair(string name, uint id) {
this.name = name ?? throw new ArgumentNullException(nameof(name));
this.id = id;
}
public string name { get; }
public uint id { get; }
}
static class Users {
public static NameIDPair Default =>
new NameIDPair("bts", 1);
}
static class Accounts {
public static NameIDPair Default =>
new NameIDPair("TEST", 1);
}
}
@@ -0,0 +1,74 @@
using bts.api.core;
using bts.api.csharp.examples;
using bts.api.orderman;
using bts.api.position;
using CommandLine;
using System;
using System.Collections.Generic;
using System.Threading;
namespace bts.api.csharp {
class Program {
static void Main(string[] args) =>
Parser.Default.ParseArguments<Options>(args).WithParsed(_run);
static void _run(Options options) {
var exit_event = new ManualResetEvent(false);
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
var service_context = new ServiceContext();
var login = service_context.login("bts", "");
login.Wait();
var examples = new List<IDisposable>();
if (options.subscribe_securities ||! options.any_enabled) {
examples.Add(new SecurityExample(service_context.locate<MarketDataService>()));
}
if (options.subscribe_positions || !options.any_enabled) {
examples.Add(new PositionExample(service_context.locate<PositionService>()));
}
if (options.subscribe_orders || !options.any_enabled) {
examples.Add(new OrderExample(service_context.locate<OrderService>()));
}
if (options.subscribe_book || !options.any_enabled) {
examples.Add(new BookExample(service_context.locate<MarketDataService>()));
}
if (options.send_orders || !options.any_enabled) {
examples.Add(SendOrderLoop.init_loop(service_context.locate<OrderService>(), service_context.locate<MarketDataService>()));
}
Console.CancelKeyPress += on_cancel_key_press;
exit_event.WaitOne();
foreach (var ex in examples)
ex.Dispose();
//
void on_cancel_key_press(object sender, ConsoleCancelEventArgs e) {
exit_event.Set();
e.Cancel = true;
}
}
class Options {
[Option("subscribe-securities", Required = false, HelpText = "run security-subscription example.")]
public bool subscribe_securities { get; set; }
[Option("subscribe-orders", Required = false, HelpText = "run order-subscription example.")]
public bool subscribe_orders { get; set; }
[Option("subscribe-positions", Required = false, HelpText = "run position-subscription example.")]
public bool subscribe_positions { get; set; }
[Option("subscribe-books", Required = false, HelpText = "run book-subscription example.")]
public bool subscribe_book { get; set; }
[Option("send-orders", Required = false, HelpText = "run send-orders example.")]
public bool send_orders { get; set; }
// if none are enabled, all options will be flipped on,
// so the user need not specify a single argument to see the examples run.
public bool any_enabled =>
subscribe_securities ||
subscribe_orders ||
subscribe_positions ||
subscribe_book ||
send_orders;
}
}
}
@@ -0,0 +1,60 @@
using bts.api.core;
using bts.api.csharp.utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using SecurityKey = System.UInt32;
namespace bts.api.csharp.examples {
/// <summary>
/// Uses the <see cref="MarketDataService"/> api to observe book updates
/// for security in the system, and organize them into a Dictionary.
/// </summary>
class BookExample : IDisposable {
readonly Dictionary<SecurityKey, BookEntryImmutable> _books = new Dictionary<SecurityKey, BookEntryImmutable>();
readonly IDisposable _subscription;
public BookExample(MarketDataService market_data_service) {
_subscription = market_data_service.subscribei_book()
.ObserveOn(SynchronizationContext.Current)
.Subscribe(_on_book_update);
}
public void Dispose() {
_subscription.Dispose();
}
void _on_book_update(Update<BookEntryImmutable, uint> update) {
foreach (var new_book in update.added) {
// of course, we shouldn't have entries for any books in update.added yet
Debug.Assert(!_books.ContainsKey(new_book.Key));
// display bbo in the console window
_print_book_info(new_book.Key, $"book received for the first time, bbo is: {BookUtils.fmt_bbo(new_book)}");
// add the new book to our map
_books.Add(new_book.Key, new_book);
}
foreach (var change in update.changes) {
// of course, the security key shouldn't change
Debug.Assert(change.Old.Key == change.New.Key);
// our book entry for this security should be identitical to change.Old
Debug.Assert(change.Old.Bid.SequenceEqual(_books[change.Old.Key].Bid));
Debug.Assert(change.Old.Ask.SequenceEqual(_books[change.Old.Key].Ask));
// display the current book in the console window
_print_book_info(change.New.Key, $"book changed, now bbo is: {BookUtils.fmt_bbo(change.New)}");
// update our map with the changed book
_books[change.New.Key] = change.New;
}
foreach (var old_book in update.removed) {
// of course, we should have entries for all books in update.removed
Debug.Assert(_books.Remove(old_book.Key));
}
}
static void _print_book_info(SecurityKey key, string message) {
Console.WriteLine($"Security {key}: {message}.");
}
}
}
@@ -0,0 +1,139 @@
using bts.api.core;
using bts.api.orderman;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using AccountKey = System.UInt32;
using OrderKey = System.UInt32;
namespace bts.api.csharp.examples {
/// <summary>
/// Uses the <see cref="OrderService"/> to observe order updates,
/// and group them by account.
/// </summary>
class OrderExample : IDisposable {
readonly Dictionary<AccountKey, Dictionary<OrderKey, OrderRecordImmutable>> _data = new Dictionary<AccountKey, Dictionary<AccountKey, OrderRecordImmutable>>();
IDisposable _subscription;
public OrderExample(OrderService order_service) {
/// 'OrderService' has 2 methods which can be used to subscribe to order updates.
/// -------------------------------------------------------------------------------------
/// 1) <see cref="OrderService.subscribe_orders(string)"/>.
/// 2) <see cref="OrderService.subscribei_orders(string)"/>.
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
/// --------------------------------------------------------------------------------------
_subscription = order_service.subscribei_orders()
// the 'ObserveOn' call below ensures that the
// callback will be invoked this thread.
// Be sure to do this if you don't want to worry
// about multi-threading issues (locking, etc).
.ObserveOn(SynchronizationContext.Current)
.Subscribe(_on_update);
}
public event Action<AccountKey> account_orders_changed;
public void Dispose() {
_subscription.Dispose();
}
public IEnumerable<OrderRecordImmutable> get_account_orders(AccountKey account) {
if (_data.TryGetValue(account, out var account_orders)) {
return account_orders.Values;
}
return Enumerable.Empty<OrderRecordImmutable>();
}
void _on_order_added(OrderRecordImmutable new_order) {
if (!_data.TryGetValue(new_order.Def.AccountId, out var account_orders)) {
// this is the first order for this account.
// initialize the account orders dictionary, and add it to our data.
account_orders = new Dictionary<AccountKey, OrderRecordImmutable>();
_data.Add(new_order.Def.AccountId, account_orders);
}
// now add the order to account_orders
account_orders.Add(new_order.Key, new_order);
}
void _on_order_changed(Change<OrderRecordImmutable> change) {
// ch.New and ch.Old have the same key.
Debug.Assert(change.New.Key == change.Old.Key);
var key = change.New.Key;
// react to a changed order
if (change.New.State.FilledQuantity != change.Old.State.FilledQuantity) {
// Display new fills in the console window.
var num_new_fills = change.New.State.FilledQuantity - change.Old.State.FilledQuantity;
_print_order_info(key, $"received {num_new_fills} new fills.");
// Be mindful that orders can be overfilled.
if (change.New.State.FilledQuantity > change.New.State.Quantity) {
// Display overfills in the console window.
var num_over_fills = change.New.State.FilledQuantity - change.New.State.Quantity;
_print_order_info(key, $"overfilled by {num_over_fills}");
} else {
// Display working qty in the console window.
var working_qty = change.New.State.Quantity - change.New.State.FilledQuantity;
_print_order_info(key, $"still working {working_qty}.");
}
}
// Display order status changes in the console window.
if (change.New.State.Status != change.Old.State.Status) {
_print_order_info(key, $"status changed from {change.Old.State.Status} to {change.New.State.Status}.");
}
// Account will not change, so our dictionary will definitely have a key for this account.
Debug.Assert(_data.ContainsKey(change.New.Def.AccountId));
var account_orders = _data[change.New.Def.AccountId];
// The value for this key in account_orders should equal ch.Old.
Debug.Assert(account_orders[key].Equals(change.Old));
// update account orders with the changed order.
account_orders[key] = change.New;
}
void _on_order_removed(OrderRecordImmutable old_val) {
// An entry for this order should be present in our data,
// as it is in the 'removed' items.
// Assert this is the case to illustrate the point.
Debug.Assert(_data.ContainsKey(old_val.Def.AccountId));
var account_orders = _data[old_val.Def.AccountId];
// the removed value should match the one currently in our data.
// Assert this is the case, then remove the order.
Debug.Assert(account_orders[old_val.Key].Equals(old_val));
account_orders.Remove(old_val.Key);
_print_order_info(old_val.Key, "has been removed.");
}
void _on_update(Update<OrderRecordImmutable, OrderKey> update) {
// record which accounts experienced an order update, then
// at the end of this method, fire the account_orders_changed event for each
var accounts_with_order_updates = new HashSet<AccountKey>();
foreach (OrderRecordImmutable new_order in update.added) {
_on_order_added(new_order);
accounts_with_order_updates.Add(new_order.Def.AccountId);
}
foreach (Change<OrderRecordImmutable> change in update.changes) {
_on_order_changed(change);
accounts_with_order_updates.Add(change.New.Def.AccountId);
}
foreach (OrderRecordImmutable old_order in update.removed) {
_on_order_removed(old_order);
accounts_with_order_updates.Add(old_order.Def.AccountId);
}
// display all accounts with order updates in the Console window.
Console.WriteLine("The following accounts experienced order changes: ");
foreach (var account in accounts_with_order_updates) {
Console.WriteLine("\n\t" + account);
}
// lastly, fire the account_orders_changed event for each account experienced an order update.
foreach (var account in accounts_with_order_updates) {
account_orders_changed?.Invoke(account);
}
}
void _print_order_info(OrderKey order_key, string message) {
Console.WriteLine($"Order {order_key}: {message}.");
}
}
}
@@ -0,0 +1,125 @@
using bts.api.core;
using bts.api.position;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reactive.Linq;
using System.Threading;
namespace bts.api.csharp.examples {
/// <summary>
/// A pair of security, and account id,
/// which uniquely identifies each PositionUnit.
/// </summary>
readonly struct PositionKey {
public PositionKey(uint security_id, uint account_id) {
this.security_id = security_id;
this.account_id = account_id;
}
public uint security_id { get; }
public uint account_id { get; }
public static PositionKey from_position(PositionUnitImmutable p) =>
new PositionKey(p.SecurityId, p.AccountId);
public override string ToString() =>
$"Sec {security_id}, Acc {account_id}";
}
/// <summary>
/// Uses the <see cref="PositionService"/> api to monitor positions
/// for every account/security pair, and organize them into a Dictionary.
/// </summary>
class PositionExample : IDisposable {
readonly Dictionary<PositionKey, PositionUnitImmutable> _data = new Dictionary<PositionKey, PositionUnitImmutable>();
IDisposable _subscription;
public PositionExample(PositionService position_service) {
/// 'PositionService' has 2 methods which can be used to subscribe to position updates.
/// -------------------------------------------------------------------------------------
/// 1) <see cref="PositionService.subscribe_position_data(string)"/>.
/// 2) <see cref="PositionService.subscribei_position_data(string)"/>.
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
/// --------------------------------------------------------------------------------------
_subscription = position_service.subscribei_position_data()
// the 'ObserveOn' call below ensures that the
// callback will be invoked this thread.
// Be sure to do this if you don't want to worry
// about multi-threading issues (locking, etc).
.ObserveOn(SynchronizationContext.Current)
.Subscribe(_on_update);
}
public event Action data_changed;
public IReadOnlyDictionary<PositionKey, PositionUnitImmutable> data => _data;
public void Dispose() =>
_subscription.Dispose();
void _on_position_added(PositionUnitImmutable new_val) {
var k = PositionKey.from_position(new_val);
// this entry should NOT be present in our _data, as it is in the 'added' items.
// Assert this is the case to illustrate the point.
Debug.Assert(!_data.ContainsKey(k));
_print_position_info(k, $"initial position received. Day: Long {new_val.DayLongPos}, Short {new_val.DayShortPos}, Net {new_val.DayLongPos - new_val.DayShortPos}.");
// add the new position unit to _data
_data.Add(k, new_val);
}
void _on_position_changed(Change<PositionUnitImmutable> change) {
var k = PositionKey.from_position(change.New);
// ch.New and ch.Old have the same key (i.e., the Account/Security pair),
Debug.Assert(change.New.AccountId == change.Old.AccountId);
Debug.Assert(change.New.SecurityId == change.Old.SecurityId);
// of course, the value for this key in our dictionary should equal ch.Old.
Debug.Assert(_data[k].Equals(change.Old));
// Display changes to the day position in the console window.
// 1) check if the day long changed
if (change.New.DayLongPos != change.Old.DayLongPos) {
Console.WriteLine($"[{k}] day long changed from {change.Old.DayLongPos} to {change.New.DayLongPos}.");
}
// 2) check if day short changed
if (change.New.DayShortPos != change.Old.DayShortPos) {
Console.WriteLine($"[{k}] day short changed from {change.Old.DayShortPos} to {change.New.DayShortPos}.");
}
// 3) check if net day position changed
var new_net_day_pos = change.New.DayLongPos - change.New.DayShortPos;
var old_net_day_pos = change.Old.DayLongPos - change.Old.DayShortPos;
if (new_net_day_pos != old_net_day_pos) {
Console.WriteLine($"[{k}] day net changed from {old_net_day_pos} to {new_net_day_pos}.");
}
// update _data with the changed position.
_data[k] = change.New;
}
void _on_position_removed(PositionUnitImmutable old_val) {
var k = PositionKey.from_position(old_val);
// this entry should be present in our _data, as it is in the 'removed' items.
// Assert this is the case to illustrate the point.
Debug.Assert(_data.ContainsKey(k));
// this should match our data. Assert this is the case.
Debug.Assert(_data[k].Equals(old_val));
_data.Remove(k);
Console.WriteLine($"[{k}] position has been removed.");
}
void _on_update(Update<PositionUnitImmutable, int> update) {
foreach (PositionUnitImmutable p in update.added) {
_on_position_added(p);
}
foreach (Change<PositionUnitImmutable> ch in update.changes) {
_on_position_changed(ch);
}
foreach (PositionUnitImmutable p in update.removed) {
_on_position_removed(p);
}
data_changed?.Invoke();
}
void _print_position_info(PositionKey position_key, string message) {
Console.WriteLine($"Position {position_key}: {message}.");
}
}
}
@@ -0,0 +1,48 @@
using bts.api.core;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using SecurityKey = System.UInt32;
namespace bts.api.csharp.examples {
/// <summary>
/// Uses the <see cref="MarketDataService"/> api to observe the
/// state of securities in the system.
/// </summary>
class SecurityExample : IDisposable {
readonly IDisposable _subscription;
readonly Dictionary<SecurityKey, SecurityImmutable> _securities = new Dictionary<SecurityKey, SecurityImmutable>();
public SecurityExample(MarketDataService market_data_service) {
_subscription = market_data_service.subscribei_securities()
.ObserveOn(SynchronizationContext.Current)
.Subscribe(_on_securities_update);
}
public void Dispose() {
_subscription.Dispose();
}
void _on_securities_update(Update<SecurityImmutable, uint> update) {
foreach (var new_security in update.added) {
// of course, we shouldn't have entries for any books in update.added yet
Debug.Assert(!_securities.ContainsKey(new_security.Id));
// display bbo in the console window
Console.WriteLine($"received new security: {new_security}.");
// add the new security to our map
_securities.Add(new_security.Id, new_security);
}
// As you may have noticed, all of the subcribe methods in the api
// return an IObservable<Update<T>>. It is worth noting that for security updates,
// 'changes' will always be empty.
Debug.Assert(!update.changes.Any());
foreach (var old_security in update.removed) {
// of course, we should have entries for all books in update.removed
Debug.Assert(_securities.Remove(old_security.Id));
}
}
}
}
@@ -0,0 +1,110 @@
using bts.api.csharp.utils;
using bts.api.orderman;
using System;
using System.Threading.Tasks;
namespace bts.api.csharp.examples {
static class OrderTemplates {
public static CustomOrderDefinition limit() => new CustomOrderDefinition { Limit = new LimitOrderDefinition() };
public static CustomOrderDefinition market() => new CustomOrderDefinition { Market = new MarketOrderDefinition() };
public static CustomOrderDefinition iceberg(double show_quantity) => iceberg(show_quantity, limit());
public static CustomOrderDefinition iceberg(double show_quantity, CustomOrderDefinition child) =>
new CustomOrderDefinition {
Iceberg = new IcebergOrderDefinition {
ShowQuantity = show_quantity,
Child = child
}
};
}
/// <summary>
/// Exemplifies how the <see cref="OrderService"/> api may be used to
/// send several common order types (limit, market, and iceberg).
/// </summary>
class SendOrderModel {
readonly OrderService _service;
public SendOrderModel(OrderService service) {
_service = service;
}
public OrderDefinition create_order_definition(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_def) {
return new OrderDefinition {
Account = account.name,
AccountId = account.id,
User = user.name,
UserId = user.id,
SecurityKey = security_key,
CustomDef = custom_def
};
}
/// <summary>
/// Sends a limit order (asynchronous).
/// </summary>
/// <returns>The order id if the submission was successful, otherwise null.</returns>
public Task<uint?> send_limit_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity, double price) {
return send_order_async(user, account, security_key, OrderTemplates.limit(), quantity, price);
}
/// <summary>
/// Sends a market order (asynchronous). Note no price parameter is required for market orders.
/// </summary>
/// <returns>The order id if the submission was successful, otherwise null.</returns>
public Task<uint?> send_market_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity) {
return send_order_async(user, account, security_key, OrderTemplates.market(), quantity, 0.0);
}
/// <summary>
/// Sends an iceberg of limit orders (asynchronous).
/// </summary>
/// <returns>The order id if the submission was successful, otherwise null.</returns>
public Task<uint?> send_iceberg_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity, double price, double show_quantity) {
return send_order_async(user, account, security_key, OrderTemplates.iceberg(show_quantity), quantity, price);
}
/// <summary>
/// Sends an order of the type specified by <paramref name="custom_order_definition"/> (asynchronous).
/// </summary>
/// <returns>The order id if the submission was successful, otherwise null.</returns>
public async Task<uint?> send_order_async(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_order_definition, uint quantity, double price) {
Console.WriteLine($"submitting order for user ({user}), account ({account}): {OrderUtils.fmt_order(quantity, price, custom_order_definition)}.");
var reply = await _service.create_order_async(new Create {
Quantity = quantity,
Price = price,
NewOrder = create_order_definition(user, account, security_key, custom_order_definition)
});
if (reply.success) {
Console.WriteLine($"received ack that order has been submitted. Order id is {reply.value.OrderKey}.");
return reply.value.OrderKey;
} else {
Console.WriteLine($"order submission was unsuccessful: [{reply.value.ErrorCode}] {reply.value.Msg}.");
return null;
}
}
public void send_order(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_order_definition, uint quantity, double price) {
/// Note, the thread will be blocked until a response is received from the server.
/// For this reason, <see cref="OrderService.create_order_async(Create)"/> is usually preferred.
/// An example of how to use that method is provided in <see cref="send_order_async(NameIDPair, NameIDPair, CustomOrderDefinition, uint, double)"/>.
Console.WriteLine($"Creating order {OrderUtils.fmt_order(quantity, price, custom_order_definition)}");
var response = _service.create_order(new Create {
Quantity = quantity,
Price = price,
NewOrder = create_order_definition(user, account, security_key, custom_order_definition)
});
switch (response.ErrorCode) {
case OrderError.Ok:
_print_order_info(response.OrderKey, "successfully created.");
break;
default:
Console.WriteLine($"Failed to create order: [{response.ErrorCode}] {response.Msg}.");
break;
}
}
static void _print_order_info(uint order_key, string message) {
Console.WriteLine($"Order {order_key}: {message}.");
}
}
}
@@ -0,0 +1,100 @@
using bts.api.core;
using bts.api.orderman;
using mbts;
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace bts.api.csharp.examples {
/// <summary>
/// The code in this example is a bit more advanced than the others,
/// as it is designed to send orders, asynchronously, in a continuous loop.
/// For straightforward examples of how to send orders with the api,
/// check out <see cref="SendOrderModel"/>.
/// </summary>
static class SendOrderLoop {
/// <summary>
/// As a security key is required to send an order, this method
/// uses the <see cref="MarketDataService"/> to observe the securities stream,
/// then on the first update, initializes the send-order-loop <see cref="LoopImpl"/>
/// with one of the newly received security keys.
/// </summary>
public static IDisposable init_loop(OrderService order_service, MarketDataService market_data_service) {
// If you need to familiarize yourself with the IObservable pattern, check out the official docs:
// https://docs.microsoft.com/en-us/dotnet/api/system.iobservable-1?view=netcore-3.1
var single_security_stream = market_data_service.subscribei_securities()
.Where(u => u.all.Any())
.Take(1)
.Select(u => u.all.FirstOrDefault());
return init_loop(single_security_stream, create_loop);
IDisposable create_loop(SecurityImmutable sec) {
return new LoopImpl(order_service, sec.Id);
}
}
/// <summary>
/// Observes the security stream and creates a new send-order-loop
/// whenever a new security arrives. The previous send-order-loop
/// is disposed when the next security arrives, so only one send-order-loop
/// shall be active at a time.
/// </summary>
/// <param name="security">The security stream used to initialize send-order-loops.</param>
/// <returns>An IDisposable that unsubsribes from the securities stream, and disposes the active send-order-loop.</returns>
public static IDisposable init_loop(IObservable<SecurityImmutable> security, Func<SecurityImmutable, IDisposable> factory) {
IDisposable active_loop = null;
var security_sub = security.Subscribe(s =>
Interlocked.Exchange(ref active_loop, factory(s))?.Dispose());
return Disposable.Create(dispose);
void dispose() {
security_sub.Dispose();
active_loop?.Dispose();
}
}
class LoopImpl : IDisposable {
bool _is_disposed;
public LoopImpl(OrderService service, uint security_key)
: this(service, Users.Default, Accounts.Default, security_key, 10, TimeSpan.FromSeconds(3)) { }
public LoopImpl(OrderService service, NameIDPair user, NameIDPair account, uint security_key, int max_orders, TimeSpan delay_between_orders) {
var model = new SendOrderModel(service);
send_orders_continuously();
async void send_orders_continuously() {
// Periodically send an order with one of the CustomOrderDefinitions below.
// If you are unfamiliar with async/await, this will also serve as a simple example
// of how those keywords may be used. However, there is no substite for the official docs:
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
var order_templates = new CustomOrderDefinitionImmutable[] {
new CustomOrderDefinitionImmutable(OrderTemplates.limit()),
new CustomOrderDefinitionImmutable(OrderTemplates.market()),
new CustomOrderDefinitionImmutable(OrderTemplates.iceberg(5))
};
for (int num_orders_sent = 0; num_orders_sent < max_orders && !_is_disposed; num_orders_sent++) {
int template_index = num_orders_sent % order_templates.Length;
CustomOrderDefinitionImmutable templatei = order_templates[template_index];
// The order templates we created are immutable, however the api uses the mutable form.
// Not to worry tho, there is a nifty method called 'CloneToMutable.'
// Note that all of the non-primitive immutable parameters in the api methods have a 'CloneToMutable' method,
// it is not unique to CustomOrderDefinitionImmutable.
CustomOrderDefinition template = templatei.CloneToMutable();
await model.send_order_async(user, account, security_key, template, 12, 100);
// pause for a moment before sending the next order
await Task.Delay(delay_between_orders);
}
}
}
public void Dispose() {
// flip _is_disposed to true, so the 'send_orders_continuously' loop
// knows it's time to stop.
_is_disposed = true;
}
}
}
}
@@ -0,0 +1,52 @@
using System.Collections.Generic;
namespace bts.api.csharp.utils {
static class BookUtils {
/// <summary>
/// Get a display string of the bbo.
/// </summary>
public static string fmt_bbo(BookEntryImmutable book) {
return fmt_bbo(get_bbo(book));
}
/// <summary>
/// Get a display string of the bbo.
/// </summary>
public static string fmt_bbo((PriceEntryImmutable? bid, PriceEntryImmutable? ask) bbo) {
return $"{fmt_price_entry(bbo.bid)} <--> {fmt_price_entry(bbo.ask)}";
}
/// <summary>
/// Get a display string of a <see cref="PriceEntryImmutable"/>.
/// </summary>
public static string fmt_price_entry(PriceEntryImmutable? price_entry) {
return price_entry.HasValue ? fmt_price_entry(price_entry.Value) : "N/A";
}
/// <summary>
/// Get a display string of a <see cref="PriceEntryImmutable"/>.
/// </summary>
public static string fmt_price_entry(PriceEntryImmutable price_entry) {
return $"{price_entry}";
}
/// <summary>
/// Get a display string of the bbo.
/// </summary>
public static (PriceEntryImmutable? best_bid, PriceEntryImmutable? best_ask) get_bbo(BookEntryImmutable book) {
return (get_top_of_book(book.Bid), get_top_of_book(book.Ask));
}
/// <summary>
/// Returns the first element in <paramref name="depth"/>,
/// as this represents the top price level
/// of a particular side of the market (bid or ask).
/// </summary>
public static PriceEntryImmutable? get_top_of_book(IEnumerable<PriceEntryImmutable> depth) {
foreach (var d in depth)
return d;
// there are no elements (the book is empty for this side), so return null.
return null;
}
}
}
@@ -0,0 +1,9 @@
using bts.api.orderman;
namespace bts.api.csharp.utils {
static class OrderUtils {
public static string fmt_order(uint quantity, double price, CustomOrderDefinition custom_order_definition) {
return $"{quantity} @ {price}: {custom_order_definition}";
}
}
}
@@ -0,0 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace bts.api.csharp.utils {
static class SecurityUtils {
}
}
+1 -1
View File
@@ -1 +1 @@
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/FIXSim.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/ParseHistoricalFIX.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/TestCeloxica.cpp;cmds/Web.cpp;dspark/mirage/quick_queue/quick_queue.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/MultipartFormDecoder.cpp;http/WebServer.cpp;http/tests/MultiPartFormTests.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.cpp;md/platforms/celoxica/CXListener.cpp;md/platforms/celoxica/CXMain.cpp;md/platforms/celoxica/tests/cx_tests.cpp;md/platforms/ice_onixs/ICEListener.cpp;md/platforms/ice_onixs/ICEMain.cpp;md/platforms/ice_onixs/tests/ice_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;position/IPositionObserver.cpp;position/MarginServiceCore.cpp;position/MarginServiceServer.cpp;position/PositionAggregator.cpp;position/PositionMap.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/import/BasePositionServiceServer.cpp;position/import/DownloadCommonStateMachine.cpp;position/import/DownloadWindow.cpp;position/import/MoneyFileServiceServer.cpp;position/import/parsing/gmi_parser.cpp;position/import/parsing/money_parser.cpp;position/import/parsing/parser_common.cpp;position/tests/PositionTests.cpp;position/tests/position_tests_internal.cpp;subprocess/SubProcessRunner.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/FIXSim.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/ParseHistoricalFIX.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/Web.cpp;dspark/mirage/quick_queue/quick_queue.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/MultipartFormDecoder.cpp;http/WebServer.cpp;http/tests/MultiPartFormTests.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.cpp;md/platforms/ice_onixs/ICEListener.cpp;md/platforms/ice_onixs/ICEMain.cpp;md/platforms/ice_onixs/tests/ice_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;position/IPositionObserver.cpp;position/MarginServiceCore.cpp;position/MarginServiceServer.cpp;position/PositionAggregator.cpp;position/PositionMap.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/import/BasePositionServiceServer.cpp;position/import/DownloadCommonStateMachine.cpp;position/import/DownloadWindow.cpp;position/import/MoneyFileServiceServer.cpp;position/import/parsing/gmi_parser.cpp;position/import/parsing/money_parser.cpp;position/import/parsing/parser_common.cpp;position/tests/PositionTests.cpp;position/tests/position_tests_internal.cpp;subprocess/SubProcessRunner.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
-26
View File
@@ -1,26 +0,0 @@
#include "TestCeloxica.h"
#include "server/sparkd/md/platforms/celoxica/CXMain.h"
#include "server/sparkd/md/platforms/celoxica/CXListener.h"
namespace bts
{
namespace cmd
{
int TestCeloxica::execute(const std::vector<std::string>&)
{
bts::md::CXMain main;
main.initialize();
return 0;
}
std::string TestCeloxica::help()
{
return "\t test BTS <> Celoxica integration \n";
}
}
}
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#include "ICmd.h"
namespace bts
{
namespace cmd
{
class TestCeloxica : public ICmd
{
public:
int execute(std::vector<std::string> const &args);
std::string help();
};
}
}
-2
View File
@@ -19,7 +19,6 @@
#include "server/sparkd/cmds/RunSim.h"
#include "server/sparkd/cmds/Span.h"
#include "server/sparkd/cmds/Test.h"
#include "server/sparkd/cmds/TestCeloxica.h"
#include "server/sparkd/cmds/Web.h"
using namespace bts;
@@ -32,7 +31,6 @@ int main(int argc, char **argv) {
REGISTER(Help);
REGISTER(Run);
REGISTER(Md);
REGISTER(TestCeloxica);
REGISTER(ConfigCheck);
REGISTER(KeyCode);
REGISTER(RunSim);
@@ -1,88 +0,0 @@
#include "CXListener.h"
#include "lib/utils/defer.h"
#include "lib/utils/CallTimer.h"
#include "lib/utils/helper_macros.h"
#include <tuple>
namespace celoxica { namespace gmac {} }
#include "celoxica.h"
using namespace celoxica::gmac;
using namespace std;
namespace bts
{
namespace md
{
CXListener::CXListener(CXSecurityIndex& index, MDProducer& producer, SecurityIDRecorder& recorder,
co2::SettlementRecorder& settles, std::vector<MDProducerChannel> channels,
utils::Dispatcher& disp) :
_index(index),
_producer(producer),
_packet_arrived(),
_latest_sequence(0),
_security_recorder(recorder),
_settlement_recorder(settles),
_symbol_filter_storage(),
_channels(channels),
_background(disp),
_security_recorder_input_timer(disp, 900, [&]() { dequeue_securities(); }),
_security_recorder_input(),
_concurrency_monitor(0),
_book_updates()
{
_book_updates.reserve(32);
_security_recorder_input_timer.start();
}
CXListener::~CXListener()
{
_security_recorder_input_timer.stop();
}
void CXListener::add_software_filter(const std::string& symbol)
{
_symbol_filter_storage.push_back(symbol);
//_symbol_filter.clear();
//for (const auto& str : _symbol_filter_storage) {
// _symbol_filter.insert(onixs::StringRef(str));
//}
}
void CXListener::on_GMACMessageOrderDepthV3(const GMACMessageOrderDepthV3*)
{
}
void CXListener::dequeue_securities()
{
}
void CXListener::flush_books()
{
// This is called at the end of the packet
// Write all book updates to shared memory and reset for the next packet
//for (auto& entry : _book_updates) {
// // bbo first
// uint16_t sequence = (_latest_sequence & 0xFFFF);
// entry.handle.update_bbo(_packet_arrived, entry.bids[0], entry.asks[0], sequence);
// // Perf Note: the remainder of the book update could be done (carefully) with a memcpy
// for (size_t x = 1; x < COCONUT_MAX_DEPTH; ++x) {
// entry.handle.update_bid(_packet_arrived, entry.bids[x], x);
// entry.handle.update_ask(_packet_arrived, entry.asks[x], x);
// }
//}
//// Reset pending updates
//_book_updates.clear();
}
}
md::CXListener::BookData::BookData(MDProducerHandle & handle) : handle(handle), bids(), asks()
{
}
}//namespace
@@ -1,61 +0,0 @@
#pragma once
#include "lib/utils/bts_no_copy.h"
#include "lib/md/coconut2.h"
#include "lib/utils/Dispatcher.h"
#include "server/sparkd/md/platforms/celoxica/CXSecurityIndex.h"
#include <tbb/concurrent_queue.h>
#include <vector>
#include <atomic>
namespace bts
{
namespace md
{
struct GMACMessageOrderDepthV3;
class CXListener
{
public:
CXListener(CXSecurityIndex&, MDProducer&, SecurityIDRecorder&,
co2::SettlementRecorder&, std::vector<MDProducerChannel>, utils::Dispatcher&);
~CXListener();
void add_software_filter(const std::string& symbol);
void on_GMACMessageOrderDepthV3(const GMACMessageOrderDepthV3*);
private:
void flush_books();
void dequeue_securities();
private:
BTS_NO_COPY(CXListener);
CXSecurityIndex& _index;
MDProducer& _producer;
utils::TimeStamp _packet_arrived;
uint64_t _latest_sequence;
SecurityIDRecorder& _security_recorder;
co2::SettlementRecorder& _settlement_recorder;
std::vector<std::string> _symbol_filter_storage;
std::vector<MDProducerChannel> _channels;
utils::Dispatcher& _background;
utils::DispatcherTimer _security_recorder_input_timer;
tbb::concurrent_queue<CXSecurityData*> _security_recorder_input;
std::atomic<int> _concurrency_monitor;
struct BookData {
BookData(MDProducerHandle& handle);
MDProducerHandle handle;
PriceSize bids[10];
PriceSize asks[10];
};
std::vector<BookData> _book_updates;
};
}}//namespace
@@ -1,83 +0,0 @@
#include "CXMain.h"
#include "lib/md/cme/MDP3Utils.h"
#include "lib/config/config.h"
#include "lib/md/coconut2.h"
#include "lib/os/os.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/proto_helper.h"
#include "lib/utils/helper_macros.h"
#include "lib/config/protos/MarketData.pb.h"
#include "lib/config/protos/system.pb.h"
namespace celoxica { namespace gmac {} }
#include "celoxica.h"
extern "C"
{
#define __retcode_start__
#define __retcode_end__
}
#include <memory>
#include <set>
using namespace std;
using namespace celoxica::gmac;
namespace bts
{
namespace md
{
void run_cx_server(UserSymbol* psyms, size_t symcount)
{
CXMain main;
vector<UserSymbol> symbols;
for(size_t x = 0; x < symcount; ++x) {
symbols.push_back(psyms[x]);
}
main.run(symbols);
}
CXMain::CXMain() :
_producer(co2::PLATFORM_CELOXICA),
_security_recorder({}),
_settlement_recorder(co2::PLATFORM_CELOXICA),
_listeners()
{
}
CXMain::~CXMain() {}
void CXMain::initialize()
{
//celoxica::gmac::GMACIdentify(stderr);
//const auto& options = config::get<CMEOptions>();
}
void CXMain::run(std::vector<UserSymbol>& symbols)
{
bts::md::await_cme_config();
initialize();
parse_channels(symbols);
// const auto& channels = parse_channels(symbols);
}
std::unique_ptr<CXListener> CXMain::prepare_listener(const std::string& channel, const CMEChannel&)
{
UNUSED(channel);
return std::unique_ptr<CXListener>();
}
}
}
@@ -1,49 +0,0 @@
#pragma once
#include "lib/utils/bts_no_copy.h"
#include "lib/md/coconut2.h"
#include "server/sparkd/md/platforms/celoxica/CXListener.h"
#include "server/sparkd/md/platforms/celoxica/CXSecurityIndex.h"
#include <memory>
#include <vector>
#include <map>
namespace bts
{
namespace md
{
void run_cx_server(UserSymbol* sym, size_t symcount);
struct CMEChannel;
class CXListener;
/*
* The CX provider is very simple. 1 CXMain maps to 1 Celoxica Queue, which maps to 1 thread and one bound CPU.
* Control starts in CXMain which starts the thread, binds to a cpu, provides a (currently unused) list of all the symbols we need
* prepare_handler configures an exchange channel to be owned by this queue, and prepare_listener sets up thing that sees
* all callbacks which maps the exchange id to the MDProducerHandle and writes to shared mem
*/
class CXMain
{
public:
CXMain();
~CXMain();
void run(std::vector<UserSymbol>&); //call initialize, start the pump
void initialize(); //set up channels, etc
std::unique_ptr<CXListener> prepare_listener(const std::string& channel, const CMEChannel&);
private:
BTS_NO_COPY(CXMain);
CXSecurityIndex _index;
MDProducer _producer;
SecurityIDRecorder _security_recorder;
co2::SettlementRecorder _settlement_recorder;
std::vector<std::unique_ptr<CXListener>> _listeners;
};
}}//namespace
@@ -1,21 +0,0 @@
#pragma once
#include "lib/md/SecurityIndex.h"
#include <string>
namespace bts
{
namespace md
{
struct CXSecurityData
{
CXSecurityData(uint32_t id, MDProducerHandle& h) : id(id), handle(h) {}
uint32_t id; // exchange id
MDProducerHandle handle;
};
using CXSecurityIndex = ConcurrentSecurityIndex<uint32_t, CXSecurityData>;
}}//namespace
@@ -1,15 +0,0 @@
#pragma once
namespace celoxica
{
namespace gmac
{
#include "celoxica/gmac.h"
#include "celoxica/gmac_template_v3.h"
#include "celoxica/gmac_info.h"
}
}
@@ -1,210 +0,0 @@
// #include "catch/catch.hpp"
// #include "lib/config/config.h"
// #include "lib/config/protos/MarketData.pb.h"
// #include <vector>
// #include <string>
// #include <functional>
// #include <stdarg.h>
// #include "../MDP3Utils.h"
// #include "../onixs.h"
// using namespace bts::md;
// using namespace std;
// namespace bts {
// namespace md {
// map<string, onixs::FeedEngineSettings> create_feed_engine_settings(const std::vector<CMEChannel>& channels);
// const CMEOptions::CMEChannel channel_settings_for(const string& id);
// namespace test {
// vector<CMEChannel> test_channels(int count, ...) {
// // Doing this the C way because I still haven't updated VS
// vector<CMEChannel> result;
// va_list ap;
// va_start(ap, count);
// for (int x = 0; x < count; ++x) {
// const char* val = va_arg(ap, char*);
// CMEChannel temp;
// temp.id = val;
// result.push_back(temp);
// }
// va_end(ap);
// return result;
// }
// TEST_CASE("mdp3_tests","[md]") {
// SECTION("test_defaults") {
// config::internal::initialize_for_test("CMEOptions {}");
// vector<CMEChannel> channels = test_channels(1, "310");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// channels = test_channels(2, "310", "311");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// channels = test_channels(4, "310", "311", "400", "412");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// REQUIRE(!channel_settings_for("310").has_feed_engine());
// REQUIRE(!channel_settings_for("311").has_feed_engine());
// REQUIRE(!channel_settings_for("315").has_feed_engine());
// }
// SECTION("test_segregate_futures") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " feed_engine {\n"
// " name: 'default'\n"
// " }\n"
// " feed_engine {\n"
// " name: 'futures'\n"
// " num_threads: 4\n"
// " }\n"
// " # Futures\n"
// " channel {\n"
// " id: '310'\n"
// " id: '312'\n"
// " feed_engine: 'futures'\n"
// " }\n"
// " # Options\n"
// " channel {\n"
// " id: '311'\n"
// " id: '313'\n"
// " id: '315'\n"
// " feed_engine: 'default'\n"
// " }\n"
// " }\n"
// "}");
// vector<CMEChannel> channels = test_channels(1, "310");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result.begin()->first == "futures");
// REQUIRE(result.begin()->second.threadsCount() == 4);
// channels = test_channels(1, "311");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result.begin()->first == "default");
// REQUIRE(result.begin()->second.threadsCount() == 1);
// channels = test_channels(2, "311", "310");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 2);
// REQUIRE(result["default"].threadsCount() == 1);
// REQUIRE(result["futures"].threadsCount() == 4);
// // Test channels not listed in config
// channels = test_channels(2, "317", "318");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// }
// SECTION("test_no_default") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " feed_engine {\n"
// " name: 'futures'\n"
// " num_threads: 4\n"
// " }\n"
// " # any channel not listed will use the default feed engine\n"
// " # Futures\n"
// " channel {\n"
// " id: '310'\n"
// " id: '312'\n"
// " feed_engine: 'futures'\n"
// " }\n"
// " # Options\n"
// " channel {\n"
// " id: '311'\n"
// " id: '313'\n"
// " id: '315'\n"
// " use_tcp_replay: false\n"
// " }\n"
// " }\n"
// "}");
// vector<CMEChannel> channels = test_channels(1, "310");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result.begin()->first == "futures");
// REQUIRE(result.begin()->second.threadsCount() == 4);
// channels = test_channels(1, "311");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// channels = test_channels(2, "311", "310");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result["futures"].threadsCount() == 4);
// REQUIRE(channel_settings_for("310").use_tcp_replay() == true);
// REQUIRE(channel_settings_for("311").use_tcp_replay() == false);
// REQUIRE(channel_settings_for("315").use_tcp_replay() == false);
// }
// SECTION("test_one_thread_each") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " channel {\n"
// " id: '*'\n"
// " }\n"
// " }\n"
// "}\n");
// vector<CMEChannel> channels = test_channels(1, "310", "311");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// const auto& settings = channel_settings_for("311");
// REQUIRE(!settings.has_feed_engine());
// }
// SECTION("test_common_futures_thread") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " feed_engine {\n"
// " name: 'futures'\n"
// " num_threads: 4\n"
// " }\n"
// " channel {\n"
// " id: '310'\n"
// " feed_engine: 'futures'\n"
// " udp_socket_buffer_size: 1234\n"
// " }\n"
// " channel {\n"
// " id: '*'\n"
// " }\n"
// " }\n"
// "}\n");
// vector<CMEChannel> channels = test_channels(6, "310", "311", "315", "100", "10", "42");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// result = create_feed_engine_settings(test_channels(1, "315"));
// REQUIRE(result.size() == 0);
// REQUIRE(!channel_settings_for("311").has_feed_engine());
// REQUIRE(channel_settings_for("310").has_feed_engine());
// REQUIRE(channel_settings_for("310").udp_socket_buffer_size() == 1234);
// REQUIRE(channel_settings_for("311").has_udp_socket_buffer_size() == false);
// }
// }
// }}}//namespace
@@ -224,8 +224,8 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
set_security_common(def, fsec);
fsec.alt_symbol(def.strategySymbol);
const auto& legs = def.legs;
if (legs.size() > 0/* && legs.size() <= 20*/) {
const auto& legs = def.legs;
if (legs.size() > 0 && legs.size() <= 512) {
fsec.set_leg_count(legs.size());
for(size_t x = 0; x < fsec.leg_count(); ++x)
{
@@ -251,6 +251,8 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
add_security(fsec);
is_interested = true;
}
else
LOG(md, Error, "Ignoring security {} with {} legs", def.marketDesc, legs.size());
}
@@ -261,7 +263,7 @@ void ICEListener::on_future_strip(const FutureDefinition& def, bool& is_interest
auto exchange_id = def.marketId;
if (filter_security(exchange_id, def) != ADD) return;
FixedSpreadSecurityXS fsec;
FixedSpreadSecurityXL fsec;
set_security_common(def, fsec);
fsec.alt_symbol(def.stripName);
@@ -138,6 +138,7 @@ struct PositionServicePNLHelper::PImpl {
void PositionServicePNLHelper::compute_pnl(
const std::map<int, PositionUnit const*>& in_pos,
const std::map<std::string,uint32_t>& account_map,
const std::map<uint32_t,double>& cpp_override_map,
std::vector<PNLUnit>& out_pnl,
std::vector<AccountPNL>& out_account_pnl){
out_account_pnl.clear();
@@ -145,6 +146,8 @@ void PositionServicePNLHelper::compute_pnl(
const PositionUnit& pu = *(p.second);
uint32_t sec_key = pu.security_id();
double cpp = me->handle_index.get_cpp(SecurityKey(sec_key));
auto it = cpp_override_map.find(sec_key);
cpp = (it != cpp_override_map.end()) ? it->second : cpp;
double bid, ask, last, settle;
me->handle_index.get_md(SecurityKey(sec_key),bid,ask,last,settle);
PNLUnit pnlu;
@@ -17,7 +17,7 @@ private:
std::unique_ptr<PImpl> me;
private:
double _compute_day_pnl (const bts::api::position::PNLUnit&);
double _get_cpp (uint32_t);
//double _get_cpp (uint32_t);
// aggregates [sec, account]->pnl tp [account]->pnl
void _aggregate_account_pnl(
const std::vector<api::position::PNLUnit>& in_pnl,
@@ -29,6 +29,7 @@ private:
~PositionServicePNLHelper();
void compute_pnl(const std::map<int, bts::api::position::PositionUnit const*>& in_pos,
const std::map<std::string,uint32_t>& account_map,
const std::map<uint32_t,double>& cpp_override_map,
std::vector<bts::api::position::PNLUnit>& out_pnl,
std::vector<bts::api::position::AccountPNL>& out_account_pnl);
};
@@ -28,6 +28,7 @@ using namespace bts::api::position;
using namespace bts::md;
static auto const manual_fills_ns = "manual_fills";
static auto const security_params_ns = "security_parameters";
struct PositionServiceServer::PImpl {
bts::orderman::PositionMap state;
@@ -37,6 +38,7 @@ struct PositionServiceServer::PImpl {
int id;
// manual fills management
std::unique_ptr<persistence::IStore> manual_fills_store;
std::unique_ptr<persistence::IStore> security_params_store;
PositionServiceBase::Manual_fillsStatefulPublisher manual_fills_publisher;
// pnl
PositionServiceBase::Pnl_dataStatefulPublisher pnl_publisher;
@@ -47,6 +49,7 @@ struct PositionServiceServer::PImpl {
bts::api::admin::AdminService admin_service;
api::admin::AdminData admin_data;
std::map<std::string, uint32_t> account_map;
std::map<uint32_t, double> cpp_override_map;
PImpl(
bts::api::ServiceContext &context,
@@ -64,6 +67,8 @@ struct PositionServiceServer::PImpl {
// manual fills
manual_fills_store(
std::make_unique<persistence::SqliteStore>("manual_fills")),
security_params_store(
std::make_unique<persistence::SqliteStore>("security_parameters")),
manual_fills_publisher(manual_fills_publisher),
// pnl
pnl_publisher(pnl_publisher),
@@ -73,7 +78,9 @@ struct PositionServiceServer::PImpl {
// admin
admin_service(context.locate<bts::api::admin::AdminService>()),
admin_data(),
account_map() {}
account_map(),
cpp_override_map()
{}
};
PositionServiceServer::PositionServiceServer(bts::api::ServiceContext &context,
@@ -196,6 +203,27 @@ void PositionServiceServer::_clear_and_publish_pos(){
me->pos_publisher.add_or_update(*p.second);
me->pos_publisher.flush();
}
void PositionServiceServer::_load_security_params(){
me->cpp_override_map.clear();
auto all = me->security_params_store->retrieve_all()[security_params_ns];
SecurityParameters rec;
for (auto const &kvp : all)
if (utils::pb::from_string<SecurityParameters>(kvp.second, &rec)){
if(rec.has_cpp_override()){
LOG(OrderMan, Info, "Overriding cpp, key:{} cpp:{}",
rec.security_key(),
rec.cpp_override().value());
me->cpp_override_map[rec.security_key()] = rec.cpp_override().value();
}
}
else {
LOG(OrderMan, Error,
"Could not recover security parameter for security {} from storage. "
"This should be investigated",
kvp.first);
}
}
///////////////////////////////////////////////////////////////////////////////
/// Manual Fills API //////////////////////////////////////////////////////////
void PositionServiceServer::create_manual_fill(
@@ -294,6 +322,30 @@ void PositionServiceServer::set_base_position(
_clear_and_publish_pos();
}
void PositionServiceServer::update_security_parameters(
const SecurityParametersBatchOp& req,
const api::Response<SecurityParametersBatchOpResponse>&){
LOG(persist, Info, "PositionServiceServer::update_security_parameters bp count:{}",
req.op_params_size());
for (const auto& p : req.op_params()){
std::string key_str = std::to_string(p.security_key());
auto db_p = me->security_params_store->retrieve_one(security_params_ns, key_str);
auto found = db_p.first;
if (!found) {
me->security_params_store->insert(security_params_ns,
key_str,
utils::pb::to_string(p));
}
else {
me->security_params_store->insert(security_params_ns,
key_str,
utils::pb::to_string(p));
}
}
_load_security_params();
_compute_pnl_and_publish();
}
///////////////////////////////////////////////////////////////////////////////
/// PNL helpers ///////////////////////////////////////////////////////////////
void PositionServiceServer::_compute_pnl_and_publish() {
@@ -301,7 +353,8 @@ void PositionServiceServer::_compute_pnl_and_publish() {
me->state.get_pos(in_pos);
std::vector<PNLUnit> pnl;
std::vector<AccountPNL> account_pnl;
me->pnl_helper.compute_pnl(in_pos, me->account_map, pnl, account_pnl);
me->pnl_helper.compute_pnl(in_pos, me->account_map, me->cpp_override_map,
pnl, account_pnl);
for (const auto &u : pnl)
me->pnl_publisher.add_or_update(u);
for (const auto &u : account_pnl)
@@ -52,6 +52,7 @@ class PositionServiceServer : public bts::api::position::PositionServiceBase {
void _compute_pnl_and_publish();
void _set_admin_data(const api::admin::AdminData &admin_data);
void _clear_and_publish_pos();
void _load_security_params();
public:
PositionServiceServer(bts::api::ServiceContext &context, md::MDClient &);
@@ -74,6 +75,11 @@ class PositionServiceServer : public bts::api::position::PositionServiceBase {
const ::bts::api::position::BasePositionBatch &request,
const api::Response<::bts::api::position::SetBasePositionResponse>
&reply) override;
void update_security_parameters(
const ::bts::api::position::SecurityParametersBatchOp &request,
const api::Response<::bts::api::position::SecurityParametersBatchOpResponse>
&reply) override;
~PositionServiceServer();
};
+1 -1
View File
@@ -1 +1 @@
set(sources ../../externals/linux/include/google/protobuf/descriptor.proto;../../externals/linux/include/google/protobuf/timestamp.proto;../lib/api/apicore.proto;../lib/api/apigateway.proto;../lib/api/apioptions.proto;../lib/api/services/AdminService.proto;../lib/api/services/AlgoDefinitionService.proto;../lib/api/services/AlgoService.proto;../lib/api/services/ApiUnitTestService.proto;../lib/api/services/AuthService.proto;../lib/api/services/BasePositionService.proto;../lib/api/services/ErrorReportService.proto;../lib/api/services/HelloService.proto;../lib/api/services/MBOService.proto;../lib/api/services/MarginService.proto;../lib/api/services/MarketDataAuxiliaryService.proto;../lib/api/services/MarketDataRelay.proto;../lib/api/services/MarketDataService.proto;../lib/api/services/MoneyFileService.proto;../lib/api/services/OrderService.proto;../lib/api/services/OutOfProcessURLListener.proto;../lib/api/services/PersistenceService.proto;../lib/api/services/PositionService.proto;../lib/api/services/ShellDiagnosticService.proto;../lib/api/services/ShellLatencyService.proto;../lib/api/services/SyntheticSecurityService.proto;../lib/domain_model/Security.proto;../lib/md/protos/co2.proto;../lib/sparkproto/SparkProtos.proto;api/build.sh;api/node_modules/exit/test/fixtures/create-files.sh;api/node_modules/object.assign/test.sh;api/node_modules/watch/release.sh;api/quiet_build.sh;api/src/DynamicService.ts;api/src/ServiceContext.ts;api/src/bts_assert.ts;ui/src/Access.fs;ui/src/Accounts/ClipLimits.fs;ui/src/Accounts/ProductLimits.fs;ui/src/Accounts/SecurityLimits.fs;ui/src/Accounts/State.fs;ui/src/Accounts/Types.fs;ui/src/Accounts/View.fs;ui/src/App.fs;ui/src/Cmd.fs;ui/src/Controls/Button.fs;ui/src/Controls/DataGrid.fs;ui/src/Controls/Fields.fs;ui/src/Controls/Modal.fs;ui/src/Controls/ReactGrid.fs;ui/src/Controls/Table.fs;ui/src/Controls/nbsp.fs;ui/src/Global.fs;ui/src/Imports.fs;ui/src/Lens.fs;ui/src/Login/Login.fs;ui/src/Nav.fs;ui/src/State.fs;ui/src/Types.fs;ui/src/Users/State.fs;ui/src/Users/Types.fs;ui/src/Users/View.fs;ui/src/Util/EnumUtils.fs;ui/src/Util/JSMap.fs;ui/src/Util/StreamUtils.fs;ui/src/obj/Debug/netstandard2.0/ui.AssemblyInfo.fs)
set(sources ../../externals/linux/include/google/protobuf/descriptor.proto;../../externals/linux/include/google/protobuf/timestamp.proto;../lib/api/apicore.proto;../lib/api/apigateway.proto;../lib/api/apioptions.proto;../lib/api/services/AdminService.proto;../lib/api/services/AlgoDefinitionService.proto;../lib/api/services/AlgoService.proto;../lib/api/services/ApiUnitTestService.proto;../lib/api/services/AuthService.proto;../lib/api/services/BasePositionService.proto;../lib/api/services/ErrorReportService.proto;../lib/api/services/HelloService.proto;../lib/api/services/MBOService.proto;../lib/api/services/MarginService.proto;../lib/api/services/MarketDataAuxiliaryService.proto;../lib/api/services/MarketDataRelay.proto;../lib/api/services/MarketDataService.proto;../lib/api/services/MoneyFileService.proto;../lib/api/services/OrderService.proto;../lib/api/services/OutOfProcessURLListener.proto;../lib/api/services/PersistenceService.proto;../lib/api/services/PositionService.proto;../lib/api/services/ShellDiagnosticService.proto;../lib/api/services/ShellLatencyService.proto;../lib/api/services/SyntheticSecurityService.proto;../lib/domain_model/Security.proto;../lib/md/protos/co2.proto;../lib/sparkproto/SparkProtos.proto;api/build.sh;api/quiet_build.sh;api/src/DynamicService.ts;api/src/ServiceContext.ts;api/src/bts_assert.ts;ui/src/Access.fs;ui/src/Accounts/ClipLimits.fs;ui/src/Accounts/ProductLimits.fs;ui/src/Accounts/SecurityLimits.fs;ui/src/Accounts/State.fs;ui/src/Accounts/Types.fs;ui/src/Accounts/View.fs;ui/src/App.fs;ui/src/Cmd.fs;ui/src/Controls/Button.fs;ui/src/Controls/DataGrid.fs;ui/src/Controls/Fields.fs;ui/src/Controls/Modal.fs;ui/src/Controls/ReactGrid.fs;ui/src/Controls/Table.fs;ui/src/Controls/nbsp.fs;ui/src/Global.fs;ui/src/Imports.fs;ui/src/Lens.fs;ui/src/Login/Login.fs;ui/src/Nav.fs;ui/src/State.fs;ui/src/Types.fs;ui/src/Users/State.fs;ui/src/Users/Types.fs;ui/src/Users/View.fs;ui/src/Util/EnumUtils.fs;ui/src/Util/JSMap.fs;ui/src/Util/StreamUtils.fs;ui/src/obj/Debug/netstandard2.0/ui.AssemblyInfo.fs)
+1 -10
View File
@@ -2,11 +2,6 @@
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export PATH="$PATH:/root/.dotnet/tools"
# install npx
if ! [ -x "$(command -v npx)" ]; then
npm install -g npx
fi
export OUT=${BUILD_DIR:-../../../build}/web
MODE=manual
case "$OUT" in
@@ -39,9 +34,6 @@ PROTOC=../../../externals/linux/bin/protoc
ORANGE='\033[0;33m'
NOCOLOR='\033[0m'
npm install
# Run pbjs to produce protobuf module
echo -e \* ${ORANGE} pbjs ${NOCOLOR}
npx --no-install pbjs -t json-module -w commonjs -o $OUT/api/proto.js $PROTO_LIST
@@ -79,11 +71,10 @@ if [ "$FULL" == "1" ]; then
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
echo -e \* ${ORANGE} fable ${NOCOLOR}
cd ../ui/
npm install
cd src
mkdir -p $OUT/ui/
cp -r ../public/* $OUT/ui/
dotnet restore
dotnet restore -s ../../../../externals/nuget/packages
if [ "$MODE" == "release" ]; then
dotnet fable webpack -- --env.BUILD_PATH=$OUT -p
else
-22
View File
@@ -1,22 +0,0 @@
cmake_minimum_required(VERSION 3.5)
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libbase.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libutils.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libfix_lib.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclxlog.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libade_version.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libade.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclx.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libgmac_version.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libgmac.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libcputimestamp.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclxconfig.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libconfigfile.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libac.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/librc.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/liblldt.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libregister.so")
file(COPY ${CELOXICA_LIB} DESTINATION ${CMAKE_BINARY_DIR}/src/server/sparkd)
+1 -1
View File
@@ -7,7 +7,7 @@ if not exist build\src\lib\libspark.vcxproj (
)
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools\VsDevCmd.bat"
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore spark_client.sln /p:Configuration=release
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln /p:Configuration=release
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR_AND_END
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary spark_client.sln /p:Configuration=release
+3 -4
View File
@@ -1,9 +1,8 @@
#!/bin/bash
BUILD=clang_debug
BUILD_LOC=/home/ppinto/temp/spark_build
if [ -f /.dockerenv ]; then
cd $BUILD_LOC/$BUILD && ninja
(cd `readlink -f .` && ninja)
else
cid=`docker ps | grep -P "emacs_ide|bluetradingsystems/linux_docker" | awk '{print $1}'`
docker exec $cid /bin/bash -c "cd $BUILD_LOC/$BUILD && ninja"
docker exec $cid /bin/bash -c "(cd `readlink -f .` && ninja)"
fi
+2 -2
View File
@@ -2,6 +2,6 @@
setlocal
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\Tools\VsDevCmd.bat" > nul
cd /d %~dp0/../
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore spark_client.sln
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore spark_client.sln
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln
@@ -12,6 +12,12 @@ ignore = Path docker
ignore = Regex .*\.pyc
ignore = Path x64
ignore = Path .vs
ignore = Path src/.vs
ignore = Path src/lib/.vs
ignore = Path src/lib/out
ignore = Path src/sdk/out
ignore = Path src/server/sparkd/out
ignore = Path src/web/out
ignore = Path .vscode
ignore = Path src/client
ignore = Path src/web/api/node_modules
+5
View File
@@ -4,6 +4,11 @@ SRC_ROOT=$DIR/../
mkdir -p $SRC_ROOT/build/
BUILD_ROOT=`readlink -f $SRC_ROOT/build`
rm -f $SRC_ROOT/src/web/ui/node_modules
rm -f $SRC_ROOT/src/web/api/node_modules
ln -s $SRC_ROOT/externals/npm/node_modules $SRC_ROOT/src/web/ui/node_modules
ln -s $SRC_ROOT/externals/npm/node_modules $SRC_ROOT/src/web/api/node_modules
rm -rf $BUILD_ROOT/clang_debug
mkdir $BUILD_ROOT/clang_debug
cd $BUILD_ROOT/clang_debug
Regular → Executable
View File
File mode changed.