Compare commits
78
Commits
2.17.0_rc0
...
2.17.0_rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
888b4e22c0 | ||
|
|
534ce5a8ec | ||
|
|
c2b9ac57dc | ||
|
|
2783361325 | ||
|
|
46d35131d7 | ||
|
|
3b95154c04 | ||
|
|
1a6b918853 | ||
|
|
bc22605a25 | ||
|
|
f3d28add1c | ||
|
|
63d260a6f8 | ||
|
|
a502c365e5 | ||
|
|
fdacd8b543 | ||
|
|
3d1de9a0c7 | ||
|
|
b2b3fa5949 | ||
|
|
86ecf69820 | ||
|
|
8f537a8fda | ||
|
|
01b9ff6b47 | ||
|
|
2609663d3e | ||
|
|
fd89241611 | ||
|
|
a69118c5d3 | ||
|
|
5accd0e2b4 | ||
|
|
df5ad61cd2 | ||
|
|
4a7674eb29 | ||
|
|
5a458287f4 | ||
|
|
5921f9f6b5 | ||
|
|
4c73a63ae8 | ||
|
|
42b1c921a8 | ||
|
|
fab3334b1e | ||
|
|
7239dd3ade | ||
|
|
dd4c8eb649 | ||
|
|
76a29bdc8a | ||
|
|
06d708f3ea | ||
|
|
e2f70cbc19 | ||
|
|
9d54e47a03 | ||
|
|
faccdf581e | ||
|
|
1a64714a54 | ||
|
|
3ba6792a85 | ||
|
|
10e75f30e6 | ||
|
|
20eac19c4f | ||
|
|
8c0765a2c7 | ||
|
|
7a1f88e813 | ||
|
|
3420f14c8a | ||
|
|
86277dfbfe | ||
|
|
4839e464c6 | ||
|
|
dc2ad071a9 | ||
|
|
65e49992f9 | ||
|
|
afbb9ae92d | ||
|
|
ff088142d9 | ||
|
|
93b7cf346b | ||
|
|
8ee9b84488 | ||
|
|
e3648f5373 | ||
|
|
facd3a9d81 | ||
|
|
84969cb481 | ||
|
|
6d521e77db | ||
|
|
82c49f8f49 | ||
|
|
e074309da1 | ||
|
|
2d622a5f39 | ||
|
|
d51d20e84c | ||
|
|
2ccdef9dc5 | ||
|
|
42e62a30bf | ||
|
|
c233c607fa | ||
|
|
462cb930f7 | ||
|
|
550a4e8d50 | ||
|
|
99b8221efc | ||
|
|
19e74c30f8 | ||
|
|
26693a4c98 | ||
|
|
d8bdb69a7c | ||
|
|
6d357a74e1 | ||
|
|
73f4e4814c | ||
|
|
312bc4be15 | ||
|
|
a0c3ba449a | ||
|
|
d5a2a7f3f0 | ||
|
|
eda8c94def | ||
|
|
9a8e80e149 | ||
|
|
6a7e3d739d | ||
|
|
e6b8274813 | ||
|
|
9783520c62 | ||
|
|
7e7f0bc37f |
No files matched your search
+1
-1
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
Submodule docker/linux updated: 0afd5e92de...296f277d40.
+1
-1
Submodule externals updated: 38945c5f49...2ea5e355a6.
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "2.1.508",
|
||||
"rollForward": "latestMajor"
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,10 @@
|
||||
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(VCTargetsPath)'==''">
|
||||
<VCTargetsPath>C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\VC\VCTargets</VCTargetsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 @@
|
||||
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
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+139
@@ -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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+125
@@ -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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+110
@@ -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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -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 @@
|
||||
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)
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 @@
|
||||
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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
File mode changed.
Reference in new issue
Block a user