Compare commits

..
74 Commits
Author SHA1 Message Date
Kevin Darby dec5ab3036 py36 we also need to put py36 in externals 2018-06-06 17:55:18 -04:00
Kevin Darby e5391245d4 python 2.7 vs 3.6 blues 2018-06-06 17:24:31 -04:00
Kevin Darby b25f5556af added python27 to front of the path in vscode.cmd 2018-06-06 15:54:37 -04:00
Kevin Darby c10a4be411 fixing crash on spreader; the VMs get created no matter what security type, they're filtered later 2018-06-06 15:49:40 -04:00
Eric Harding de312c3023 Update c# language version to 7.2 2018-06-06 15:45:48 -04:00
Jake Norbut 93ff56c5cd Merged in badge_perf (pull request #36)
Badge perf

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2018-06-06 19:04:17 +00:00
Jake Norbut 5254b7367a PR suggestions; changing summary and description to backings as well since the tooltip was not changing as the lovm updated 2018-06-06 13:52:50 -05:00
Kevin Darby 6d2efb7dc9 clarified min_initial_tick_offset 2018-06-06 13:34:28 -04:00
Jake Norbut 50214741e7 PR - backings for a lazy life 2018-06-06 12:31:22 -05:00
Pete 56950b682d fat fingered 2018-06-06 13:00:45 -04:00
Jake Norbut 83b1bbf087 PR suggestions 2018-06-06 11:44:58 -05:00
Pete 203998e201 change default secure CME login to true 2018-06-06 12:40:25 -04:00
Kevin Darby 59d88d3cc1 colors per Michael 2018-06-06 12:32:13 -04:00
Kevin Darby 13f9717195 Merged in algos (pull request #30)
Algos
2018-06-06 16:15:06 +00:00
Kevin Darby 855e0d7ef9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into algos 2018-06-06 11:46:36 -04:00
Michael Harding 27a1cb277c SPARK-1201: Remove Connections table borders; Add colSpan to table cells 2018-06-06 11:18:59 -04:00
Michael Harding 3107d5f382 SPARK-1201: Add modal create dialog for Users and Accounts 2018-06-06 11:18:59 -04:00
Michael Harding e1940faeac AdminUI: Add Modal Dialog 2018-06-06 11:18:59 -04:00
Jake Norbut a2f22482f6 removing perf testing code; removing unused orders_cache 2018-06-06 09:36:21 -05:00
Taha Afzal ccea4571a6 SPARK-1206 uninitialized limit returned 2018-06-06 10:24:19 -04:00
Taha Afzal 3a72a4a82e Spreader's bookkeepingdata_ptr is a unique_ptr and some stylistic changes to shorten line widths 2018-06-06 10:11:39 -04:00
John Colvin ed6affb6b5 SPARK-1196 synth dialog - security text in column header is wrong (part 2) 2018-06-05 21:17:16 -04:00
John Colvin 2fc208ef74 SPARK-1205 Orderbook filter bug 2018-06-05 19:57:16 -04:00
Jake Norbut 64f86db86b misc cleanup 2018-06-05 18:07:05 -05:00
Jake Norbut 5e960479c7 slow badges no more 2018-06-05 18:01:04 -05:00
Pedro Pinto d7a7b480ad Persist exchange book in sim even if merge_md is false. 2018-06-05 17:36:39 -04:00
Pedro Pinto d94afe9867 Tweaked performance analysis tools. 2018-06-05 16:59:54 -04:00
Taha Afzal a8bd99af56 quoter's leg_prop_data is a ref into spreaderbookkeepingdata 2018-06-05 15:00:55 -04:00
John Colvin e11b5928ce SPARK-1181 touch up time filter dialog 2018-06-05 14:34:29 -04:00
Taha Afzal 6bcf352813 deleting cons's and =operators for SpreaderOrder 2018-06-05 14:10:07 -04:00
Michael Harding eeec7b0076 SPARK-1146: Settings Button in the ladder looks consistent 2018-06-05 13:33:18 -04:00
Michael Harding bacdb1c4cd SPARK-1201: Trading and admin permission columns for Users/Accounts Tab 2018-06-05 10:22:25 -04:00
Michael Harding 1e4ad28088 SPARK-1201: Security column in Secutity Limits 2018-06-05 10:03:32 -04:00
Michael Harding e013f93169 SPARK-1201: Show empty add dropdown 2018-06-05 10:03:32 -04:00
Kevin Darby 002c051c06 make it more clear that detach does not necessarily imply cancellation of the parent 2018-06-01 16:58:18 -04:00
Kevin Darby 42fd7b3f40 added state mutators for base class Pimpl...lovin that c++ 2018-06-01 16:46:28 -04:00
Kevin Darby f73d163312 implemented most of Pedro's review, need to do the mutators still 2018-06-01 15:36:06 -04:00
Kevin Darby ee124401b9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into algos 2018-06-01 13:46:27 -04:00
Kevin Darby ec7417a9c6 added comments to algo order headers and changed wording of a parameter 2018-06-01 13:46:10 -04:00
Eric Harding 536f4703ab Add visual studio perf session files to gitignore 2018-06-01 13:46:10 -04:00
Michael Harding 5f848a36e2 fix ui.fsproj 2018-06-01 13:46:10 -04:00
Michael Harding a7c714591a Fix Webpack config for development 2018-06-01 13:46:10 -04:00
Michael Harding 4655a68593 SPARK-1155 2018-06-01 13:46:10 -04:00
Eric Harding 9677d8996e SPARK-1189 - Windows C++ build is not producing symbols file 2018-06-01 13:46:10 -04:00
John Colvin 3f0a9e442e change UpdateDemuxer to TableUpdateDemuxer, which updates incrementally 2018-06-01 13:46:10 -04:00
Eric Harding f3387b57af SPARK-1183 3-5 1000 lot iceberg orders can lag/lock up the UI 2018-06-01 13:46:09 -04:00
John Colvin abed4bbff6 CVOrderBook - fix typeo that trapped the root_node() fn in an endless loop 2018-06-01 13:46:09 -04:00
Eric Harding bb90429ada Fix problem where you can't see price moves when mouse is over a cell in the ladder 2018-06-01 13:46:09 -04:00
Kevin Darby 1efbc2fd45 Merge branch 'master' of bitbucket.org:bluetradesys/spark into algos 2018-05-31 16:37:00 -04:00
Kevin Darby 04027ed980 Merge branch 'master' of bitbucket.org:bluetradesys/spark into algos 2018-05-29 15:26:55 -04:00
Kevin Darby 5a850c314c Merge branch 'algos' of bitbucket.org:bluetradesys/spark into algos 2018-05-29 15:26:08 -04:00
Kevin Darby b566668d83 pushd, a better cd 2018-05-29 11:52:17 -04:00
Kevin Darby 6679695931 fixed cancel state in TopOrder 2018-05-26 17:01:04 -04:00
Kevin Darby 23a8112dd3 Merge branch 'algos' of bitbucket.org:bluetradesys/spark into algos 2018-05-26 15:13:22 -04:00
Kevin Darby b9a07a8e21 toporder, maybe 2018-05-26 15:13:13 -04:00
Kevin Darby 86deaf2edb Join order works and hedges 2018-05-26 15:13:13 -04:00
Kevin Darby fc347250e3 pushing for join to look at joinordervm validation 2018-05-26 15:13:13 -04:00
Kevin Darby aae867a555 doskey, alive and well 2018-05-26 15:13:13 -04:00
Kevin Darby 37a454cf51 behold, TickOrder 2018-05-26 15:13:13 -04:00
Kevin Darby 5162b9f0c0 toporder, maybe 2018-05-26 15:12:55 -04:00
Taha Afzal ad757495bd comments 2018-05-25 18:42:30 -04:00
Kevin Darby e473c18a30 Join order works and hedges 2018-05-25 18:33:30 -04:00
Taha Afzal 640ec0b1ed spreader tests 2018-05-25 18:06:19 -04:00
Jake Norbut c3e2622a22 Ladder - restoring badge context menu and hooking up crack 2018-05-25 18:06:19 -04:00
John Colvin 7d05f76fe4 DIYGrid HighPerformanceRow - InvalidateVisual when DataContext changes 2018-05-25 18:06:19 -04:00
Kevin Darby 7a8a38f89d pushing for join to look at joinordervm validation 2018-05-25 18:04:58 -04:00
Michael Harding 1e54cb1daf Order Type Colors 2018-05-25 18:04:57 -04:00
Eric Harding 7b714018d2 SPARK-1139 Make Combox faster when system is under load (no AllowsTransparency) 2018-05-25 18:04:57 -04:00
Taha Afzal c3371d0971 re-org'ing SpreaderUtils functions into cpp 2018-05-25 18:04:57 -04:00
Kevin Darby 322e3d81ba doskey, alive and well 2018-05-24 18:24:10 -04:00
Taha Afzal 4ada5a89f4 SPARK-1119 completed 2018-05-24 18:24:10 -04:00
Eric Harding 15aa016aa0 Reduced CPU for cert market data during development 2018-05-24 18:24:10 -04:00
Taha Afzal e77f3169de SPARK-1119 (deleting unused API functions) 2018-05-24 18:24:10 -04:00
Kevin Darby 660c5d3a06 behold, TickOrder 2018-05-24 16:22:19 -04:00
68 changed files with 3081 additions and 508 deletions

No files matched your search

+1
View File
@@ -39,6 +39,7 @@
<Message Importance="High" Text="Generating service %(Proto3.FileName)" />
<Exec Command="
set PATH=$(spark_build_python_path);%path% &amp;
set PYTHONPATH=$(Externals)\python &amp;
$(protoc) ^
%(Proto3.FullPath) ^
@@ -293,7 +293,7 @@
</DataTrigger>
</Style.Triggers>
</Style>
<DataGridTextColumn x:Key="AddColumnPlaceholder" x:Shared="False" IsReadOnly="True">
<DataGridTextColumn x:Key="{x:Static usp:ExecutionStrategyEditor.AddColumnPlaceholderKey}" x:Shared="False" IsReadOnly="True">
<DataGridTextColumn.Header>
<c:PopupControl
ShowExpanderArrow="False" PopupPlacement="Right" Header="Add Leg" Padding="6,0"
@@ -308,7 +308,7 @@
</c:PopupControl>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataTemplate x:Key="RemovableColumnHeaderTemplate">
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.RemovableColumnHeaderTemplateKey}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -321,7 +321,9 @@
<TextBlock Grid.Column="1" Margin="4,0,0,0" Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.NonRemovableColumnHeaderTemplateKey}">
<TextBlock Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
</DataTemplate>
<ControlTemplate x:Key="ExecutionStrategyEditorTemplate" TargetType="usp:ExecutionStrategyEditor">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
@@ -25,7 +25,11 @@ namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for ExecutionStrategyEditor.xaml
/// </summary>
partial class ExecutionStrategyEditor : Control {
partial class ExecutionStrategyEditor : Control {
public const string
RemovableColumnHeaderTemplateKey = "RemovableColumnHeaderTemplate",
NonRemovableColumnHeaderTemplateKey = "ReadOnlyColumnHeaderTemplate",
AddColumnPlaceholderKey = "AddColumnPlaceholder";
static ExecutionStrategyEditor() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExecutionStrategyEditor), new FrameworkPropertyMetadata(typeof(ExecutionStrategyEditor)));
@@ -196,7 +200,7 @@ namespace mbts.ui.spark {
if (dg != null) {
var legs = LegTuples;
dg.Columns.SetItems(legs.Select(_column_factory));
if (IsNew) dg.Columns.Add((DataGridColumn)FindResource("AddColumnPlaceholder"));
if (IsNew) dg.Columns.Add((DataGridColumn)FindResource(AddColumnPlaceholderKey));
_dg_line_adorner = AdornerHelper.attach_continuously_on_loaded(() => new ExecutionStrategyGridLineAdorner(dg), dg);
_legs_collection_changed_sub = (legs as INotifyCollectionChanged)?.Subscribe((s, e) => dg.Columns.ReflectChange(legs, _column_factory, e));
}
@@ -204,7 +208,7 @@ namespace mbts.ui.spark {
DataGridColumn _column_factory(LegSpreaderParamsTuple l) {
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter) { Width = 75, };
if (IsNew) col.HeaderTemplate = (DataTemplate)FindResource("RemovableColumnHeaderTemplate");
col.HeaderTemplate = (DataTemplate)FindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey);
DataGridEx.SetRightBorder(col, new DataGridExVerticalLine(false, true, Colors.DarkGray, 1));
return col;
}
@@ -240,7 +244,7 @@ namespace mbts.ui.spark {
FrameworkElement _generate_element(IValueConverter cell_template_converter) {
var rv = new ContentControl();
rv.SetBinding(ContentControl.ContentProperty, new Binding());
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding("data_type") { Converter = cell_template_converter });
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding(nameof(ICell.data_type)) { Converter = cell_template_converter });
return rv;
}
}
@@ -20,6 +20,10 @@ namespace mbts.ui.spark {
public DataTemplate StopLimitOrderTemplate { get; set; }
public DataTemplate StopMarketOrderTemplate { get; set; }
public DataTemplate TimeBoxOrderTemplate { get; set; }
public DataTemplate TickOrderTemplate { get; set; }
public DataTemplate LeanOrderTemplate { get; set; }
public DataTemplate JoinOrderTemplate { get; set; }
public DataTemplate TopOrderTemplate { get; set; }
public DataTemplate IcebergOrderTemplate { get; set; }
public DataTemplate SpreaderTemplate { get; set; }
public DataTemplate BTSStopOrderTemplate { get; set; }
@@ -33,6 +37,10 @@ namespace mbts.ui.spark {
case CustomOrderDefinition.CustomOneofCase.Stop: return StopMarketOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.StopLimit: return StopLimitOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.TimeBox: return TimeBoxOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Tick: return TickOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Lean: return LeanOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Join: return JoinOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Top: return TopOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Iceberg: return IcebergOrderTemplate;
case CustomOrderDefinition.CustomOneofCase.Spreader: return SpreaderTemplate;
}
@@ -0,0 +1,287 @@
using bts.api;
using bts.api.orderman;
using mbts.ui;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark
{
static partial class OrderTypeVMFactory
{
class InlineJoinOrderProperties
{
public InlineJoinOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
var hedgetype = CustomOneofCase.Lean;
if(!child_order_types.Contains(hedgetype))
hedgetype = CustomOneofCase.Limit;
//ExceptionAssert.InvalidOperation.That(child_order_types.Contains(hedgetype), $"Hedge order type {hedgetype} is not in hedge_order_types");
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
join_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
enable_hedge = new InlineUserProperty<bool>(false, false);
hedge_ticks = new InlineUserProperty<int>(1, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
public readonly InlineUserProperty<bts.spark.QuantityMeasure> join_threshold;
public readonly InlineUserProperty<bool> enable_hedge;
public readonly InlineUserProperty<int> hedge_ticks;
}
class JoinOrderVM : OrderTypeVMBase<JoinOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
{
readonly IObservable<SecurityImmutable> _security_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
InlineJoinOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
IOrderTypeVM _hedge_order;
IDisposable _hedge_order_errors_sub;
public uint tick_offset_on_deploy { get; set; } = 0;
public bts.spark.QuantityMeasure join_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
bool _enable_hedge = false;
public bool enable_hedge
{
get { return _enable_hedge; }
set
{
_enable_hedge = value;
RaisePropertyChanged(nameof(enable_hedge));
}
}
public int hedge_ticks { get; set; } = 1;
public JoinOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
: this(model, security_source, security_source.Select(s =>
new InlineJoinOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
public JoinOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
IObservable<InlineJoinOrderProperties> inline_properties) : base(model)
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
}
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
{
_select_hedge_order(p.selected_hedge_order_type.value);
}
if (p.tick_offset_on_deploy.force_override)
{
tick_offset_on_deploy = p.tick_offset_on_deploy.value;
RaisePropertyChanged(nameof(tick_offset_on_deploy));
}
if (p.join_threshold.force_override)
{
join_threshold = p.join_threshold.value;
RaisePropertyChanged(nameof(join_threshold));
}
if (p.enable_hedge.force_override)
{
enable_hedge = p.enable_hedge.value;
RaisePropertyChanged(nameof(enable_hedge));
}
if (p.hedge_ticks.force_override)
{
hedge_ticks = p.hedge_ticks.value;
RaisePropertyChanged(nameof(hedge_ticks));
}
_inline_props = p;
_validate();
})
);
_validate();
}
public override void assign_to(CustomOrderDefinition value)
{
if (_child_order == null)
_select_child_order(model.factory.default_order_type(_security.Value, true));
if (_hedge_order == null && enable_hedge)
_select_hedge_order(model.factory.default_order_type(_security.Value, true));
_validate();
if (!HasErrors)
{
var i = factory.join_order();
i.TickOffsetOnDeploy = tick_offset_on_deploy;
i.JoinThreshold = join_threshold;
i.EnableHedge = enable_hedge;
i.HedgeTicks = hedge_ticks;
child_order.assign_to(i.Child);
if(enable_hedge && hedge_order != null)
hedge_order.assign_to(i.Hedge);
value.Join = i;
}
}
public ICollection<IOrderTypeVM> child_order_types
{
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _child_order_choices;
}
}
public ICollection<IOrderTypeVM> hedge_order_types
{
get
{
if (_hedge_order_choices == null)
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _hedge_order_choices;
}
}
public IOrderTypeVM child_order
{
get
{
init();
return _child_order;
}
set
{
Disposable.dispose(ref _child_order_errors_sub);
_child_order = value;
_child_order?.init();
if (_child_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(child_order));
}
}
public IOrderTypeVM hedge_order
{
get
{
init();
return _hedge_order;
}
set
{
Disposable.dispose(ref _hedge_order_errors_sub);
_hedge_order = value;
_hedge_order?.init();
if (_hedge_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_hedge_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(hedge_order));
}
}
public void Dispose()
{
Disposable.dispose(ref _dispose);
if (_child_order_choices != null)
_child_order_choices.OfType<IDisposable>().DisposeAll();
if (_hedge_order_choices != null)
_hedge_order_choices.OfType<IDisposable>().DisposeAll();
}
public override void init()
{
if (_child_order == null && _security.HasValue)
_select_child_order();
if (_hedge_order == null && _security.HasValue && enable_hedge)
_select_hedge_order();
_validate();
}
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
void _select_child_order(CustomOneofCase ot)
{
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_child_order == null)
_child_order = child_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(child_order));
}
void _select_hedge_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
_inline_props.selected_hedge_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_hedge_order(ot);
}
void _select_hedge_order(CustomOneofCase ot)
{
_hedge_order = hedge_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_hedge_order == null)
_hedge_order = hedge_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(hedge_order));
}
void _validate()
{
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
set_error(nameof(child_order), string.Empty, true);
else clear_error(nameof(child_order));
if ( (_hedge_order == null && enable_hedge) || (_hedge_order is INotifyDataErrorInfo hdei && hdei.HasErrors))
set_error(nameof(hedge_order), string.Empty, true);
else clear_error(nameof(hedge_order));
}
}
}
}
@@ -0,0 +1,174 @@
using bts.api;
using bts.api.orderman;
using mbts.ui;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark
{
static partial class OrderTypeVMFactory
{
class InlineLeanOrderProperties
{
public InlineLeanOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
max_chase_ticks = new InlineUserProperty<uint>(10, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
public readonly InlineUserProperty<uint> max_chase_ticks;
}
class LeanOrderVM : OrderTypeVMBase<LeanOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
{
readonly IObservable<SecurityImmutable> _security_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
InlineLeanOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
public uint max_chase_ticks { get; set; } = 10;
public LeanOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
: this(model, security_source, security_source.Select(s =>
new InlineLeanOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
public LeanOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
IObservable<InlineLeanOrderProperties> inline_properties) : base(model)
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
}
if (p.cross_threshold.force_override)
{
cross_threshold = p.cross_threshold.value;
RaisePropertyChanged(nameof(cross_threshold));
}
if (p.max_chase_ticks.force_override)
{
max_chase_ticks = p.max_chase_ticks.value;
RaisePropertyChanged(nameof(max_chase_ticks));
}
_inline_props = p;
_validate();
})
);
_validate();
}
public override void assign_to(CustomOrderDefinition value)
{
if (_child_order == null)
_select_child_order(model.factory.default_order_type(_security.Value, true));
_validate();
if (!HasErrors)
{
var i = factory.lean_order();
i.CrossThreshold = cross_threshold;
i.MaxChaseTicks = max_chase_ticks;
child_order.assign_to(i.Child);
value.Lean = i;
}
}
public ICollection<IOrderTypeVM> child_order_types
{
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _child_order_choices;
}
}
public IOrderTypeVM child_order
{
get
{
init();
return _child_order;
}
set
{
Disposable.dispose(ref _child_order_errors_sub);
_child_order = value;
_child_order?.init();
if (_child_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(child_order));
}
}
public void Dispose()
{
Disposable.dispose(ref _dispose);
if (_child_order_choices != null)
_child_order_choices.OfType<IDisposable>().DisposeAll();
}
public override void init()
{
if (_child_order == null && _security.HasValue)
_select_child_order();
_validate();
}
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
void _select_child_order(CustomOneofCase ot)
{
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_child_order == null)
_child_order = child_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(child_order));
}
void _validate()
{
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
set_error(nameof(child_order), string.Empty, true);
else clear_error(nameof(child_order));
}
}
}
}
@@ -14,6 +14,10 @@ namespace mbts.spark {
public static IOrderTypeVM stop(OrdersModel model) => new StopOrderVM(model);
public static IOrderTypeVM stop_limit(OrdersModel model) => new StopLimitOrderVM(model);
public static IOrderTypeVM time_box(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TimeBoxOrderVM(model, security_source);
public static IOrderTypeVM tick(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TickOrderVM(model, security_source);
public static IOrderTypeVM lean(OrdersModel model, IObservable<SecurityImmutable> security_source) => new LeanOrderVM(model, security_source);
public static IOrderTypeVM join(OrdersModel model, IObservable<SecurityImmutable> security_source) => new JoinOrderVM(model, security_source);
public static IOrderTypeVM top(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TopOrderVM(model, security_source);
public static IOrderTypeVM iceberg(OrdersModel model, IObservable<SecurityImmutable> security_source) => new IcebergOrderVM(model, security_source);
public static IOrderTypeVM spreader(OrdersModel model, IObservable<SecurityImmutable> security_source) => new SpreaderOrderVM(model, security_source);
@@ -27,6 +31,10 @@ namespace mbts.spark {
case CustomOneofCase.Stop: return stop(model);
case CustomOneofCase.StopLimit: return stop_limit(model);
case CustomOneofCase.TimeBox: return time_box(model, security_source);
case CustomOneofCase.Tick: return tick(model, security_source);
case CustomOneofCase.Lean: return lean(model, security_source);
case CustomOneofCase.Join: return join(model, security_source);
case CustomOneofCase.Top: return top(model, security_source);
case CustomOneofCase.Iceberg: return iceberg(model, security_source);
case CustomOneofCase.Spreader: return spreader(model, security_source);
default: throw new NotImplementedException();
@@ -0,0 +1,201 @@
using bts.api;
using bts.api.orderman;
using mbts.ui;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark
{
static partial class OrderTypeVMFactory
{
class InlineTickOrderProperties
{
public InlineTickOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
cross_ticks = new InlineUserProperty<uint>(0, false);
max_chase_ticks = new InlineUserProperty<uint>(10, false);
detach_on_toward = new InlineUserProperty<bool>(true, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
public readonly InlineUserProperty<uint> cross_ticks;
public readonly InlineUserProperty<uint> max_chase_ticks;
public readonly InlineUserProperty<bool> detach_on_toward;
}
class TickOrderVM : OrderTypeVMBase<TickOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
{
readonly IObservable<SecurityImmutable> _security_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
InlineTickOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
public uint tick_offset_on_deploy { get; set; } = 0;
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
public uint cross_ticks { get; set; } = 0;
public uint max_chase_ticks { get; set; } = 10;
public bool detach_on_toward { get; set; } = true;
public TickOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
: this(model, security_source, security_source.Select(s =>
new InlineTickOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
public TickOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
IObservable<InlineTickOrderProperties> inline_properties) : base(model)
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
}
if (p.tick_offset_on_deploy.force_override)
{
tick_offset_on_deploy = p.tick_offset_on_deploy.value;
RaisePropertyChanged(nameof(tick_offset_on_deploy));
}
if (p.cross_threshold.force_override)
{
cross_threshold = p.cross_threshold.value;
RaisePropertyChanged(nameof(cross_threshold));
}
if (p.cross_ticks.force_override)
{
cross_ticks = p.cross_ticks.value;
RaisePropertyChanged(nameof(cross_ticks));
}
if (p.max_chase_ticks.force_override)
{
max_chase_ticks = p.max_chase_ticks.value;
RaisePropertyChanged(nameof(max_chase_ticks));
}
if (p.detach_on_toward.force_override)
{
detach_on_toward = p.detach_on_toward.value;
RaisePropertyChanged(nameof(detach_on_toward));
}
_inline_props = p;
_validate();
})
);
_validate();
}
public override void assign_to(CustomOrderDefinition value)
{
if (_child_order == null)
_select_child_order(model.factory.default_order_type(_security.Value, true));
_validate();
if (!HasErrors)
{
var i = factory.tick_order();
i.TickOffsetOnDeploy = tick_offset_on_deploy;
i.CrossThreshold = cross_threshold;
i.CrossTicks = cross_ticks;
i.MaxChaseTicks = max_chase_ticks;
i.DetachOnToward = detach_on_toward;
child_order.assign_to(i.Child);
value.Tick = i;
}
}
public ICollection<IOrderTypeVM> child_order_types
{
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _child_order_choices;
}
}
public IOrderTypeVM child_order
{
get
{
init();
return _child_order;
}
set
{
Disposable.dispose(ref _child_order_errors_sub);
_child_order = value;
_child_order?.init();
if (_child_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(child_order));
}
}
public void Dispose()
{
Disposable.dispose(ref _dispose);
if (_child_order_choices != null)
_child_order_choices.OfType<IDisposable>().DisposeAll();
}
public override void init()
{
if (_child_order == null && _security.HasValue)
_select_child_order();
_validate();
}
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
void _select_child_order(CustomOneofCase ot)
{
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_child_order == null)
_child_order = child_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(child_order));
}
void _validate()
{
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
set_error(nameof(child_order), string.Empty, true);
else clear_error(nameof(child_order));
}
}
}
}
@@ -0,0 +1,287 @@
using bts.api;
using bts.api.orderman;
using mbts.ui;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark
{
static partial class OrderTypeVMFactory
{
class InlineTopOrderProperties
{
public InlineTopOrderProperties(CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
var hedgetype = CustomOneofCase.Lean;
if(!child_order_types.Contains(hedgetype))
hedgetype = CustomOneofCase.Limit;
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
watch_level_tick_offset = new InlineUserProperty<uint>(0, false);
top_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 }, false);
enable_hedge = new InlineUserProperty<bool>(false, false);
hedge_ticks = new InlineUserProperty<int>(1, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
public readonly InlineUserProperty<uint> watch_level_tick_offset;
public readonly InlineUserProperty<bts.spark.QuantityMeasure> top_threshold;
public readonly InlineUserProperty<bool> enable_hedge;
public readonly InlineUserProperty<int> hedge_ticks;
}
class TopOrderVM : OrderTypeVMBase<TopOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
{
readonly IObservable<SecurityImmutable> _security_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
InlineTopOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
IOrderTypeVM _hedge_order;
IDisposable _hedge_order_errors_sub;
public uint watch_level_tick_offset { get; set; } = 0;
public bts.spark.QuantityMeasure top_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
bool _enable_hedge = false;
public bool enable_hedge
{
get { return _enable_hedge; }
set
{
_enable_hedge = value;
RaisePropertyChanged(nameof(enable_hedge));
}
}
public int hedge_ticks { get; set; } = 1;
public TopOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
: this(model, security_source, security_source.Select(s =>
new InlineTopOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList())))
{ }
public TopOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source,
IObservable<InlineTopOrderProperties> inline_properties) : base(model)
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
.Subscribe(p =>
{
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
}
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
{
_select_hedge_order(p.selected_hedge_order_type.value);
}
if (p.watch_level_tick_offset.force_override)
{
watch_level_tick_offset = p.watch_level_tick_offset.value;
RaisePropertyChanged(nameof(watch_level_tick_offset));
}
if (p.top_threshold.force_override)
{
top_threshold = p.top_threshold.value;
RaisePropertyChanged(nameof(top_threshold));
}
if (p.enable_hedge.force_override)
{
enable_hedge = p.enable_hedge.value;
RaisePropertyChanged(nameof(enable_hedge));
}
if (p.hedge_ticks.force_override)
{
hedge_ticks = p.hedge_ticks.value;
RaisePropertyChanged(nameof(hedge_ticks));
}
_inline_props = p;
_validate();
})
);
_validate();
}
public override void assign_to(CustomOrderDefinition value)
{
if (_child_order == null)
_select_child_order(model.factory.default_order_type(_security.Value, true));
if (_hedge_order == null && enable_hedge)
_select_hedge_order(model.factory.default_order_type(_security.Value, true));
_validate();
if (!HasErrors)
{
var i = factory.top_order();
i.WatchLevelTickOffset = watch_level_tick_offset;
i.TopThreshold = top_threshold;
i.EnableHedge = enable_hedge;
i.HedgeTicks = hedge_ticks;
child_order.assign_to(i.Child);
if (enable_hedge && hedge_order != null)
hedge_order.assign_to(i.Hedge);
value.Top = i;
}
}
public ICollection<IOrderTypeVM> child_order_types
{
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _child_order_choices;
}
}
public ICollection<IOrderTypeVM> hedge_order_types
{
get
{
if (_hedge_order_choices == null)
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
return _hedge_order_choices;
}
}
public IOrderTypeVM child_order
{
get
{
init();
return _child_order;
}
set
{
Disposable.dispose(ref _child_order_errors_sub);
_child_order = value;
_child_order?.init();
if (_child_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(child_order));
}
}
public IOrderTypeVM hedge_order
{
get
{
init();
return _hedge_order;
}
set
{
Disposable.dispose(ref _hedge_order_errors_sub);
_hedge_order = value;
_hedge_order?.init();
if (_hedge_order is INotifyDataErrorInfo ndei)
{
ndei.ErrorsChanged += on_errors_changed;
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
_validate();
_hedge_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
}
_validate();
RaisePropertyChanged(nameof(hedge_order));
}
}
public void Dispose()
{
Disposable.dispose(ref _dispose);
if (_child_order_choices != null)
_child_order_choices.OfType<IDisposable>().DisposeAll();
if (_hedge_order_choices != null)
_hedge_order_choices.OfType<IDisposable>().DisposeAll();
}
public override void init()
{
if (_child_order == null && _security.HasValue)
_select_child_order();
if (_hedge_order == null && _security.HasValue && enable_hedge)
_select_hedge_order();
_validate();
}
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
void _select_child_order(CustomOneofCase ot)
{
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_child_order == null)
_child_order = child_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(child_order));
}
void _select_hedge_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
_inline_props.selected_hedge_order_type.value :
model.factory.default_order_type(_security.Value, true);
_select_hedge_order(ot);
}
void _select_hedge_order(CustomOneofCase ot)
{
_hedge_order = hedge_order_types.FirstOrDefault(vm => vm.order_type == ot);
if (_hedge_order == null)
_hedge_order = hedge_order_types.FirstOrDefault();
RaisePropertyChanged(nameof(hedge_order));
}
void _validate()
{
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
set_error(nameof(child_order), string.Empty, true);
else clear_error(nameof(child_order));
if ((_hedge_order == null && enable_hedge) || (_hedge_order is INotifyDataErrorInfo hdei && hdei.HasErrors))
set_error(nameof(hedge_order), string.Empty, true);
else clear_error(nameof(hedge_order));
}
}
}
}
@@ -199,10 +199,10 @@ namespace mbts.ui {
public static BTSColor straddle_theovsmarket_positive { get { return BTSCoreColors.green_light; } }
// Order Types
public static BTSColor tick_order { get { return new BTSColor(31, 4, 57);}}
public static BTSColor lean_order { get { return new BTSColor(110, 77, 143);}}
public static BTSColor join_order { get { return new BTSColor(15, 1, 29);}}
public static BTSColor leader_order { get { return new BTSColor(40, 9, 72);}}
public static BTSColor tick_order { get { return BTSColors.purple.rgb_color();}}
public static BTSColor lean_order { get { return BTSColors.purple.rgb_color();}}
public static BTSColor join_order { get { return BTSColors.purple.rgb_color();}}
public static BTSColor top_order { get { return BTSColors.purple.rgb_color();}}
}
public static class AW2 {
@@ -26,7 +26,7 @@
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding Mode, RelativeSource={RelativeSource TemplatedParent}}"
/>
<Grid x:Name="PeriodRoot" Grid.Row="1" Margin="0,6,0,0" IsEnabled="False">
<Grid x:Name="PeriodRoot" Grid.Row="1" Margin="24,6,0,0" IsEnabled="False">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="4"/>
@@ -73,8 +73,9 @@ namespace mbts.ui.controls {
public enum TimeFilterMode {
Anytime,
Today,
[Description("Period:")]
Period,
[Description("Glbx Open")]
[Description("Since Open")]
CMEOpen
}
}
@@ -10,6 +10,7 @@
<ResourceDictionary Source="/view/Controls/PriceEntryControl.xaml"/>
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
<ResourceDictionary Source="/view/Controls/Time/TimeSpanControl.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/QuantityMeasureControl.xaml"/>
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="uag:OrderDetailsPopupControl">
@@ -155,6 +156,186 @@
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.TimeBoxOrderTemplate>
<usp:OrderModificationTemplateSelector.TickOrderTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" VerticalAlignment="Center">Cross Thresh</TextBlock>
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding cross_threshold}"/>
<TextBlock Grid.Row="3" VerticalAlignment="Center">Cross Ticks</TextBlock>
<c:TickUpDownUInt Grid.Row="3" Grid.Column="2" Value="{Binding cross_ticks}"/>
<TextBlock Grid.Row="5" VerticalAlignment="Center">Max Chase Ticks</TextBlock>
<c:TickUpDownUInt Grid.Row="5" Grid.Column="2" Value="{Binding max_chase_ticks}"/>
<CheckBox Grid.Row="7" Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,0" IsChecked="{Binding detach_on_toward}">Detach on Toward</CheckBox>
<TextBlock Grid.Row="9" VerticalAlignment="Center">Initial Tick Offset</TextBlock>
<c:TickUpDownUInt Grid.Row="9" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
<TextBlock Grid.Row="11">Child</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="11" Grid.Column="2"
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding child_order_types}"
SelectedItem="{Binding child_order}"
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.TickOrderTemplate>
<usp:OrderModificationTemplateSelector.LeanOrderTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" VerticalAlignment="Center">Cross Thresh</TextBlock>
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding cross_threshold}"/>
<TextBlock Grid.Row="3" VerticalAlignment="Center">Max Chase Ticks</TextBlock>
<c:TickUpDownUInt Grid.Row="3" Grid.Column="2" Value="{Binding max_chase_ticks}"/>
<TextBlock Grid.Row="5">Child</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="5" Grid.Column="2"
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding child_order_types}"
SelectedItem="{Binding child_order}"
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.LeanOrderTemplate>
<usp:OrderModificationTemplateSelector.JoinOrderTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" VerticalAlignment="Center">Join Thresh</TextBlock>
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding join_threshold}"/>
<CheckBox Grid.Row="3" Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,0" IsChecked="{Binding enable_hedge}">Enable Hedge</CheckBox>
<TextBlock Grid.Row="5" VerticalAlignment="Center" IsEnabled="{Binding enable_hedge}" >Hedge Ticks</TextBlock>
<c:TickUpDownInt Grid.Row="5" Grid.Column="2" IsEnabled="{Binding enable_hedge}" Value="{Binding hedge_ticks}"/>
<TextBlock Grid.Row="7">Hedge</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="7" Grid.Column="2"
IsEnabled="{Binding enable_hedge}"
IsOpen="{Binding hedge_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding hedge_order_types}"
SelectedItem="{Binding hedge_order}"
HasEditableProperties="{Binding hedge_order.has_editable_properties, FallbackValue=False}"
/>
<TextBlock Grid.Row="9" VerticalAlignment="Center">Initial Tick Offset</TextBlock>
<c:TickUpDownUInt Grid.Row="9" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
<TextBlock Grid.Row="11">Child</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="11" Grid.Column="2"
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding child_order_types}"
SelectedItem="{Binding child_order}"
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.JoinOrderTemplate>
<usp:OrderModificationTemplateSelector.TopOrderTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="4" />
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" VerticalAlignment="Center">Top Thresh</TextBlock>
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding top_threshold}"/>
<CheckBox Grid.Row="3" Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,0" IsChecked="{Binding enable_hedge}">Enable Hedge</CheckBox>
<TextBlock Grid.Row="5" VerticalAlignment="Center" IsEnabled="{Binding enable_hedge}" >Hedge Ticks</TextBlock>
<c:TickUpDownInt Grid.Row="5" Grid.Column="2" IsEnabled="{Binding enable_hedge}" Value="{Binding hedge_ticks}"/>
<TextBlock Grid.Row="7">Hedge</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="7" Grid.Column="2"
IsEnabled="{Binding enable_hedge}"
IsOpen="{Binding hedge_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding hedge_order_types}"
SelectedItem="{Binding hedge_order}"
HasEditableProperties="{Binding hedge_order.has_editable_properties, FallbackValue=False}"
/>
<TextBlock Grid.Row="9" VerticalAlignment="Center">Watch Levels</TextBlock>
<c:TickUpDownUInt Grid.Row="9" Grid.Column="2" Value="{Binding watch_level_tick_offset}"/>
<TextBlock Grid.Row="11">Child</TextBlock>
<uag:OrderDetailsPopupControl
Grid.Row="11" Grid.Column="2"
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
ItemsSource="{Binding child_order_types}"
SelectedItem="{Binding child_order}"
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.TopOrderTemplate>
<usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
<DataTemplate>
<Grid>
@@ -180,7 +361,7 @@
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
</usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
<usp:OrderModificationTemplateSelector.SpreaderTemplate>
<DataTemplate>
<usp:ExecutionStrategyEditor
@@ -13,9 +13,10 @@ using System.Windows.Input;
using System.Windows.Media;
namespace mbts.spark {
class LadderOrderVM : DisposableViewModelBase, IEquatable<LadderOrderVM> {
class LadderOrderVM : DisposableViewModelBase {
#region Colors
static readonly Color _colorbg = Colors.White, _darkbg = Color.FromRgb(0xbf, 0xbf, 0xbf);
static readonly Brush _color_bg_brush = _colorbg.rgb_color().rgb_brush();
static Brush mkbrush(Color color, Color endcolor, double pct_complete) {
GradientStopCollection stops = new GradientStopCollection();
stops.Add(new GradientStop(color, 0.0));
@@ -34,7 +35,8 @@ namespace mbts.spark {
readonly Property<FontFamily> _font_family;
readonly Property<FontWeight> _font_weight;
readonly Property<double> _font_size;
readonly Property<Brush> _foreground;
readonly Property<Brush> _foreground, _background, _hoverbackground, _border;
readonly Property<string> _summary, _description;
IObservable<LadderSettings.BadgeStyle> _bs;
LadderSettings.BadgeStyle _current_badge_style;
int _n_active_price_change_ops = 0;
@@ -56,6 +58,11 @@ namespace mbts.spark {
_font_weight = backing(nameof(font_weight), parent.settings, get_font_weight, get_font_weight(fs));
_font_size = backing(nameof(font_size), parent.settings, get_font_size, get_font_size(fs));
_foreground = backing(nameof(foreground), parent.settings, get_foreground, get_foreground(fs));
_background = backing(nameof(background), _color_bg_brush);
_hoverbackground = backing(nameof(hoverbackground), _color_bg_brush);
_border = backing(nameof(border), _color_bg_brush);
_summary = backing(nameof(summary), string.Empty);
_description = backing(nameof(description), string.Empty);
FontFamily get_font_family(LadderSettings s) => new FontFamily(s.orders_column.font_family);
FontWeight get_font_weight(LadderSettings s) => ui.utility.text.TextUtils.from_open_type_weight_safe(s.orders_column.font_weight);
double get_font_size(LadderSettings s) => s.orders_column.font_size;
@@ -68,13 +75,12 @@ namespace mbts.spark {
})
);
//
add_order(o);
}
void _init() {
public void update() {
// update brushes
Color color;
switch (side) {
@@ -94,6 +100,9 @@ namespace mbts.spark {
working_quantity = _parent_and_child_not_sharing_slot().Select(order => order.working_quantity()).Sum();
quantity = _parent_and_child_not_sharing_slot().Select(order => order.State.Quantity).Sum();
summary = _orders.Values.Select(order => order.short_description(_parent.security.ContractSpec)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
description = _orders.Values.Select(order => order.full_description(_parent.security)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
_set_badge_text();
}
@@ -101,7 +110,6 @@ namespace mbts.spark {
if (_orders.Count > 0) {
switch (_current_badge_style) {
case LadderSettings.BadgeStyle.Alternate:
//text = $"{_buy_sell_indicator}:{filled}
top_line = $"{_buy_sell_indicator}:{filled}";
bottom_line = $"W:{ working_quantity}";
break;
@@ -114,7 +122,10 @@ namespace mbts.spark {
public void add_order(OrderRecordImmutable new_order) {
_orders[new_order.Key] = new_order;
_init();
}
public void remove_order(uint order_key) {
_orders.Remove(order_key);
}
//public void update(OrderRecordImmutable neworder) {
@@ -127,6 +138,7 @@ namespace mbts.spark {
public uint security_key => _parent.security.Id;
public double price { get => double.IsNaN(_phantom_price) ? _real_price : _phantom_price; }
public double real_price => _real_price;
public Side side {
get {
var distinct_sides = _orders.Values.Select(o => o.side()).Distinct();
@@ -186,8 +198,6 @@ namespace mbts.spark {
}
}
public string summary => _orders.Values.Select(order => order.short_description(_parent.security.ContractSpec)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
public string description => _orders.Values.Select(order => order.full_description(_parent.security)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
public bool has_parent => _orders.Values.Any( order => order.has_parent());
public bool has_phantom_price => !double.IsNaN(_phantom_price);
@@ -204,10 +214,11 @@ namespace mbts.spark {
public FontWeight font_weight => _font_weight.Value;
public double font_size => _font_size.Value;
public Brush foreground => _foreground.Value;
public Brush background { get; private set; }
public Brush hoverbackground { get; private set; }
public Brush header_brush => _orders.Values.FirstOrDefault().Def.Side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask;
public Brush border { get; private set; }
public Brush background { get => _background.Value; private set => _background.Value = value; }
public Brush hoverbackground { get => _hoverbackground.Value; private set => _hoverbackground.Value = value; }
public Brush border { get => _border.Value; private set => _border.Value = value; }
public string summary { get => _summary.Value; private set => _summary.Value = value; }
public string description { get => _description.Value; private set => _description.Value = value; }
public IEnumerable<OrderRecordImmutable> orders => _orders.Values;
void _cancel_order() {
@@ -239,40 +250,8 @@ namespace mbts.spark {
}
}
public override bool Equals(object obj) => this.safe_equals(obj as LadderOrderVM, _non_null_equals);
public override int GetHashCode() {
// can't use aggregate_hash since OrderRecordImmutable is not a reference type
unchecked {
int hash = 19;
foreach (var o in _orders.Values) {
hash = hash * 31 + o.GetHashCode();
}
return hash;
}
}
public bool Equals(LadderOrderVM other) => this.safe_equals(other, _non_null_equals);
static bool _non_null_equals(LadderOrderVM x, LadderOrderVM y) {
// null checks already taken care of safe_equals
// same object ?
if (object.ReferenceEquals(x,y)) return true;
// if the Dicts have different count, we have different objects
if (x._orders.Count != y._orders.Count) return false;
// are the keys the same ?
foreach(uint kx in x._orders.Keys) {
if (!y._orders.ContainsKey(kx))
return false;
}
// I don't think we have to look at the Dictionary values
// since the keys originate from the OrderRecordImmutable.key
//foreach (uint kx in x._orders.Keys) {
// if (!x._orders[kx].Equals(y._orders[kx)
// return false;
//}
return true;
public override string ToString() {
return $"Price: {this.price} Order Count: {this.orders.Count()}";
}
}
}
+53 -22
View File
@@ -26,6 +26,7 @@ namespace mbts.spark {
readonly Property<Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
IWindowSettingsManager<LadderSettings> _settings_manager;
ulong _misc_time = 0;
Dictionary<double, LadderOrderVM> _slot_price_vs_orders = new Dictionary<double, LadderOrderVM>();
public LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager) {
_bus = bus;
@@ -62,46 +63,77 @@ namespace mbts.spark {
return dialog;
}, -500, -500);
var under_orders = new ObservableCollection<OrderRecordImmutable>();
orders = new ObservableCollection<LadderOrderVM>();
//new ObservableAutoWrapper<LadderOrderVM, OrderRecordImmutable>(under_orders, rec => new LadderOrderVM(rec, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style)));
_dispose = Disposable.Create(
settings_command,
order_properties_vm = new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(security), _settings_manager.subscribe_current().Select(s => new InlineOrderProperties(model.order_gateway, s))),
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
bus.subscribe(SparkTags.LogActivity).ObserveOnDispatcher().Subscribe(status_update),
// need to subscribe to all orders, and cache them, because parent orders don't necessarily have the same security
// and OrderVM needs access to parent orders, if there are any
model.order_gateway.working_orders.ObserveOnDispatcher().Subscribe(working => {
under_orders.sync_with(working.Values.Where(o => o.Def.SecurityKey == security.Id && o.is_non_terminal()));
// Here we manage N orders to 1 price slot in the ladder
// there is likely a smarter way to do this than creating this anew each time
// need to do something about stop and stop limits in here
var slot_price_vs_orders = new Dictionary<double, LadderOrderVM>();
foreach(var uo in under_orders) {
var slot_price_target = uo.price();
var order_type = uo.order_type();
var filled = uo.filled();
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>();
var ladders_orders = working.Values.Where(o => o.Def.SecurityKey == security.Id);
foreach (var wo in ladders_orders) {
var slot_price_target = wo.price();
var order_type = wo.order_type();
var filled = wo.filled();
if (order_type == CustomOrderDefinition.CustomOneofCase.Stop) {
// if the stop order has been submitted but hasn't been triggered
// we draw the badge at the stop/trigger price, else the price price
if (filled == 0) { slot_price_target = uo.stop_price(); }
if (filled == 0) { slot_price_target = wo.stop_price(); }
} else if (order_type == CustomOrderDefinition.CustomOneofCase.StopLimit) {
// consistent behavior as Stop order above.
if (filled == 0) { slot_price_target = uo.stop_limit_price(); }
if (filled == 0) { slot_price_target = wo.stop_limit_price(); }
}
slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm);
if (slot_vm != null) {
slot_vm.add_order(uo);
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
slot_vm.add_order(wo);
} else {
slot_vm = new LadderOrderVM(uo, slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
slot_vm = new LadderOrderVM(wo, slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
_slot_price_vs_orders[slot_price_target] = slot_vm;
}
slot_price_vs_orders[slot_price_target] = slot_vm;
// keep track of slot prices vs orders; we use this below to remove orders from the LadderOrderVMs
// that are not found in this callback run
if (!orders_used_in_this_update.TryGetValue(slot_price_target, out HashSet<uint> orders_at_level)) {
orders_at_level = new HashSet<uint>();
}
orders_at_level.Add(wo.Key);
orders_used_in_this_update[slot_price_target] = orders_at_level;
}
orders.sync_with(slot_price_vs_orders.Values);
orders_cache = working;
// remove any orders from the lovm that aren't in here
foreach(var kvp in orders_used_in_this_update) {
var slot_vm = _slot_price_vs_orders[kvp.Key];
foreach(var existing_order_key in slot_vm.orders.Select(o => o.Key).ToList()) {
if (!kvp.Value.Contains(existing_order_key)) {
slot_vm.remove_order(existing_order_key);
}
}
}
// update dirty brushes and text
foreach (var lovm in _slot_price_vs_orders.Values) {
lovm.update();
}
// complete removal of slots that were not updated
// orders_used_in_this_update.Keys are the price levels in the update
foreach (var slot_price in _slot_price_vs_orders.Keys.ToList()) {
if (!orders_used_in_this_update.Keys.Contains(slot_price)) {
_slot_price_vs_orders.TryGetValue(slot_price, out LadderOrderVM removal_candidate);
if (removal_candidate != null) {
removal_candidate.Dispose();
}
_slot_price_vs_orders.Remove(slot_price);
}
}
orders.sync_with(_slot_price_vs_orders.Values);
}),
WorkLoop.Start($"Ladder '{security}'", _tick)
);
@@ -152,7 +184,6 @@ namespace mbts.spark {
public Brush ask_foreground => _ask_foreground.Value;
public DialogCommand settings_command { get; }
internal LadderModel model => _model;
internal IReadOnlyDictionary<uint, OrderRecordImmutable> orders_cache { get; private set; } = new Dictionary<uint, OrderRecordImmutable>();
internal IObservable<LadderSettings> settings => _settings_manager.subscribe_current();
public LadderSettings get_save_state() {
@@ -227,6 +227,41 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SettingsButton" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center">Settings</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{mbts:BTSColor UI.Button.normal_border}"
Background="{mbts:BTSColor UI.Button.normal_background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True"
VerticalAlignment="Bottom"
Margin="0,0,0,12">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="{mbts:BTSColor UI.Button.mouseover_background}" />
<Setter TargetName="Border" Property="BorderBrush" Value="{mbts:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
@@ -371,16 +406,7 @@
<Button Command="{Binding cancel_command}" CommandParameter="Asks" Style="{StaticResource CancelSells}"/>
<Button Command="{Binding cancel_command}" CommandParameter="All" Style="{StaticResource CancelAll}"/>
</StackPanel>
<Button Grid.Row="1" HorizontalContentAlignment="Stretch" VerticalAlignment="Bottom" Margin="5,12,5,16" Command="{Binding settings_command}" Padding="4,0" Style="{StaticResource LadderMenuButton}" ToolTip="Settings">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Height="15" SnapsToDevicePixels="True" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Source="{x:Static mbts:Icons.ViewSettings}" RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="6,2">Settings</TextBlock>
</Grid>
</Button>
<Button Grid.Row="1" HorizontalContentAlignment="Stretch" Command="{Binding settings_command}" Style="{StaticResource SettingsButton}" />
</Grid>
</Grid>
<Border Grid.Column="2" HorizontalAlignment="Stretch">
@@ -258,7 +258,6 @@ namespace mbts.spark {
RaisePropertyChanged(p);
foreach (var child_row in _child_rows)
child_row.on_parent_updated();
_row_passes_filter = _book_vm.columns.filter(this);
row_passes_filter = _book_vm.columns.filter(this);
}
@@ -28,6 +28,10 @@ namespace mbts.spark.utilities {
CustomOrderDefinition.CustomOneofCase.StopLimit,
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.TimeBox,
CustomOrderDefinition.CustomOneofCase.Tick,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Top,
}, _spreader_types = new[] {
CustomOrderDefinition.CustomOneofCase.Spreader,
CustomOrderDefinition.CustomOneofCase.Iceberg,
@@ -188,6 +192,30 @@ namespace mbts.spark.utilities {
Start = Timestamp.FromDateTime(DateTime.UtcNow),
Stop = Timestamp.FromDateTime(DateTime.UtcNow)
};
public TickOrderDefinition tick_order() => new TickOrderDefinition
{
Child = custom_order(),
CrossThreshold = new QuantityMeasure(),
};
public LeanOrderDefinition lean_order() => new LeanOrderDefinition
{
Child = custom_order(),
CrossThreshold = new QuantityMeasure(),
};
public JoinOrderDefinition join_order() => new JoinOrderDefinition
{
Child = custom_order(),
JoinThreshold = new QuantityMeasure(),
Hedge = custom_order(),
};
public TopOrderDefinition top_order() => new TopOrderDefinition
{
Child = custom_order(),
TopThreshold = new QuantityMeasure(),
Hedge = custom_order(),
};
public SpreaderOrderDefinition spreader_order() => new SpreaderOrderDefinition { ExecutionStrategy = new ExecutionStrategy() };
public CustomOrderDefinition custom_order(CustomOrderDefinition.CustomOneofCase order_type) {
@@ -199,6 +227,10 @@ namespace mbts.spark.utilities {
case CustomOrderDefinition.CustomOneofCase.StopLimit: rv.StopLimit = stop_limit_order(); break;
case CustomOrderDefinition.CustomOneofCase.Iceberg: rv.Iceberg = iceberg_order(); break;
case CustomOrderDefinition.CustomOneofCase.TimeBox: rv.TimeBox = time_box_order(); break;
case CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = tick_order(); break;
case CustomOrderDefinition.CustomOneofCase.Lean: rv.Lean = lean_order(); break;
case CustomOrderDefinition.CustomOneofCase.Join: rv.Join = join_order(); break;
case CustomOrderDefinition.CustomOneofCase.Top: rv.Top = top_order(); break;
case CustomOrderDefinition.CustomOneofCase.Spreader: rv.Spreader = spreader_order(); break;
}
return rv;
@@ -210,10 +242,14 @@ namespace mbts.spark.utilities {
switch (order_type) {
case CustomOrderDefinition.CustomOneofCase.Market: return new OrderMetadata { KeyBinding = "M", Color = BTSColors.yellow.rgb_color(), NoProps = true };
case CustomOrderDefinition.CustomOneofCase.Limit: return new OrderMetadata { KeyBinding = "L", Color = BTSColors.green.rgb_color(), NoProps = true };
case CustomOrderDefinition.CustomOneofCase.Stop: return new OrderMetadata { KeyBinding = "S", Color = BTSColors.orange.rgb_color(), NoProps = true };
case CustomOrderDefinition.CustomOneofCase.Stop: return new OrderMetadata { KeyBinding = "S", Color = BTSColors.red_orange.rgb_color(), NoProps = true };
case CustomOrderDefinition.CustomOneofCase.StopLimit: return new OrderMetadata { KeyBinding = "SL", Color = BTSColors.pale_green.rgb_color() };
case CustomOrderDefinition.CustomOneofCase.Iceberg: return new OrderMetadata { KeyBinding = "I", Color = BTSColors.pale_blue.rgb_color() };
case CustomOrderDefinition.CustomOneofCase.TimeBox: return new OrderMetadata { KeyBinding = "TB", Color = BTSColors.purple.rgb_color() };
case CustomOrderDefinition.CustomOneofCase.Tick: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.tick_order };
case CustomOrderDefinition.CustomOneofCase.Lean: return new OrderMetadata { KeyBinding = "L", Color = BTSColors.Orders.lean_order };
case CustomOrderDefinition.CustomOneofCase.Join: return new OrderMetadata { KeyBinding = "J", Color = BTSColors.Orders.join_order };
case CustomOrderDefinition.CustomOneofCase.Top: return new OrderMetadata { KeyBinding = "D", Color = BTSColors.Orders.top_order };
case CustomOrderDefinition.CustomOneofCase.Spreader: goto default;
case CustomOrderDefinition.CustomOneofCase.None: goto default;
default: return _default_order_metadata;
@@ -223,10 +259,14 @@ namespace mbts.spark.utilities {
switch (order_type) {
case OrderState.CustomOneofCase.Market: return new OrderMetadata { KeyBinding = "M", Color = BTSColors.yellow.rgb_color(), NoProps = true };
case OrderState.CustomOneofCase.Limit: return new OrderMetadata { KeyBinding = "L", Color = BTSColors.green.rgb_color(), NoProps = true };
case OrderState.CustomOneofCase.Stop: return new OrderMetadata { KeyBinding = "S", Color = BTSColors.orange.rgb_color() };
case OrderState.CustomOneofCase.Stop: return new OrderMetadata { KeyBinding = "S", Color = BTSColors.red_orange.rgb_color() };
case OrderState.CustomOneofCase.StopLimit: return new OrderMetadata { KeyBinding = "SL", Color = BTSColors.pale_green.rgb_color() };
case OrderState.CustomOneofCase.Iceberg: return new OrderMetadata { KeyBinding = "I", Color = BTSColors.pale_blue.rgb_color() };
case OrderState.CustomOneofCase.TimeBox: return new OrderMetadata { KeyBinding = "TB", Color = BTSColors.purple.rgb_color() };
case OrderState.CustomOneofCase.Tick: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.tick_order };
case OrderState.CustomOneofCase.Lean: return new OrderMetadata { KeyBinding = "L", Color = BTSColors.Orders.lean_order };
case OrderState.CustomOneofCase.Join: return new OrderMetadata { KeyBinding = "J", Color = BTSColors.Orders.join_order };
case OrderState.CustomOneofCase.Top: return new OrderMetadata { KeyBinding = "D", Color = BTSColors.Orders.top_order };
case OrderState.CustomOneofCase.Spreader: goto default;
case OrderState.CustomOneofCase.None: goto default;
default: return _default_order_metadata;
@@ -258,6 +298,10 @@ namespace mbts.spark.utilities {
case nameof(IcebergOrderDefinition): return CustomOrderDefinition.CustomOneofCase.Iceberg;
case nameof(SpreaderOrderDefinition): return CustomOrderDefinition.CustomOneofCase.Spreader;
case nameof(TimeBoxOrderDefinition): return CustomOrderDefinition.CustomOneofCase.TimeBox;
case nameof(TickOrderDefinition): return CustomOrderDefinition.CustomOneofCase.Tick;
case nameof(LeanOrderDefinition): return CustomOrderDefinition.CustomOneofCase.Lean;
case nameof(JoinOrderDefinition): return CustomOrderDefinition.CustomOneofCase.Join;
case nameof(TopOrderDefinition): return CustomOrderDefinition.CustomOneofCase.Top;
default: throw new ArgumentException($"Unable to match '{order_type_def?.Descriptor.Name}' to an order definition type");
}
}
-3
View File
@@ -20,9 +20,6 @@ namespace sparkcmd
if (args.FirstOrDefault() == "usertest") {
user_test();
}
else if (args.FirstOrDefault() == "service") {
new ServiceDriver().run(args.Skip(1));
}
else if (args.FirstOrDefault() == "synthetic") {
synthetic_test();
}
-77
View File
@@ -1,77 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using bts.api.admin;
using Newtonsoft.Json;
namespace sparkcmd
{
class ServiceDriver
{
string menu(string prompt, params string[] options) {
while (true) {
Console.WriteLine();
Console.WriteLine(prompt);
for (int x = 0; x < options.Length; ++x) {
Console.WriteLine($"{x}. {options[x]}");
}
var k = Console.ReadKey();
Console.WriteLine();
if (Int32.TryParse(k.KeyChar.ToString(), out int c) &&
c >= 0 && c < options.Length) {
return options[c];
}
}
}
string prompt(string prompt, int indent = 0) {
Console.Write(new String(' ', indent));
Console.Write(prompt);
return Console.ReadLine();
}
T prompt2<T>(string msg, Func<string,T> andthen) {
while(true) {
var str = prompt(msg);
try {
return andthen(str);
}catch(Exception e) {
Console.WriteLine(e);
Console.WriteLine();
}
}
}
T json_value_prompt<T>() {
T value = default;
do {
var serialized = prompt($"Enter {typeof(T).Name} as json: ");
serialized = serialized.Trim(' ', '\t', '\r', '\n');
if (serialized[0] != '{') serialized = "{" + serialized;
if (serialized.Last() != '}') serialized += "}";
try {
value = JsonConvert.DeserializeObject<T>(serialized);
} catch(Exception e) { Console.WriteLine(e); }
} while(value == null);
return value;
}
public void run(IEnumerable<string> args) {
// input service name
//
var service_name = args.FirstOrDefault() ?? prompt("Service Name: ");
var service_type = Type.GetType(service_name);
// List methods
var service_methods = service_type.GetMethods().Select(md => md.Name).ToArray();
menu("Which method do you want to call?", service_methods);
// var ps = json_value_prompt<SecurityScope>();
// Console.WriteLine(ps.ToString());
}
}
}
+6 -5
View File
@@ -1,4 +1,5 @@
#!/usr/bin/python
from __future__ import print_function
import os, sys
from collections import namedtuple
@@ -30,8 +31,8 @@ import apioptions_pb2
def loadTemplate(lang, name):
path = os.path.join("templates", lang, name)
text = open(path).read()
utext = unicode(text, "utf-8")
return compiler.compile(utext)
#utext = unicode(text, "utf-8")
return compiler.compile(text)
def option(options, key, default=None):
if options.HasExtension(key):
@@ -307,7 +308,7 @@ if __name__ == "__main__":
if DEBUG:
data = open("data", mode='rb').read()
else:
data = sys.stdin.read()
data = sys.stdin.buffer.read()
request = plugin.CodeGeneratorRequest()
# open("data", 'wb').write(data)
request.ParseFromString(data)
@@ -316,8 +317,8 @@ if __name__ == "__main__":
if DEBUG:
for f in response.file:
print f.content
print(f.content)
else:
output = response.SerializeToString()
sys.stdout.write(output)
sys.stdout.buffer.write(output)
+18 -3
View File
@@ -64,9 +64,13 @@ message CustomOrderDefinition
IcebergOrderDefinition iceberg = 15;
SpreaderOrderDefinition spreader = 16;
TimeBoxOrderDefinition time_box = 17;
TickOrderDefinition tick = 18;
LeanOrderDefinition lean = 19;
JoinOrderDefinition join = 20;
TopOrderDefinition top = 21;
}
}
// Custom order definitions
message MarketOrderDefinition {}
message LimitOrderDefinition {}
@@ -76,6 +80,11 @@ message IcebergOrderDefinition {double show_quantity = 1; CustomOrderDefinition
message SpreaderOrderDefinition {api.Security security = 1; spark.ExecutionStrategy execution_strategy = 2;}
message TimeBoxOrderDefinition {google.protobuf.Timestamp start = 1; google.protobuf.Timestamp stop = 2;CustomOrderDefinition child = 3;}
message TickOrderDefinition {uint32 tick_offset_on_deploy = 1; spark.QuantityMeasure cross_threshold = 2; uint32 cross_ticks = 3; uint32 max_chase_ticks = 4; bool detach_on_toward = 5; CustomOrderDefinition child = 6; }
message LeanOrderDefinition {spark.QuantityMeasure cross_threshold = 1; uint32 max_chase_ticks = 2; CustomOrderDefinition child = 3; }
message JoinOrderDefinition {uint32 tick_offset_on_deploy = 1; spark.QuantityMeasure join_threshold = 2; bool enable_hedge = 3; CustomOrderDefinition hedge = 4; int32 hedge_ticks = 5; CustomOrderDefinition child = 6;}
message TopOrderDefinition {uint32 watch_level_tick_offset = 1; spark.QuantityMeasure top_threshold = 2; bool enable_hedge = 3; CustomOrderDefinition hedge = 4; int32 hedge_ticks = 5; CustomOrderDefinition child = 6;}
message OrderRecord {
uint32 key = 1 [(bts.api.key)=true];
uint32 parent_key = 2;
@@ -106,6 +115,10 @@ message OrderState {
IcebergOrderState iceberg = 13;
SpreaderOrderState spreader = 14;
TimeBoxOrderState time_box = 15;
TickOrderState tick = 17;
LeanOrderState lean = 18;
JoinOrderState join = 19;
TopOrderState top = 20;
}
}
@@ -158,6 +171,10 @@ message StopLimitOrderState {NativeOrderState native_state = 1;}
message IcebergOrderState {}
message SpreaderOrderState {SpreaderChildKind child_kind = 1;}
message TimeBoxOrderState {}
message TickOrderState {}
message LeanOrderState {}
message JoinOrderState {}
message TopOrderState {}
enum OrderStatus {
@@ -199,8 +216,6 @@ enum TimeInForce {
TIME_IN_FORCE_UNDEFINED = 0;
TIME_IN_FORCE_DAY = 48;
TIME_IN_FORCE_GTC = 49;
TIME_IN_FORCE_OPG = 50;
TIME_IN_FORCE_IOC = 51;
}
enum ManualOrderIndicator {
+1 -1
View File
@@ -77,7 +77,7 @@ message JetFIX {
optional string trading_session_id = 330 [default=""];
optional int32 udf_support_indicator = 340 [default=1];
optional bool use_cme_secure_logon = 341 [default=false];
optional bool use_cme_secure_logon = 341 [default=true];
optional bool auto_cert_mode = 342 [default=false];
}
@@ -192,7 +192,7 @@ void ExchangeSimApplication::onMessage(const FIX42::NewOrderSingle &message,
message.get(ordType);
if (ordType == FIX::OrdType_LIMIT)
message.get(price);
message.get(orderQty);
message.getFieldIfSet(timeInForce);
@@ -206,7 +206,7 @@ void ExchangeSimApplication::onMessage(const FIX42::NewOrderSingle &message,
auto &matcher = me->matchers[sec_desc];
if (matcher.get_order(clOrdID))
throw std::logic_error("Duplicate order");
ex_sim::Order order;
order.set_clordid(clOrdID);
order.set_sec_desc(sec_desc);
@@ -227,7 +227,7 @@ void ExchangeSimApplication::onMessage(const FIX42::NewOrderSingle &message,
else
throw new std::logic_error("No sell market");
} else {
if (book.bids.size())
order.set_price(book.bids.begin()->first.value());
else
@@ -283,9 +283,11 @@ void ExchangeSimApplication::onMessage(
try {
auto &matcher = me->matchers[sec_desc];
auto order = matcher.get_order(clOrdID);
if (!order)
if (!order) {
LOG(ExSim, Info, "Receive cancel/replace for unknown order {}",
clOrdID);
throw std::logic_error("Unknown order");
else {
} else {
auto order_cpy = *order;
order_cpy.set_price(price);
order_cpy.set_quantity(orderQty);
@@ -318,9 +320,11 @@ void ExchangeSimApplication::onMessage(const FIX42::OrderCancelRequest &message,
try {
auto &matcher = me->matchers[sec_desc];
auto order = matcher.get_order(origClOrdID);
if (!order)
if (!order) {
LOG(ExSim, Info, "Receive cancel for unknown order {}",
origClOrdID.getString());
throw std::logic_error("Unknown order");
else {
} else {
auto order_cpy = *order;
order_cpy.set_open_quantity(0);
updateOrder(order_cpy, FIX::OrdStatus_CANCELED);
@@ -439,20 +443,32 @@ void ExchangeSimApplication::update_md(std::string const &sec_desc) {
auto &handle = it->second;
auto &matcher = me->matchers[sec_desc];
auto order_book = matcher.get_order_book("SYNTH");
order_book.set_sec_desc(sec_desc);
persist_book(order_book);
auto book = matcher.get_book();
auto price_factor = pow(10, handle.security().display_factor);
const auto cmax = md::COCONUT_MAX_DEPTH;
std::array<md::PriceSize,cmax> bids_temp = { }, asks_temp = { };
auto bids_end = book.bids.size() > cmax ?
std::next(book.bids.begin(), cmax) : book.bids.end();
std::transform(book.bids.begin(), bids_end, bids_temp.begin(),
[&](const auto& bs) { return md::PriceSize::from_double<7>(bs.first.value()*price_factor, bs.second.value()); });
auto asks_end = book.asks.size() > cmax ?
std::next(book.asks.begin(), cmax) : book.asks.end();
std::transform(book.asks.begin(), asks_end, asks_temp.begin(),
[&](const auto& bs) { return md::PriceSize::from_double<7>(bs.first.value()*price_factor, bs.second.value()); });
std::array<md::PriceSize, cmax> bids_temp = {}, asks_temp = {};
auto bids_end = book.bids.size() > cmax
? std::next(book.bids.begin(), cmax)
: book.bids.end();
std::transform(book.bids.begin(), bids_end, bids_temp.begin(),
[&](const auto &bs) {
return md::PriceSize::from_double<7>(
bs.first.value() * price_factor, bs.second.value());
});
auto asks_end = book.asks.size() > cmax
? std::next(book.asks.begin(), cmax)
: book.asks.end();
std::transform(book.asks.begin(), asks_end, asks_temp.begin(),
[&](const auto &bs) {
return md::PriceSize::from_double<7>(
bs.first.value() * price_factor, bs.second.value());
});
handle.update_book(utils::TimeStamp::now(), bids_temp, asks_temp);
auto new_trades = matcher.get_new_trades();
@@ -588,7 +604,8 @@ void ExchangeSimApplication::merge_with_md_book(std::string const &sec_desc,
order_book.set_sec_desc(sec_desc);
persist_book(order_book);
auto sz = matcher.size();
LOG(ExSim,Diag,"Book size: {} {} {} {}",sec_desc,std::get<0>(sz),std::get<1>(sz),std::get<2>(sz));
LOG(ExSim, Diag, "Book size: {} {} {} {}", sec_desc, std::get<0>(sz),
std::get<1>(sz), std::get<2>(sz));
}
void ExchangeSimApplication::persist_book(OrderBook const &b) {
@@ -64,7 +64,7 @@ public:
// risk limit checks
////////////////////
Limiter::LimiterQueryResult can_send_new_order(api::Side side, Quantity quantity) const{
Limiter::LimiterQueryResult result;
Limiter::LimiterQueryResult result;result.ok=true;
if (quantity.value() > _clip_size){
result.ok = false;
result.error_msg = fmt::format("Security Clip Size (Max {} Qty {})", _clip_size, quantity.value());
@@ -12,6 +12,10 @@
#include "server/sparkd/orderman/orders/spreader_orders/SpreaderOrder.h"
#include "server/sparkd/orderman/orders/NativeOrder.h"
#include "server/sparkd/orderman/orders/TimeBoxOrder.h"
#include "server/sparkd/orderman/orders/book_algos/TickOrder.h"
#include "server/sparkd/orderman/orders/book_algos/LeanOrder.h"
#include "server/sparkd/orderman/orders/book_algos/JoinOrder.h"
#include "server/sparkd/orderman/orders/book_algos/TopOrder.h"
#include <limits>
#include <mutex>
@@ -120,6 +124,18 @@ Order &OrderFactory::allocate(OrderKey k, OrderKey pk,
case CustomOrderDefinition::kTimeBox:
order_ptr.reset (new orderman::TimeBoxOrder());
break;
case CustomOrderDefinition::kTick:
order_ptr.reset (new orderman::TickOrder());
break;
case CustomOrderDefinition::kLean:
order_ptr.reset (new orderman::LeanOrder());
break;
case CustomOrderDefinition::kJoin:
order_ptr.reset (new orderman::JoinOrder());
break;
case CustomOrderDefinition::kTop:
order_ptr.reset (new orderman::TopOrder());
break;
case CustomOrderDefinition::CUSTOM_NOT_SET:
bts_assert(false);
break;
@@ -78,10 +78,7 @@ void NativeOrder::on_init_new(OrderDefinition const &def,
case CustomOrderDefinition::kStopLimit:
me->state.mutable_stop_limit();
break;
case CustomOrderDefinition::kIceberg:
case CustomOrderDefinition::kSpreader:
case CustomOrderDefinition::kTimeBox:
case CustomOrderDefinition::CUSTOM_NOT_SET:
default:
bts_assert(false);
break;
}
@@ -343,10 +340,7 @@ void NativeOrder::on_activate(Price p, Quantity q,
case CustomOrderDefinition::kStop:
case CustomOrderDefinition::kMarket:
break;
case CustomOrderDefinition::kIceberg:
case CustomOrderDefinition::kSpreader:
case CustomOrderDefinition::kTimeBox:
case CustomOrderDefinition::CUSTOM_NOT_SET:
default:
bts_assert(false);
break;
}
@@ -829,10 +823,7 @@ void NativeOrder::init_fix() {
me->update_price_bm = update_encoding.add<fix::Price>(0, price_sz());
break;
}
case CustomOrderDefinition::kIceberg:
case CustomOrderDefinition::kSpreader:
case CustomOrderDefinition::kTimeBox:
case CustomOrderDefinition::CUSTOM_NOT_SET:
default:
bts_assert(false);
break;
}
@@ -975,10 +966,7 @@ NativeOrder::get_native_state(api::orderman::OrderState &st) {
return *st.mutable_stop()->mutable_native_state();
case OrderState::kStopLimit:
return *st.mutable_stop_limit()->mutable_native_state();
case OrderState::kIceberg:
case OrderState::kSpreader:
case OrderState::kTimeBox:
case OrderState::CUSTOM_NOT_SET:
default:
break;
}
@@ -0,0 +1,442 @@
#include "BookAlgo.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/helper_macros.h"
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
#include <google/protobuf/timestamp.pb.h>
namespace bts {
namespace orderman {
using namespace api::orderman;
struct BookAlgo::PImpl
{
PImpl() : services(nullptr), fake_detached_child(false),
supports_hedge(false)
{
}
OrderServices* services;
api::orderman::OrderDefinition def;
api::orderman::OrderState state;
OrderBuilder child_builder;
ChildOrder child;
api::orderman::OrderState child_state;
bool fake_detached_child;
bool supports_hedge;
OrderBuilder hedge_builder;
std::vector<ChildOrder> hedges;
using HedgeStateMap = boost::container::flat_map<OrderKey, api::orderman::OrderState>;
HedgeStateMap hedges_state;
double tick_size;
};
BookAlgo::BookAlgo() : me(new PImpl())
{
}
BookAlgo::~BookAlgo() {}
const api::orderman::OrderDefinition& BookAlgo::definition() const
{
return me->def;
}
const api::orderman::OrderState& BookAlgo::this_state() const
{
return me->state;
}
void BookAlgo::set_price(double p)
{
me->state.set_price(p);
}
void BookAlgo::set_quantity(uint32_t s)
{
me->state.set_quantity(s);
}
const api::orderman::OrderState& BookAlgo::child_state() const
{
return me->child_state;
}
void BookAlgo::set_child_price(double p)
{
me->child_state.set_price(p);
}
void BookAlgo::set_child_quantity(uint32_t s)
{
me->child_state.set_quantity(s);
}
void BookAlgo::activate_child(Price p, Quantity q)
{
me->child.activate(p, q);
}
void BookAlgo::update_child(Price p, Quantity q)
{
me->child.update(p, q);
}
void BookAlgo::cancel_child()
{
me->child.cancel();
}
void BookAlgo::build_and_send_hedge(Price p, Quantity q)
{
auto hedge = me->hedge_builder.build();
me->hedges.push_back(std::move(hedge));
me->hedges.back().activate(p, q);
}
double BookAlgo::tick_size() const
{
return me->tick_size;
}
void BookAlgo::on_init_new(const api::orderman::OrderDefinition& def,
const PreActivationOrderServices &services)
{
me->services = nullptr;
me->def = def;
me->child_state.set_status(ORDER_STATUS_INACTIVE);
OrderDefinition child_def = def;
setup_child(def, services, me->child_state, &child_def);
me->child_builder = OrderBuilder(key(), child_def, services, 1);
OrderDefinition hedge_def = def;
me->supports_hedge = setup_hedge(def, services, &hedge_def);
if(me->supports_hedge)
me->hedge_builder = OrderBuilder(key(), hedge_def, services, 4);
const bts::api::Security* sec = services.sec_db.resolve(SecurityKey(def.security_key()));
if(sec)
{
me->tick_size = sec->contract_spec().low_tick();
transition(ORDER_STATUS_INACTIVE);
}
else
{
auto msg = fmt::format("Unknown security: {}", def.security_key());
me->state.set_last_msg(msg);
transition(ORDER_STATUS_REJECTED_BY_SPARK, msg);
}
}
void BookAlgo::on_init_recovered(const api::orderman::OrderRecord& rec,
const PreActivationOrderServices &)
{
auto const &def = rec.def();
me->def = def;
me->state = rec.state();
switch (me->state.status())
{
case ORDER_STATUS_WORKING:
{
auto msg = "System restarted with active BookAlgo";
me->state.set_last_msg(msg);
transition(ORDER_STATUS_ERROR, msg);
break;
}
// terminal states
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
break;
// impossible to reach states for this order type
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_INACTIVE:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
}
void BookAlgo::on_activate(Price p, Quantity q, OrderServices& services)
{
bts_assert(!me->services);
me->services = &services;
switch (this_state().status())
{
case ORDER_STATUS_INACTIVE:
me->child_builder.activate(services);
if(me->supports_hedge)
me->hedge_builder.activate(services);
me->child = me->child_builder.build();
set_price(p.value());
set_quantity(q.value());
services.child_updates.sub(key(),
[this](auto const &os) { this->on_child_order_update(os); });
services.book_updates.sub(SecurityKey(me->def.security_key()),
[this](auto const &b) { this->on_book_update(b); });
transition(ORDER_STATUS_WORKING);
break;
// terminal states
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
break;
// impossible to reach states at this stage (on recovery from disk
// we would have transitioned to error if not already in a terminal
// state)
case ORDER_STATUS_WORKING:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_DISCONNECTED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
report_state();
}
void BookAlgo::on_update(Price p, Quantity q)
{
LOG(OrderMan, Info, "Updating BookAlgo {}", key().value());
switch (this_state().status())
{
case ORDER_STATUS_WORKING:
switch (child_state().status())
{
case ORDER_STATUS_INACTIVE:
set_price(p.value());
set_quantity(q.value());
break;
case ORDER_STATUS_WORKING:
set_price(p.value());
set_quantity(q.value());
if(!me->fake_detached_child)
me->child.update(p,q);
break;
case ORDER_STATUS_PENDING_WORKING:
// we launched our child but we are not yet able
// to update it
transition(this_state().status(), "Cannot update order");
break;
// if the child was in any of these states we couldn't be
// in the working state
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
break;
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
transition(this_state().status(), "Cannot update order");
break;
// these are all unreacheable for this order type at this point
case ORDER_STATUS_INACTIVE:
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_PENDING_WORKING:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
report_state();
}
void BookAlgo::on_crack() {}
void BookAlgo::on_cancel()
{
LOG(OrderMan, Info, "Canceling BookAlgo Order {}", key().value());
switch (this_state().status())
{
case ORDER_STATUS_WORKING:
switch (child_state().status())
{
case ORDER_STATUS_INACTIVE:
transition(ORDER_STATUS_CANCELED);
break;
case ORDER_STATUS_WORKING:
if(!me->fake_detached_child)
me->child.cancel();
for(auto& i : me->hedges)
i.crack();
transition(ORDER_STATUS_CANCELED);
break;
case ORDER_STATUS_PENDING_WORKING:
// child is running but it cannot be canceled at this point
transition(this_state().status(), "Cannot cancel order");
break;
// child cannot be in any of these if we are in the working state
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
break;
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
transition(this_state().status(), "Cannot cancel order");
break;
// these are unreacheable for this order type
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_INACTIVE:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
report_state();
}
void BookAlgo::transition(api::orderman::OrderStatus new_status, std::string msg)
{
auto const &from = OrderStatus_Name(me->state.status());
auto const &to = OrderStatus_Name(new_status);
LOG(OrderMan, Info, "Transitioning BookAlgo {} from {} to {}. Msg: {}", key().value(), from, to, msg);
me->state.set_status(new_status);
}
void BookAlgo::on_child_order_update(const OrderStateUpdateEvent& ev)
{
if(me->fake_detached_child)
return;
if(ev.key != me->child.key())
me->hedges_state[ev.key] = ev.state;
else
{
api::orderman::OrderState orig_state = child_state();
me->child_state = ev.state;
switch (this_state().status())
{
case ORDER_STATUS_WORKING:
switch (ev.state.status())
{
case ORDER_STATUS_PENDING_WORKING:
break;
case ORDER_STATUS_WORKING:
if(me->supports_hedge)
send_hedge_if_new_fills(orig_state, ev);
break;
case ORDER_STATUS_COMPLETED:
transition(ORDER_STATUS_COMPLETED);
if(me->supports_hedge)
send_hedge_if_new_fills(orig_state, ev);
break;
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
transition(ORDER_STATUS_ERROR, "Unexpected child order state");
break;
case ORDER_STATUS_INACTIVE:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
break;
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_COMPLETED:
break;
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_INACTIVE:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
}
report_state();
}
void BookAlgo::report_state()
{
auto merged_state = me->state;
merged_state.set_filled_quantity(child_state().filled_quantity());
merged_state.set_last_price(child_state().last_price());
merged_state.set_avg_price(child_state().avg_price());
me->services->report_state(merged_state);
}
void BookAlgo::payup(const md::PriceSize& opposite, uint32_t cross_ticks)
{
double new_price = opposite.price() + me->tick_size * cross_ticks;
me->child.update(Price(new_price), Quantity(this_state().quantity()));
report_state();
}
void BookAlgo::paydown(const md::PriceSize& opposite, uint32_t cross_ticks)
{
double new_price = opposite.price() - me->tick_size * cross_ticks;
me->child.update(Price(new_price), Quantity(this_state().quantity()));
report_state();
}
void BookAlgo::detach(bts::api::orderman::OrderStatus status_after_detach)
{
me->fake_detached_child = true;
me->child.crack();
transition(status_after_detach);
report_state();
}
void BookAlgo::on_book_update(const bts::orderman::BookUpdateEvent&) {}
bool BookAlgo::setup_hedge(const api::orderman::OrderDefinition&, const PreActivationOrderServices&, api::orderman::OrderDefinition*)
{
return false;
}
void BookAlgo::send_hedge_if_new_fills(const api::orderman::OrderState& orig_state, const OrderStateUpdateEvent& new_state)
{
UNUSED(orig_state);UNUSED(new_state);
// uint32_t qty = new_state.state.filled_quantity() - orig_state.filled_quantity();
// UNUSED(qty);
}
}
}
@@ -0,0 +1,68 @@
#pragma once
#include <memory>
#include <boost/container/flat_map.hpp>
#include "server/sparkd/orderman/orders/Order.h"
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
#include "lib/api/services/OrderService.server.pb.h"
namespace bts {
namespace orderman {
class OrderStateUpdateEvent;
class BookUpdateEvent;
//Book algo is a useful base class for implementing order algorithms that do things base on the on_book method
class BookAlgo : public Order
{
public:
BookAlgo();
virtual ~BookAlgo();
const api::orderman::OrderDefinition& definition() const override;
void on_init_new(const api::orderman::OrderDefinition&,
const PreActivationOrderServices&) override;
void on_init_recovered(const api::orderman::OrderRecord&,
const PreActivationOrderServices &) override;
void on_activate(Price, Quantity, OrderServices&) override;
void on_update(Price, Quantity) override;
void on_crack() override;
void on_cancel() override;
protected:
virtual void setup_child(const api::orderman::OrderDefinition& parent, const PreActivationOrderServices&,
api::orderman::OrderState&, api::orderman::OrderDefinition*) = 0;
virtual bool setup_hedge(const api::orderman::OrderDefinition& parent, const PreActivationOrderServices&,
api::orderman::OrderDefinition*);
virtual void send_hedge_if_new_fills(const api::orderman::OrderState&, const OrderStateUpdateEvent&);
virtual void on_child_order_update(const OrderStateUpdateEvent&);
virtual void on_book_update(const bts::orderman::BookUpdateEvent&);
virtual void transition(api::orderman::OrderStatus new_state, std::string = "");
virtual void report_state();
virtual void payup(const md::PriceSize&, uint32_t cross_ticks = 0);
virtual void paydown(const md::PriceSize&, uint32_t cross_ticks = 0);
void detach(bts::api::orderman::OrderStatus status_after_detach = bts::api::orderman::ORDER_STATUS_CANCELED);
private:
struct PImpl;
std::unique_ptr<PImpl> me;
protected:
const api::orderman::OrderState& this_state() const;
void set_price(double);
void set_quantity(uint32_t);
const api::orderman::OrderState& child_state() const;
void set_child_price(double);
void set_child_quantity(uint32_t);
void activate_child(Price p, Quantity q);
void update_child(Price, Quantity);
void cancel_child();
void build_and_send_hedge(Price, Quantity);
double tick_size() const;
};
}}
@@ -0,0 +1,185 @@
#include "JoinOrder.h"
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
#include "lib/utils/PBUtils.h"
#include <google/protobuf/timestamp.pb.h>
namespace bts {
namespace orderman {
using namespace api::orderman;
struct JoinOrder::PImpl
{
PImpl() {}
uint32_t tick_offset_on_deploy;
spark::QuantityMeasure join_threshold;
bool enable_hedge;
int hedge_ticks;
OrderDefinition hedge_def;
md::PriceSize opposite_at_inception;
bool did_join;
};
JoinOrder::JoinOrder() : me(new PImpl()) {}
JoinOrder::~JoinOrder() {}
void JoinOrder::setup_child(const api::orderman::OrderDefinition& this_def, const PreActivationOrderServices&,
api::orderman::OrderState& initial_state, api::orderman::OrderDefinition* out_child)
{
initial_state.mutable_join();
auto const &join_def = this_def.custom_def().join();
*out_child->mutable_custom_def() = join_def.child();
me->tick_offset_on_deploy = join_def.tick_offset_on_deploy();
me->join_threshold = join_def.join_threshold();
me->enable_hedge = join_def.enable_hedge();
me->hedge_ticks = join_def.hedge_ticks();
me->did_join = false;
}
bool JoinOrder::setup_hedge(const api::orderman::OrderDefinition& this_def, const PreActivationOrderServices&,
api::orderman::OrderDefinition* out_hedge)
{
auto const &join_def = this_def.custom_def().join();
if(me->enable_hedge && join_def.has_hedge())
{
*out_hedge->mutable_custom_def() = join_def.hedge();
if(definition().side() == api::SIDE_BUY)
out_hedge->set_side(api::SIDE_SELL);
else
out_hedge->set_side(api::SIDE_BUY);
return true;
}
else
return false;
}
void JoinOrder::send_hedge_if_new_fills(const api::orderman::OrderState& orig_state, const OrderStateUpdateEvent& new_state)
{
uint32_t qty = new_state.state.filled_quantity() - orig_state.filled_quantity();
if(qty > 0)
{
double prx = new_state.state.last_price();
if(definition().side() == api::SIDE_BUY) //if this is a buy, I'm going to sell my hedge, nticks higher than the fill price
prx = prx + tick_size() * me->hedge_ticks;
else
prx = prx - tick_size() * me->hedge_ticks;
build_and_send_hedge(Price(prx), Quantity(qty));
}
}
void JoinOrder::on_book_update(const bts::orderman::BookUpdateEvent& book)
{
switch (this_state().status())
{
case ORDER_STATUS_WORKING:
switch (child_state().status())
{
case ORDER_STATUS_INACTIVE:
{
if(definition().side() == api::SIDE_BUY)
{
me->opposite_at_inception = book.book.side(api::Side::SIDE_SELL)[0];
if(me->tick_offset_on_deploy > 0)
{
double price = me->opposite_at_inception.price() - tick_size() * me->tick_offset_on_deploy;
activate_child(Price(price), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
}
else
{
activate_child(Price(this_state().price()), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
}
}
else
{
me->opposite_at_inception = book.book.side(api::Side::SIDE_BUY)[0];
if(me->tick_offset_on_deploy > 0)
{
double price = me->opposite_at_inception.price() + tick_size() * me->tick_offset_on_deploy;
activate_child(Price(price), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
}
else
{
activate_child(Price(this_state().price()), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
}
}
report_state();
}
break;
case ORDER_STATUS_WORKING:
{
Quantity thresh = SpreaderUtils::_quantity_threshold(me->join_threshold, Quantity(this_state().quantity()));
if(definition().side() == api::SIDE_BUY)
{
const auto& same = book.book.side(api::Side::SIDE_BUY)[0];
if(me->opposite_at_inception.price() > same.price())
{}
//we join exactly once, but we can't detach because we have to hang around and look for hedges
else if(!me->did_join && same.size() >= thresh.value())
{
me->did_join = true;
update_child(Price(same.price()), Quantity(this_state().quantity()));
report_state();
}
}
else
{
const auto& same = book.book.side(api::Side::SIDE_SELL)[0];
if(me->opposite_at_inception.price() < same.price())
{}
else if(!me->did_join && same.size() >= thresh.value())
{
me->did_join = true;
update_child(Price(same.price()), Quantity(this_state().quantity()));
report_state();
}
}
}
break;
case ORDER_STATUS_PENDING_WORKING:
break;
// child cannot be in any of these states if we are in the working state
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
break;
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
break;
case ORDER_STATUS_INACTIVE:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_DISCONNECTED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
}
}
}
@@ -0,0 +1,34 @@
#pragma once
#include "server/sparkd/orderman/orders/book_algos/BookAlgo.h"
namespace bts {
namespace orderman {
class OrderStateUpdateEvent;
class BookUpdateEvent;
//JoinOrder starts working N ticks away from the market (usually) and then watches for
//the opposite inside to become same side inside market; if there is then enough size on that market, it will join.
//Fills optionally trigger hedge orders to the opposite side of the market, n ticks away.
class JoinOrder : public BookAlgo
{
public:
JoinOrder();
virtual ~JoinOrder();
protected:
void setup_child(const api::orderman::OrderDefinition&, const PreActivationOrderServices&,
api::orderman::OrderState&, api::orderman::OrderDefinition*) override;
bool setup_hedge(const api::orderman::OrderDefinition& parent, const PreActivationOrderServices&,
api::orderman::OrderDefinition*) override;
void send_hedge_if_new_fills(const api::orderman::OrderState&, const OrderStateUpdateEvent&) override;
void on_book_update(const bts::orderman::BookUpdateEvent&) override;
private:
struct PImpl;
std::unique_ptr<PImpl> me;
};
}}
@@ -0,0 +1,153 @@
#include "LeanOrder.h"
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
#include "lib/utils/PBUtils.h"
#include <google/protobuf/timestamp.pb.h>
namespace bts {
namespace orderman {
using namespace api::orderman;
struct LeanOrder::PImpl
{
PImpl() {}
spark::QuantityMeasure cross_threshold;
uint32_t max_chase_ticks;
md::PriceSize opposite_at_inception;
};
LeanOrder::LeanOrder() : me(new PImpl()) {}
LeanOrder::~LeanOrder() {}
void LeanOrder::setup_child(const api::orderman::OrderDefinition& this_def, const PreActivationOrderServices&,
api::orderman::OrderState& initial_state, api::orderman::OrderDefinition* out_child)
{
initial_state.mutable_lean();
auto const &lean_def = this_def.custom_def().lean();
*out_child->mutable_custom_def() = lean_def.child();
me->cross_threshold = lean_def.cross_threshold();
me->max_chase_ticks = lean_def.max_chase_ticks();
}
void LeanOrder::payup(const md::PriceSize& opposite, uint32_t cross_ticks)
{
double new_price = opposite.price() + tick_size() * cross_ticks;
double limit = me->opposite_at_inception.price() + tick_size() * me->max_chase_ticks;
if(new_price <= limit)
update_child(Price(new_price), Quantity(this_state().quantity()));
else
{
update_child(Price(limit), Quantity(this_state().quantity()));
detach();
}
report_state();
}
void LeanOrder::paydown(const md::PriceSize& opposite, uint32_t cross_ticks)
{
double new_price = opposite.price() - tick_size() * cross_ticks;
double limit = me->opposite_at_inception.price() - tick_size() * me->max_chase_ticks;
if(new_price >= limit)
update_child(Price(new_price), Quantity(this_state().quantity()));
else
{
update_child(Price(limit), Quantity(this_state().quantity()));
detach();
}
report_state();
}
void LeanOrder::on_book_update(const bts::orderman::BookUpdateEvent& book)
{
switch (this_state().status())
{
case ORDER_STATUS_WORKING:
switch (child_state().status())
{
case ORDER_STATUS_INACTIVE:
{
if(definition().side() == api::SIDE_BUY)
me->opposite_at_inception = book.book.side(api::Side::SIDE_SELL)[0];
else
me->opposite_at_inception = book.book.side(api::Side::SIDE_BUY)[0];
activate_child(Price(this_state().price()), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
report_state();
}
break;
case ORDER_STATUS_WORKING:
{
Quantity thresh = SpreaderUtils::_quantity_threshold(me->cross_threshold, Quantity(this_state().quantity()));
if(definition().side() == api::SIDE_BUY)
{
const auto& opposite = book.book.side(api::Side::SIDE_SELL)[0];
if(opposite.price() < me->opposite_at_inception.price())
me->opposite_at_inception = opposite; //your lucky day, try to get luckier
else if(opposite.price() > me->opposite_at_inception.price())
payup(opposite); //you missed, payup
else
{
if(opposite.size() <= thresh.value())
payup(opposite);
}
}
else
{
const auto& opposite = book.book.side(api::Side::SIDE_BUY)[0];
if(opposite.price() > me->opposite_at_inception.price())
me->opposite_at_inception = opposite;
else if(opposite.price() < me->opposite_at_inception.price())
paydown(opposite);
else
{
if(opposite.size() <= thresh.value())
paydown(opposite);
}
}
}
break;
case ORDER_STATUS_PENDING_WORKING:
break;
// child cannot be in any of these states if we are in the working state
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
break;
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
break;
case ORDER_STATUS_INACTIVE:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_DISCONNECTED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
}
}
}
@@ -0,0 +1,35 @@
#pragma once
#include "server/sparkd/orderman/orders/book_algos/BookAlgo.h"
namespace bts {
namespace orderman {
class OrderStateUpdateEvent;
class BookUpdateEvent;
//LeanOrder watches for the opposite inside to trade below a certain size.
//If size goes below a threshold or the market moves away, LeanOrder Pays up once.
//The process repeats until the algo has paid up a max of N ticks from the original opposite inside order.
//If the market moves toward, the algo starts watching the new inside market and keeps going
class LeanOrder : public BookAlgo
{
public:
LeanOrder();
virtual ~LeanOrder();
protected:
void setup_child(const api::orderman::OrderDefinition&, const PreActivationOrderServices&,
api::orderman::OrderState&, api::orderman::OrderDefinition*) override;
void on_book_update(const bts::orderman::BookUpdateEvent&) override;
private:
void payup(const md::PriceSize& opposite, uint32_t cross_ticks = 0) override;
void paydown(const md::PriceSize& opposite, uint32_t cross_ticks = 0) override;
private:
struct PImpl;
std::unique_ptr<PImpl> me;
};
}}
@@ -0,0 +1,192 @@
#include "TickOrder.h"
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
#include "lib/utils/PBUtils.h"
#include <google/protobuf/timestamp.pb.h>
namespace bts {
namespace orderman {
using namespace api::orderman;
struct TickOrder::PImpl
{
PImpl() {}
uint32_t tick_offset_on_deploy;
spark::QuantityMeasure cross_threshold;
uint32_t cross_ticks;
uint32_t max_chase_ticks;
bool detach_on_toward;
md::PriceSize opposite_at_inception;
};
TickOrder::TickOrder() : me(new PImpl()) {}
TickOrder::~TickOrder() {}
void TickOrder::setup_child(const api::orderman::OrderDefinition& this_def, const PreActivationOrderServices&,
api::orderman::OrderState& initial_state, api::orderman::OrderDefinition* out_child)
{
initial_state.mutable_tick();
auto const &tick_def = this_def.custom_def().tick();
*out_child->mutable_custom_def() = tick_def.child();
me->tick_offset_on_deploy = tick_def.tick_offset_on_deploy();
me->cross_threshold = tick_def.cross_threshold();
me->cross_ticks = tick_def.cross_ticks();
me->max_chase_ticks = tick_def.max_chase_ticks();
me->cross_threshold = tick_def.cross_threshold();
me->detach_on_toward = tick_def.detach_on_toward();
}
void TickOrder::payup(const md::PriceSize& opposite, uint32_t cross_ticks)
{
double new_price = opposite.price() + tick_size() * cross_ticks;
double limit = me->opposite_at_inception.price() + tick_size() * me->max_chase_ticks;
if(new_price <= limit)
update_child(Price(new_price), Quantity(this_state().quantity()));
else
{
update_child(Price(limit), Quantity(this_state().quantity()));
detach();
}
report_state();
}
void TickOrder::paydown(const md::PriceSize& opposite, uint32_t cross_ticks)
{
double new_price = opposite.price() - tick_size() * cross_ticks;
double limit = me->opposite_at_inception.price() - tick_size() * me->max_chase_ticks;
if(new_price >= limit)
update_child(Price(new_price), Quantity(this_state().quantity()));
else
{
update_child(Price(limit), Quantity(this_state().quantity()));
detach();
}
report_state();
}
void TickOrder::on_book_update(const bts::orderman::BookUpdateEvent& book)
{
switch (this_state().status())
{
case ORDER_STATUS_WORKING:
switch (child_state().status())
{
case ORDER_STATUS_INACTIVE:
{
if(definition().side() == api::SIDE_BUY)
{
me->opposite_at_inception = book.book.side(api::Side::SIDE_SELL)[0];
if(me->tick_offset_on_deploy > 0)
{
double price = me->opposite_at_inception.price() - tick_size() * me->tick_offset_on_deploy;
activate_child(Price(price), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
}
else
{
activate_child(Price(this_state().price()), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
}
}
else
{
me->opposite_at_inception = book.book.side(api::Side::SIDE_BUY)[0];
if(me->tick_offset_on_deploy > 0)
{
double price = me->opposite_at_inception.price() + tick_size() * me->tick_offset_on_deploy;
activate_child(Price(price), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
}
else
{
activate_child(Price(this_state().price()), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
}
}
report_state();
}
break;
case ORDER_STATUS_WORKING:
{
Quantity thresh = SpreaderUtils::_quantity_threshold(me->cross_threshold, Quantity(this_state().quantity()));
if(definition().side() == api::SIDE_BUY)
{
const auto& opposite = book.book.side(api::Side::SIDE_SELL)[0];
if(opposite.price() < me->opposite_at_inception.price())
{
if(me->detach_on_toward)
detach();
else
me->opposite_at_inception = opposite;
}
else if(opposite.price() > me->opposite_at_inception.price())
payup(opposite, me->cross_ticks);
else
{
if(opposite.size() <= thresh.value())
payup(opposite, me->cross_ticks);
}
}
else
{
const auto& opposite = book.book.side(api::Side::SIDE_BUY)[0];
if(opposite.price() > me->opposite_at_inception.price())
{
if(me->detach_on_toward)
detach();
else
me->opposite_at_inception = opposite;
}
else if(opposite.price() < me->opposite_at_inception.price())
paydown(opposite, me->cross_ticks);
else
{
if(opposite.size() <= thresh.value())
paydown(opposite, me->cross_ticks);
}
}
}
break;
case ORDER_STATUS_PENDING_WORKING:
break;
// child cannot be in any of these states if we are in the working state
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
break;
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
break;
case ORDER_STATUS_INACTIVE:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_DISCONNECTED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
}
}
}
@@ -0,0 +1,35 @@
#pragma once
#include "server/sparkd/orderman/orders/book_algos/BookAlgo.h"
namespace bts {
namespace orderman {
class OrderStateUpdateEvent;
class BookUpdateEvent;
//TickOrder deploys N ticks away from the initial opposite inside market and watches for it to trade below a certain size.
//If that size goes below a threshold or the market moves away, TickOrder Pays up once.
//The process repeats until the algo has paid up a max of N ticks from the original opposite inside order.
//If the market moves toward, the algo either starts watching the new inside market and keeps going or detaches depending on a parameter
class TickOrder : public BookAlgo
{
public:
TickOrder();
virtual ~TickOrder();
protected:
void setup_child(const api::orderman::OrderDefinition& this_def, const PreActivationOrderServices&,
api::orderman::OrderState&, api::orderman::OrderDefinition*) override;
void on_book_update(const bts::orderman::BookUpdateEvent&) override;
private:
void payup(const md::PriceSize& opposite, uint32_t cross_ticks) override;
void paydown(const md::PriceSize& opposite, uint32_t cross_ticks) override;
private:
struct PImpl;
std::unique_ptr<PImpl> me;
};
}}
@@ -0,0 +1,154 @@
#include "TopOrder.h"
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
#include "lib/domain_model/Security.h"
#include "lib/utils/PBUtils.h"
#include <google/protobuf/timestamp.pb.h>
namespace bts {
namespace orderman {
using namespace api::orderman;
struct TopOrder::PImpl
{
PImpl() {}
uint32_t watch_level_tick_offset;
spark::QuantityMeasure top_threshold;
bool enable_hedge;
int hedge_ticks;
OrderDefinition hedge_def;
};
TopOrder::TopOrder() : me(new PImpl()) {}
TopOrder::~TopOrder() {}
void TopOrder::setup_child(const api::orderman::OrderDefinition& this_def, const PreActivationOrderServices&,
api::orderman::OrderState& initial_state, api::orderman::OrderDefinition* out_child)
{
initial_state.mutable_top();
auto const &top_def = this_def.custom_def().top();
*out_child->mutable_custom_def() = top_def.child();
me->watch_level_tick_offset = top_def.watch_level_tick_offset();
me->top_threshold = top_def.top_threshold();
me->enable_hedge = top_def.enable_hedge();
me->hedge_ticks = top_def.hedge_ticks();
}
bool TopOrder::setup_hedge(const api::orderman::OrderDefinition& this_def, const PreActivationOrderServices&,
api::orderman::OrderDefinition* out_hedge)
{
auto const &top_def = this_def.custom_def().top();
if(me->enable_hedge && top_def.has_hedge())
{
*out_hedge->mutable_custom_def() = top_def.hedge();
if(definition().side() == api::SIDE_BUY)
out_hedge->set_side(api::SIDE_SELL);
else
out_hedge->set_side(api::SIDE_BUY);
return true;
}
else
return false;
}
void TopOrder::send_hedge_if_new_fills(const api::orderman::OrderState& orig_state, const OrderStateUpdateEvent& new_state)
{
uint32_t qty = new_state.state.filled_quantity() - orig_state.filled_quantity();
if(qty > 0)
{
double prx = new_state.state.last_price();
if(definition().side() == api::SIDE_BUY) //if this is a buy, I'm going to sell my hedge, nticks higher than the fill price
prx = prx + tick_size() * me->hedge_ticks;
else
prx = prx - tick_size() * me->hedge_ticks;
build_and_send_hedge(Price(prx), Quantity(qty));
}
}
void TopOrder::on_book_update(const bts::orderman::BookUpdateEvent& book)
{
switch (this_state().status())
{
case ORDER_STATUS_WORKING:
switch (child_state().status())
{
case ORDER_STATUS_INACTIVE:
{
activate_child(Price(this_state().price()), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
report_state();
}
break;
case ORDER_STATUS_WORKING:
{
Quantity thresh = SpreaderUtils::_quantity_threshold(me->top_threshold, Quantity(this_state().quantity()));
if(definition().side() == api::SIDE_BUY)
{
const auto& same = book.book.side(api::Side::SIDE_BUY)[0];
if(securities::test_price_equality(this_state().price(), same.price(), tick_size()))
{
if(same.size() <= thresh.value())
{
cancel_child();
transition(ORDER_STATUS_CANCELED);
report_state();
}
}
}
else
{
const auto& same = book.book.side(api::Side::SIDE_SELL)[0];
if(securities::test_price_equality(this_state().price(), same.price(), tick_size()))
{
if(same.size() <= thresh.value())
{
cancel_child();
transition(ORDER_STATUS_CANCELED);
report_state();
}
}
}
}
break;
case ORDER_STATUS_PENDING_WORKING:
break;
// child cannot be in any of these states if we are in the working state
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
break;
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
break;
case ORDER_STATUS_INACTIVE:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_DISCONNECTED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
}
}
}
@@ -0,0 +1,33 @@
#pragma once
#include "server/sparkd/orderman/orders/book_algos/BookAlgo.h"
namespace bts {
namespace orderman {
class OrderStateUpdateEvent;
class BookUpdateEvent;
//If TopOrder becomes the top of book, it cancels if there is less than some threshold of size joined with it.
//Fills optionally trigger hedge orders to the opposite side of the market, n ticks away.
class TopOrder : public BookAlgo
{
public:
TopOrder();
virtual ~TopOrder();
protected:
void setup_child(const api::orderman::OrderDefinition& this_def, const PreActivationOrderServices&,
api::orderman::OrderState&, api::orderman::OrderDefinition*) override;
bool setup_hedge(const api::orderman::OrderDefinition& parent, const PreActivationOrderServices&,
api::orderman::OrderDefinition*) override;
void send_hedge_if_new_fills(const api::orderman::OrderState&, const OrderStateUpdateEvent&) override;
void on_book_update(const bts::orderman::BookUpdateEvent&) override;
private:
struct PImpl;
std::unique_ptr<PImpl> me;
};
}}
@@ -15,7 +15,7 @@ SpreaderOrder::SpreaderOrder()
_cancelled (false),
_cancel_timeout (5000),// TODO:get it from config
_active(true),
_bookkeeping_data_ptr(nullptr),
//_bookkeeping_data_ptr(nullptr),
_order_services(nullptr),
_reported_state(),
_state_worker(nullptr),
@@ -52,7 +52,7 @@ private:
// is false if created from disk
const bool _active;
// this is the main data
SpreaderBookkeepingData* _bookkeeping_data_ptr;
std::unique_ptr<SpreaderBookkeepingData> _bookkeeping_data_ptr;
// pointer kept to send reports
SpreaderOrderServices* _order_services;
// state thats sent out in report (its kept here vs creating a new one everytime)
@@ -68,6 +68,10 @@ private:
/**************************Cons/Dest*******************************/
public:
SpreaderOrder();
SpreaderOrder(const SpreaderOrder&)=delete;
SpreaderOrder(SpreaderOrder&&)=delete;
SpreaderOrder& operator=(const SpreaderOrder&) = delete;
SpreaderOrder& operator=(SpreaderOrder&&) = delete;
virtual ~SpreaderOrder();
/**************************Order Derived***************************/
public:
@@ -73,21 +73,23 @@ public:
SingleFillHedger (SingleFillHedger&&);
SingleFillHedger& operator=(const SingleFillHedger&) = delete;
SingleFillHedger& operator=(SingleFillHedger&&) = delete;
//1. leave the start state and submit the order
// the a-f actions are associated with the state machine
// side effect actions
//a. leave the start state and submit the order
void init();
//2. called by the spreader when there is an order update
//b. called by the spreader when there is an order update
// reported by the exchange
void on_order_update(const SpreaderChildOrderState& order_update_message);
//3. called when the market data associated
//c. called when the market data associated
// with this security is changed
void on_book(bts::orderman::TickToTradeCorID corid, const bts::orderman::Book& book);
//4. called when the hedge order is cancelled, either
//d. called when the hedge order is cancelled, either
// manually or automatically
void cancel_hedge();
//5. immediatly terminates this hedger, without any attempts
//e. immediatly terminates this hedger, without any attempts
// to cancel the order
void terminate ();
//6. on the engine infrastructure time
//f. on the engine infrastructure time
void on_time(const bts::TimeStamp&);
// returns true if the quoter is in the final state, from which
// it will never come back
@@ -10,7 +10,7 @@ namespace bts {
namespace orderman {
SingleLegQuoter::SingleLegQuoter(ISpreaderManager& spreader,
LegPropertyData this_leg_property,
const LegPropertyData& this_leg_property,
size_t n_spreader_legs,
SpreaderChildOrderState& child_order_state)
:
@@ -38,7 +38,7 @@ private:
const bool _two_legged;
const bool _active_quoting;
ISpreaderManager& _spreader;
const LegPropertyData _this_leg_data;
const LegPropertyData& _this_leg_data;
const size_t _n_spreader_legs;
SpreaderChildOrderState& _child_order_state;
// computing the quoting price/quantity for multiple
@@ -60,41 +60,43 @@ private:
QuoterEndStateWorker _end_state_worker;
public:
SingleLegQuoter(ISpreaderManager& spreader,
LegPropertyData this_leg_property,
const LegPropertyData& this_leg_property,
size_t n_spreader_legs,
SpreaderChildOrderState& child_order_state);
public:
SingleLegQuoter() = delete;
SingleLegQuoter (SingleLegQuoter&&);
SingleLegQuoter (const SingleLegQuoter&) = delete;
SingleLegQuoter(SingleLegQuoter&&); // implementation has an assert since it will not be called
SingleLegQuoter(const SingleLegQuoter&) = delete;
SingleLegQuoter& operator=(const SingleLegQuoter&) = delete;
SingleLegQuoter& operator=(SingleLegQuoter&&) = delete;
public:
//1. this is called only once. this all could have been passed in
// the a-h actions are associated with the state machine
// side effect actions
//a. this is called only once. this could have been passed in
// the constructor, but the spreader is created in 2 stages.
// all the memory is allocated in a slow path and then values
// are initialized on fast activation path
void one_time_activate (Price, Quantity);
//2. called by the spreader a change in
//b. called by the spreader a change in
// order status(or fills) is reported
// by the exchange
// by the exchange
void update_order_status(const SpreaderChildOrderState& u);
//3. called when the market data associated
//c. called when the market data associated
// with this security is changed
void on_book(bts::orderman::TickToTradeCorID cid, size_t leg_index, const bts::orderman::Book&);
//4. called when the order is cancelled, either
//d. called when the order is cancelled, either
// manually or automatically
void cancel_order();
//5. the quoter forgets everything and goes to the end state
//e. the quoter forgets everything and goes to the end state
void terminate ();
//6. time update
//f. time update
void on_time(const bts::TimeStamp&);
//7. this is called by the spreader when a quoting fill
//g. this is called by the spreader when a quoting fill
// (ie not hedging fill) happens on one of the quoting
// legs
void update_other_leg_fills(size_t leg_index, Quantity filled_quantity);
//8. called when the user modified the spreader order price or quantity
//h. called when the user modified the spreader order price or quantity
void update_order_price_quantity(Price price, Quantity quantity);
// true if the quoter has an active working order. the order can
// be in an unknown state, but as long as an order has been sent
@@ -125,8 +127,8 @@ public:
private:
bts::orderman::OrderKey _key() {return _child_order_state.order_key();}
TimeStamp _current_time () const {return _spreader.current_time();}
std::string _log_prefix()
{
// TODO: state out and move the string to a const
std::string _log_prefix(){
std::ostringstream oss;
oss << " spreader_order_key: " << _spreader.spreader_order_key();
oss << " sec_key: " << _this_leg_data.security_key();
@@ -24,7 +24,7 @@ void SpreaderEndStateWorker::on_init_recovered(const bts::api::orderman::OrderRe
void SpreaderEndStateWorker::on_activate(Price price, Quantity quantity,
SpreaderOrderServices&){
// this can only happen if the security could not be resolved
// resulting in START->END. however on_active will could still
// resulting in START->END. however on_active could still
// be called
bts_assert (!_spreader->_bookkeeping_data().all_legs_resolved());
_spreader->_bookkeeping_data().update_order(price, quantity);
@@ -6,26 +6,27 @@ namespace orderman {
class SpreaderOrder;
class SpreaderEndStateWorker : public ISpreaderStateWorker
{
class SpreaderEndStateWorker : public ISpreaderStateWorker{
private:
SpreaderOrder* const _spreader;
public:
SpreaderEndStateWorker (SpreaderOrder*);
void on_transition() override;
SpreaderState state() const override;
void on_init_new(api::orderman::OrderDefinition const&, const SpreaderPreActivationOrderServices&) override;
void on_init_recovered(const bts::api::orderman::OrderRecord&, const SpreaderPreActivationOrderServices&) override;
void on_transition() override;
void on_init_new(const api::orderman::OrderDefinition&,
const SpreaderPreActivationOrderServices&) override;
void on_init_recovered(const bts::api::orderman::OrderRecord&,
const SpreaderPreActivationOrderServices&) override;
void on_activate(Price, Quantity, SpreaderOrderServices&) override;
void on_update(Price, Quantity) override;
void on_cancel() override;
void on_book(bts::orderman::TickToTradeCorID, const bts::orderman::Book&) override;
void on_book(TickToTradeCorID, const Book&) override;
void on_time(const TimeStamp&) override;
void on_quoter_order_error(bts::orderman::SpreaderLegData& leg_data) override;
void on_quoter_order_fill(bts::orderman::SpreaderLegData& leg_data) override;
void on_quoter_order_status_update(bts::orderman::SpreaderLegData& leg_data) override;
void on_quoter_order_error(SpreaderLegData& leg_data) override;
void on_quoter_order_fill(SpreaderLegData& leg_data) override;
void on_quoter_order_status_update(SpreaderLegData& leg_data) override;
void on_hedger_order_error(SingleFillHedger&) override;
void on_hedger_order_update(SingleFillHedger&) override;
void on_check_termination () override;
void on_check_termination() override;
};
}}
@@ -22,10 +22,10 @@ void SpreaderStartStateWorker::on_init_new(const api::orderman::OrderDefinition&
bts_assert(!_spreader->_inited());
std::vector<LegPropertyData> spreader_legs;
if (SpreaderUtils::resolve_synthetic_spread (spreader_pao_services, order_def, spreader_legs, false))
_spreader->_bookkeeping_data_ptr = new SpreaderBookkeepingData (*_spreader, _spreader->key(), order_def, spreader_pao_services, spreader_legs);
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader, _spreader->key(), order_def, spreader_pao_services, spreader_legs);
else
{
_spreader->_bookkeeping_data_ptr = new SpreaderBookkeepingData (*_spreader, _spreader->key(), order_def, spreader_pao_services, std::vector<LegPropertyData>());
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader, _spreader->key(), order_def, spreader_pao_services, std::vector<LegPropertyData>());
// if securities can not be resolved, the order is terminated
LOG(OrderMan, Error, "SpreaderStartStateWorker.on_init_new key: {} , could not resolve securities", _spreader->key().value());
std::string msg = "Could not resolve securities";
@@ -36,7 +36,7 @@ void SpreaderStartStateWorker::on_init_recovered(const bts::api::orderman::Order
{
bts_assert(!_spreader->_inited());
// we are not handling this yet, so lets send it directly to END
_spreader->_bookkeeping_data_ptr = new SpreaderBookkeepingData (*_spreader, _spreader->key(), order_record.def(), spreader_pao_services, std::vector<LegPropertyData>());
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader, _spreader->key(), order_record.def(), spreader_pao_services, std::vector<LegPropertyData>());
LOG(OrderMan, Warn, "SpreaderStartStateWorker.on_init_recovered key: {} , going to END", _spreader->key().value());
std::string msg = "Recovered spreader orders and not supported";
_spreader->_transform_to_END (&msg);
@@ -9,50 +9,47 @@ SpreaderWorkingStateWorker::SpreaderWorkingStateWorker (SpreaderOrder* spreader)
:
_spreader(spreader)
{}
SpreaderState SpreaderWorkingStateWorker::state() const {return SpreaderState::WORKING;}
void SpreaderWorkingStateWorker::on_transition()
{}
void SpreaderWorkingStateWorker::on_init_new(api::orderman::OrderDefinition const&, const SpreaderPreActivationOrderServices&)
{
SpreaderState SpreaderWorkingStateWorker::state() const {
return SpreaderState::WORKING;
}
void SpreaderWorkingStateWorker::on_transition(){
}
void SpreaderWorkingStateWorker::on_init_new(api::orderman::OrderDefinition const&,
const SpreaderPreActivationOrderServices&){
bts_assert_msg (false, "SpreaderWorkingStateWorker::on_init_new is an illegal operation");
}
void SpreaderWorkingStateWorker::on_init_recovered(const bts::api::orderman::OrderRecord&, const SpreaderPreActivationOrderServices&)
{
void SpreaderWorkingStateWorker::on_init_recovered(const bts::api::orderman::OrderRecord&,
const SpreaderPreActivationOrderServices&){
bts_assert_msg (false, "SpreaderWorkingStateWorker::on_init_recovered is an illegal operation");
}
void SpreaderWorkingStateWorker::on_activate(Price, Quantity, SpreaderOrderServices&)
{
void SpreaderWorkingStateWorker::on_activate(Price, Quantity,
SpreaderOrderServices&){
bts_assert_msg (false, "SpreaderWorkingStateWorker::on_activate is an illegal operation");
}
void SpreaderWorkingStateWorker::on_update(Price price, Quantity quantity)
{
if (_spreader->_quoters_can_accept_modification())
{
void SpreaderWorkingStateWorker::on_update(Price price, Quantity quantity){
if (_spreader->_quoters_can_accept_modification()){
_spreader->_set_price_quantity (price, quantity);
// updating all active quoters, nothing to do with the hedgers
for (size_t q_idx = 0; q_idx < _spreader->_bookkeeping_data().n_legs(); q_idx++)
{
for (size_t q_idx = 0; q_idx < _spreader->_bookkeeping_data().n_legs(); q_idx++){
SpreaderLegData& leg_data = _spreader->_bookkeeping_data().get_leg_data(q_idx);
if (leg_data.leg_property_data().is_active_quoting())
leg_data.quoter().update_order_price_quantity (price, quantity);
}
}
}
void SpreaderWorkingStateWorker::on_cancel()
{
void SpreaderWorkingStateWorker::on_cancel(){
_spreader->_cancel_orders (true, true);
_spreader->_transform_to_WAITING_FOR_CANCEL_ALL ();
}
void SpreaderWorkingStateWorker::on_book(bts::orderman::TickToTradeCorID cid, const bts::orderman::Book& book)
{
void SpreaderWorkingStateWorker::on_book(TickToTradeCorID cid,
const Book& book){
SpreaderBookkeepingData& bookkeeping_data = _spreader->_bookkeeping_data();
// this is a key->index lookup
SpreaderLegData& md_leg = bookkeeping_data.get_leg_data(book.security_key());
// updating md state (this is the only place where its done)
md_leg.update_book(book);
// sending md to all quoters
for (size_t q_idx = 0; q_idx < bookkeeping_data.n_legs(); q_idx++)
{
for (size_t q_idx = 0; q_idx < bookkeeping_data.n_legs(); q_idx++){
SpreaderLegData& d = bookkeeping_data.get_leg_data(q_idx);
if (d.leg_property_data().is_active_quoting())
d.quoter().on_book(cid, md_leg.leg_property_data().leg_index(), book);
@@ -61,11 +58,9 @@ void SpreaderWorkingStateWorker::on_book(bts::orderman::TickToTradeCorID cid, co
for (size_t h_idx = 0; h_idx < md_leg.n_hedgers(); h_idx++)
md_leg.get_hedger(h_idx).on_book(cid, book);
}
void SpreaderWorkingStateWorker::on_time(const TimeStamp& ts)
{
void SpreaderWorkingStateWorker::on_time(const TimeStamp& ts){
SpreaderBookkeepingData& bookkeeping_data = _spreader->_bookkeeping_data();
for (size_t q_idx = 0; q_idx < bookkeeping_data.n_legs(); q_idx++)
{
for (size_t q_idx = 0; q_idx < bookkeeping_data.n_legs(); q_idx++){
SpreaderLegData& leg_data = bookkeeping_data.get_leg_data(q_idx);
if (leg_data.leg_property_data().is_active_quoting())
leg_data.quoter().on_time(ts);
@@ -73,15 +68,13 @@ void SpreaderWorkingStateWorker::on_time(const TimeStamp& ts)
leg_data.get_hedger(h_idx).on_time (ts);
}
}
void SpreaderWorkingStateWorker::on_quoter_order_error(bts::orderman::SpreaderLegData& leg_data)
{
void SpreaderWorkingStateWorker::on_quoter_order_error(SpreaderLegData& leg_data){
// this is because the error message could also have status changes
leg_data.quoter().update_order_status (leg_data.order_update_message());
_spreader->_cancel_orders (true, true);
_spreader->_transform_to_WAITING_FOR_CANCEL_ALL ();
}
void SpreaderWorkingStateWorker::on_quoter_order_fill(bts::orderman::SpreaderLegData& leg_data)
{
void SpreaderWorkingStateWorker::on_quoter_order_fill(SpreaderLegData& leg_data){
// this is the most complex part of the spreader. the following needs to be done
// 1. tell other quoters about it (so they reduce their sizes)
// 2. launch a new hedger
@@ -101,11 +94,9 @@ void SpreaderWorkingStateWorker::on_quoter_order_fill(bts::orderman::SpreaderLeg
LOG(OrderMan, Info, "SpreaderWorkingStateWorker::on_quoter_order_fill order: key: {} order_key: {} status: {} filled_qty: {} last_price: {}",
_spreader->key(), order_update_message.order_key(), order_update_message.status(), order_update_message.last_update_filled_quantity(), order_update_message.last_update_filled_price());
bts_assert (order_update_message.last_update_filled_quantity().value() > 0);
for (size_t q_idx = 0; q_idx < _spreader->_bookkeeping_data().n_legs(); q_idx++)
{
for (size_t q_idx = 0; q_idx < _spreader->_bookkeeping_data().n_legs(); q_idx++){
SpreaderLegData& other_leg_data = _spreader->_bookkeeping_data().get_leg_data (q_idx);
if (q_idx != leg_data.leg_property_data().leg_index())
{
if (q_idx != leg_data.leg_property_data().leg_index()){
// if it is an actively quoting leg, inform the quoter about it
// so it can decrease the proportional size
if (other_leg_data.leg_property_data().is_active_quoting())
@@ -140,10 +131,4 @@ void SpreaderWorkingStateWorker::on_check_termination()
if (_spreader->_all_quoter_orders_in_terminal_state() && _spreader->_all_hedger_orders_in_terminal_state())
_spreader->_transform_to_END ();
}
bool SpreaderWorkingStateWorker::_is_leg_filled (size_t leg_idx) const
{
const SpreaderLegData& leg_data = _spreader->_bookkeeping_data().get_leg_data(leg_idx);
Quantity leg_target_qty = SpreaderUtils::get_leg_quantity_from_spreader_quantity (leg_data.leg_property_data(), _spreader->_bookkeeping_data().quantity());
return leg_data.total_fills().value() >= leg_target_qty.value();
}
}}
@@ -14,21 +14,22 @@ public:
SpreaderWorkingStateWorker (SpreaderOrder*);
void on_transition() override;
SpreaderState state() const override;
void on_init_new(api::orderman::OrderDefinition const&, const SpreaderPreActivationOrderServices&) override;
void on_init_recovered(const bts::api::orderman::OrderRecord&, const SpreaderPreActivationOrderServices&) override;
void on_activate(Price, Quantity, SpreaderOrderServices&) override;
void on_init_new(api::orderman::OrderDefinition const&,
const SpreaderPreActivationOrderServices&) override;
void on_init_recovered(const bts::api::orderman::OrderRecord&,
const SpreaderPreActivationOrderServices&) override;
void on_activate(Price, Quantity,
SpreaderOrderServices&) override;
void on_update(Price, Quantity) override;
void on_cancel() override;
void on_book(bts::orderman::TickToTradeCorID, const bts::orderman::Book&) override;
void on_book(TickToTradeCorID,
const Book&) override;
void on_time(const TimeStamp&) override;
void on_quoter_order_error(bts::orderman::SpreaderLegData& leg_data) override;
void on_quoter_order_fill(bts::orderman::SpreaderLegData& leg_data) override;
void on_quoter_order_status_update(bts::orderman::SpreaderLegData& leg_data) override;
void on_quoter_order_error(SpreaderLegData& leg_data) override;
void on_quoter_order_fill(SpreaderLegData& leg_data) override;
void on_quoter_order_status_update(SpreaderLegData& leg_data) override;
void on_hedger_order_error(SingleFillHedger&) override;
void on_hedger_order_update(SingleFillHedger&) override;
void on_check_termination () override;
private:
bool _is_leg_filled (size_t leg_idx) const;
};
}}
@@ -30,7 +30,7 @@ def print_traces(last,traces):
if last in in_rec and "zero" in in_rec:
first = in_rec["zero"]
shot = in_rec[last]
if shot-first < 500000:
o
print "%d -> %d" % (k, shot-first)
previous = first
for key, value in sorted(in_rec.iteritems(), key=lambda (k,v): (v,k)):
@@ -57,25 +57,26 @@ def transform(delta_exps,raw_events):
exps = parse_exps(sys.argv[1:])
traces = parse_log()
print_traces("SocketConnection_write_pre_write",traces)
times = transform(exps,traces)
print traces
# print_traces("SocketConnection_write_pre_write",traces)
# times = transform(exps,traces)
clean_times = []
for t in times:
if t['t'] < 500000:
clean_times.append(t)
# clean_times = []
# for t in times:
# if t['t'] < 500000:
# clean_times.append(t)
frame = DataFrame(clean_times)
# frame = DataFrame(clean_times)
# print "Overview (all data)"
# print "--------------------"
# print frame
# print
frame = frame.drop('id', 1)
print "Medians (all data)"
print "------------------"
print frame.median()
print
# frame = frame.drop('id', 1)
# print "Medians (all data)"
# print "------------------"
# print frame.median()
# print
# print "StdDevs (all data)"
# print "------------------"
@@ -102,3 +103,5 @@ print
# print "StdDevs (clean data)"
# print "--------------------"
# print frame.std()
Python finished at Tue Jun 5 14:54:09
@@ -1,2 +1,2 @@
#!/bin/bash
./parse_log.sh ~/sparkdata/data/logging/log | ./analyze.py t=SocketConnection_write_pre_write-zero
./parse_log.sh $1 | ./analyze.py t=SocketConnection_write_pre_write-MDSource
@@ -1,3 +1,2 @@
#!/bin/bash
cat $1 | grep "| Latency" | awk '{ print $6,$7,$5 }' | sort -n
cat $1 | grep "|Latency" | awk -F "|" '{print $9}' | awk '{print $2,$3,$1}'
+1 -44
View File
@@ -9,39 +9,8 @@
#include <lttng/tracepoint.h>
TRACEPOINT_EVENT(bts_trace, md,
TP_ARGS(char *, text, long, security),
TP_FIELDS(
ctf_string(msg, text)
ctf_integer_hex(long, sec,security)
))
TRACEPOINT_EVENT(bts_trace, eye,
TP_ARGS(char *, text, long, security),
TP_FIELDS(
ctf_string(msg, text)
ctf_integer_hex(long, sec,security)
))
TRACEPOINT_EVENT(bts_trace, btscmd,
TP_ARGS(char *, text),
TP_FIELDS(
ctf_string(msg, text)
))
TRACEPOINT_EVENT(bts_trace, o2,
TP_ARGS(char *, text),
TP_FIELDS(
ctf_string(msg, text)
))
TRACEPOINT_EVENT(bts_trace,quickfix,
TP_ARGS(char *, text),
TP_FIELDS(
ctf_string(msg, text)
))
TRACEPOINT_EVENT(bts_trace, quoter,
TRACEPOINT_EVENT(bts_trace, trace_order,
TP_ARGS(char *, text, long long, argument1, long long, argument2),
TP_FIELDS(
ctf_string(msg, text)
@@ -49,18 +18,6 @@ TRACEPOINT_EVENT(bts_trace, quoter,
ctf_integer(long long, arg2,argument2)
))
TRACEPOINT_EVENT(bts_trace, driver,
TP_ARGS(char *, text, long long, arg1,long long, sec_key,float, bid, float, ask, int, bid_sz,int, ask_sz, int, seq_n),
TP_FIELDS(
ctf_string(msg, text)
ctf_integer(long long, arg1,arg1)
ctf_integer(long long, sec_key,sec_key)
ctf_float(float, bid,bid)
ctf_float(float, ask,ask)
ctf_integer(int, bid_sz,bid_sz)
ctf_integer(int, ask_sz,ask_sz)
ctf_integer (int,seq_n,seq_n)
))
#endif /* _BTS_TP_H */
#include <lttng/tracepoint-event.h>
-1
View File
@@ -17,7 +17,6 @@ echo OUT=$OUT
rm -rf $OUT/api
mkdir -p $OUT/api
mkdir -p $OUT/api/fable
# BUILD_DIR
if [ "$#" -gt 0 ]
then
+9 -1
View File
@@ -55,4 +55,12 @@ input[type=text].login-input, input[type=password].login-input
vertical-align: middle
table .fabutton
margin-top: 0.9ex
margin-top: 0.9ex
.no-border
border: 0
td
border: 0
th
border: 0
+8 -5
View File
@@ -20,19 +20,22 @@ let init (context:ServiceContext.ServiceContext) =
mdRelayProductData = [||]
mdRelayExpNameData = Array.empty<ExpirationNameList>
scratch = emptyAccount
previousScratch = emptyAccount
AllValue = " "
AllValueDisplay = "All"
}, Access.getAdminData admin UpdateAdminData NoAdminData
let existingSelection (scratch:BTSAccount) (data:AdminData) =
let existingSelection (scratch:BTSAccount) (previousScratch:BTSAccount) (data:AdminData) =
printf "%O" data.accounts
List.choose id [
data.accounts |> Array.tryFind (fun a -> a.name = scratch.name)
data.accounts |> Array.tryFind (fun a -> a.name = previousScratch.name)
data.accounts |> Array.tryHead
Some emptyAccount]
|> List.head
let preserveSelection (data:AdminData) model =
let account = existingSelection model.scratch data
let account = existingSelection model.scratch model.previousScratch data
{ model with scratch = account }
@@ -98,7 +101,7 @@ let erase (arr:'a[]) i =
let update msg (model:Model) : Model * Cmd<Msg> =
match msg with
| UpdateAdminData data ->
let presentAccounts = data.accounts |> Array.filter (fun a -> a.isPresent) |> Array.sortBy (fun a -> a.name)
let presentAccounts = data.accounts |> Array.filter (fun a -> a.isPresent) |> Array.sortBy (fun a -> a.name.ToLower())
let data = { data with accounts = presentAccounts }
{ model with adminData = Some data }
|> preserveSelection data, Cmd.ofExistingPromise (requestProducts model.mdRelay) id (box>>ErrorOccured)
@@ -107,14 +110,14 @@ let update msg (model:Model) : Model * Cmd<Msg> =
model, Cmd.Empty
| SelectAccount account ->
// When an account is selected 'scratch' account is discarded
{ model with scratch = account }, Cmd.Empty
{ model with previousScratch = model.scratch; scratch = account }, Cmd.Empty
| DeleteAccount account ->
match Fable.Import.Browser.window.confirm (sprintf "Are you sure you want to delete '%s'" account.name) with
| true -> model, Cmd.ofExistingPromise (deleteAccount model.admin account) id (box>>ErrorOccured)
| false -> model, Cmd.Empty
| AddAccount ->
// Create a new scratch account. Send to server after user has filled in details
{ model with scratch = emptyAccount }, Cmd.ofMsg <| SelectTab General
{ model with previousScratch = model.scratch; scratch = emptyAccount }, Cmd.ofMsg <| SelectTab General
| SaveScratch ->
match isNewAccount model.scratch with
| true -> model, Cmd.ofExistingPromise (createAccount model.admin model.scratch) id (box>>ErrorOccured)
+1
View File
@@ -28,6 +28,7 @@ type Model = {
mdRelayProductData: Product[]
mdRelayExpNameData: ExpirationNameList[]
scratch : BTSAccount
previousScratch : BTSAccount
AllValue: string
AllValueDisplay: string
}
+102 -95
View File
@@ -61,12 +61,29 @@ let mappedDataGridWithAddDelete<'a,'r> title (descriptor:MappedDataGridDescripto
//
let generalView model dispatch =
let account = model.scratch
let revertFun =
match model.adminData with
| Some data -> fun _ -> (dispatch <| SelectAccount (State.existingSelection model.scratch model.previousScratch data))
| None -> fun _ -> (dispatch <| SelectAccount emptyAccount)
let modal = Modal.showCreate
"Create Account"
(div [] [
Fields.text "Name" account.name [] (AccountName >> Update >> dispatch)
Fields.text "Description" account.description [] (Description >> Update >> dispatch)
Fields.checkbox "Active" "" account.isActive [] (IsActive >> Update >> dispatch)
])
(fun _ -> dispatch SaveScratch)
revertFun
div [] [
p [ Style [FontWeight "Bold"] ] [ str ("ID: " + (string account.id))]
br []
Fields.text "Name" account.name [] (AccountName >> Update >> dispatch)
Fields.text "Description" account.description [] (Description >> Update >> dispatch)
Fields.checkbox "Active" "" account.isActive [] (IsActive >> Update >> dispatch)
(if State.isNewAccount account then modal else span[][])
]
let clipLimitsView model dispatch =
@@ -74,12 +91,12 @@ let clipLimitsView model dispatch =
{
cells =
[
{ content = str ""; className = ""; isHeader = true }
{ content = str "Exchange"; className = ""; isHeader = true }
{ content = str "Security Type"; className = ""; isHeader = true }
{ content = str "Symbol"; className = ""; isHeader = true }
{ content = str "Security"; className = ""; isHeader = true }
{ content = str "Clip Size"; className = ""; isHeader = true }
TableControl.Cell (str "")
TableControl.Cell (str "Exchange")
TableControl.Cell (str "Security Type")
TableControl.Cell (str "Symbol")
TableControl.Cell (str "Security")
TableControl.Cell (str "Clip Size")
]
className = ""
header = None
@@ -138,51 +155,41 @@ let clipLimitsView model dispatch =
[ for limit in model.scratch.securityClipLimits.limit do yield {
cells =
[
{ content = Button.fabutton "Remove" "times" Button.Enabled (fun _ -> (Array.findIndex (fun l -> limit = l) model.scratch.securityClipLimits.limit) |> DeleteClipLimit |> dispatch); className = ""; isHeader = false }
{ content = Fields.enumSelect<string>
TableControl.Cell (Button.fabutton "Remove" "times" Button.Enabled (fun _ -> (Array.findIndex (fun l -> limit = l) model.scratch.securityClipLimits.limit) |> DeleteClipLimit |> dispatch))
TableControl.Cell (Fields.enumSelect<string>
""
(EnumUtils.toHuman<bts.api.Venue> false venues limit.security.exchange)
(EnumUtils.allToHuman<bts.api.Venue> false venues)
[]
((changeVenue limit) >> ClipLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.enumSelect<string>
)
TableControl.Cell (Fields.enumSelect<string>
""
(EnumUtils.toHuman<bts.api.SecurityType> false securityTypes limit.security.securityType)
(EnumUtils.allToHuman<bts.api.SecurityType> false securityTypes)
[]
((changeType limit) >> ClipLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.enumSelect<string>
)
TableControl.Cell (Fields.enumSelect<string>
""
limit.security.securitySymbol
symbolArray
[]
((changeSymbol limit) >> ClipLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.mapSelect
)
TableControl.Cell (Fields.mapSelect
""
(JSMap.toMap (getSecurityMap limit))
None
(Some (sprintf "%i" limit.security.securityId))
((changeSecurityId limit) >> ClipLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.text
)
TableControl.Cell (Fields.text
""
(sprintf "%i" limit.clipSize)
[ Style [Width "10em"]]
((changeClipSize limit) >> ClipLimits >> dispatch)
className = ""
isHeader = false
}
)
]
className = ""
header = None
@@ -206,12 +213,12 @@ let productLimits model dispatch =
{
cells =
[
{ content = str ""; className = ""; isHeader = true }
{ content = str "Exchange"; className = ""; isHeader = true }
{ content = str "Security Type"; className = ""; isHeader = true }
{ content = str "Symbol"; className = ""; isHeader = true }
{ content = str "Max Net Long Position"; className = ""; isHeader = true }
{ content = str "Max Net Short Position"; className = ""; isHeader = true }
TableControl.Cell (str "")
TableControl.Cell (str "Exchange")
TableControl.Cell (str "Security Type")
TableControl.Cell (str "Symbol")
TableControl.Cell (str "Max Net Long Position")
TableControl.Cell (str "Max Net Short Position")
]
className = ""
header = None
@@ -251,50 +258,40 @@ let productLimits model dispatch =
[ for limit in model.scratch.productPositionLimits do yield {
cells =
[
{ content = Button.fabutton "Remove" "times" Button.Enabled (fun _ -> (Array.findIndex (fun l -> limit = l) model.scratch.productPositionLimits) |> DeleteProductLimit |> dispatch); className = ""; isHeader = false }
{ content = Fields.enumSelect<string>
TableControl.Cell (Button.fabutton "Remove" "times" Button.Enabled (fun _ -> (Array.findIndex (fun l -> limit = l) model.scratch.productPositionLimits) |> DeleteProductLimit |> dispatch))
TableControl.Cell (Fields.enumSelect<string>
""
(EnumUtils.toHuman<bts.api.Venue> false venues limit.product.exchange)
(EnumUtils.allToHuman<bts.api.Venue> false venues)
[]
((changeVenue limit) >> ProductLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.enumSelect<string>
)
TableControl.Cell (Fields.enumSelect<string>
""
(EnumUtils.toHuman<bts.api.SecurityType> false securityTypes limit.product.securityType)
(EnumUtils.allToHuman<bts.api.SecurityType> false securityTypes)
[]
((changeType limit) >> ProductLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.enumSelect<string>
)
TableControl.Cell (Fields.enumSelect<string>
""
limit.product.securitySymbol
symbolArray
[]
((changeSymbol limit) >> ProductLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.text
)
TableControl.Cell (Fields.text
""
(sprintf "%i" limit.longPositionLimit)
[ Style [Width "10em"]]
((changeLongPositionLimit limit) >> ProductLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.text
)
TableControl.Cell (Fields.text
""
(sprintf "%i" limit.shortPositionLimit)
[ Style [Width "10em"]]
((changeShortPositionLimit limit) >> ProductLimits >> dispatch)
className = ""
isHeader = false
}
)
]
className = ""
header = None
@@ -326,12 +323,13 @@ let securityLimits model dispatch =
{
cells =
[
{ content = str ""; className = ""; isHeader = true }
{ content = str "Exchange"; className = ""; isHeader = true }
{ content = str "Security Type"; className = ""; isHeader = true }
{ content = str "Symbol"; className = ""; isHeader = true }
{ content = str "Max Net Long Position"; className = ""; isHeader = true }
{ content = str "Max Net Short Position"; className = ""; isHeader = true }
TableControl.Cell (str "")
TableControl.Cell (str "Exchange")
TableControl.Cell (str "Security Type")
TableControl.Cell (str "Symbol")
TableControl.Cell (str "Security")
TableControl.Cell (str "Max Net Long Position")
TableControl.Cell (str "Max Net Short Position")
]
className = ""
header = None
@@ -339,6 +337,25 @@ let securityLimits model dispatch =
let symbolArray = Array.concat [| [| model.AllValueDisplay |]; Array.map<bts.api.Product, string> (fun a -> a.symbol ) model.mdRelayProductData |]
let getSecurityMap (limit:AccountSecurityPositionLimitUnit) =
let allOption = {
product = {
exchange = bts.api.Venue.UNSET
``type`` = bts.api.SecurityType.UNSET
symbol = model.AllValueDisplay
};
names = (JSMap.add<string> "0" model.AllValueDisplay JSMap.empty<string>)
}:ExpirationNameList
Array.concat [ [| allOption |]; model.mdRelayExpNameData]
|> Array.filter (fun (x:bts.co2.ExpirationNameList) -> (
((x.product.exchange = limit.security.exchange) || (x.product.exchange = bts.api.Venue.UNSET))
&& ((x.product.``type`` = limit.security.securityType) || (x.product.``type`` = bts.api.SecurityType.UNSET))
&& ((x.product.symbol = limit.security.securitySymbol) || (x.product.symbol = model.AllValueDisplay))
)
)
|> Seq.map (fun x -> x.names ) |> JSMap.concat
let modifyRow (oldRow:AccountSecurityPositionLimitUnit) (newRow:AccountSecurityPositionLimitUnit) =
let i = Array.findIndex (fun l -> oldRow = l) model.scratch.securityPositionLimits
let left = Array.append (model.scratch.securityPositionLimits.[..i-1]) [| newRow |]
@@ -361,6 +378,9 @@ let securityLimits model dispatch =
| true -> model.AllValue
modifyRow oldRow { oldRow with security = { oldRow.security with securitySymbol = v } }
let changeSecurityId (oldRow:AccountSecurityPositionLimitUnit) (value:string) =
modifyRow oldRow { oldRow with security = { oldRow.security with securityId = (uint32 value) } }
let changeLongPositionLimit (oldRow:AccountSecurityPositionLimitUnit) (value:string) =
modifyRow oldRow { oldRow with longPositionLimit = (uint32 value) }
@@ -371,50 +391,47 @@ let securityLimits model dispatch =
[ for limit in model.scratch.securityPositionLimits do yield {
cells =
[
{ content = Button.fabutton "Remove" "times" Button.Enabled (fun _ -> (Array.findIndex (fun l -> limit = l) model.scratch.securityPositionLimits) |> DeleteSecurityLimit |> dispatch); className = ""; isHeader = false }
{ content = Fields.enumSelect<string>
TableControl.Cell (Button.fabutton "Remove" "times" Button.Enabled (fun _ -> (Array.findIndex (fun l -> limit = l) model.scratch.securityPositionLimits) |> DeleteSecurityLimit |> dispatch))
TableControl.Cell (Fields.enumSelect<string>
""
(EnumUtils.toHuman<bts.api.Venue> false venues limit.security.exchange)
(EnumUtils.allToHuman<bts.api.Venue> false venues)
[]
((changeVenue limit) >> SecurityLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.enumSelect<string>
)
TableControl.Cell (Fields.enumSelect<string>
""
(EnumUtils.toHuman<bts.api.SecurityType> false securityTypes limit.security.securityType)
(EnumUtils.allToHuman<bts.api.SecurityType> false securityTypes)
[]
((changeType limit) >> SecurityLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.enumSelect<string>
)
TableControl.Cell (Fields.enumSelect<string>
""
limit.security.securitySymbol
symbolArray
[]
((changeSymbol limit) >> SecurityLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.text
)
TableControl.Cell (Fields.mapSelect
""
(JSMap.toMap (getSecurityMap limit))
None
(Some (sprintf "%i" limit.security.securityId))
((changeSecurityId limit) >> SecurityLimits >> dispatch)
)
TableControl.Cell (Fields.text
""
(sprintf "%i" limit.longPositionLimit)
[ Style [Width "10em"]]
((changeLongPositionLimit limit) >> SecurityLimits >> dispatch)
className = ""
isHeader = false
}
{ content = Fields.text
)
TableControl.Cell (Fields.text
""
(sprintf "%i" limit.shortPositionLimit)
[ Style [Width "10em"]]
((changeShortPositionLimit limit) >> SecurityLimits >> dispatch)
className = ""
isHeader = false
}
)
]
className = ""
header = None
@@ -449,18 +466,10 @@ let ConnectionsView (model:Accounts.Types.Model) dispatch =
let rws:TableControl.row list =
[for name in conNames do yield {
cells = [
{
content = Button.fabutton "" "times" Button.Enabled (fun _ -> name |> ( RemoveConnection >> dispatch))
className = ""
isHeader = false
}
{
content = str name
className = ""
isHeader = false
}
(TableControl.Cell (Button.fabutton "" "times" Button.Enabled (fun _ -> name |> ( RemoveConnection >> dispatch))))
(TableControl.Cell (str name))
]
className=""
className="no-border"
header = None
}
]
@@ -476,9 +485,7 @@ let ConnectionsView (model:Accounts.Types.Model) dispatch =
|> Map.ofArray
let rows =
if not (Map.isEmpty conMap) then
rws @ [{cells = [ {content = Fields.mapSelect "" conMap (Some "Add Connection") None (fun v -> v |> (AddConnection >> dispatch)); className = ""; isHeader = true } ]; className = ""; header = None }]
else rws
rws @ [{cells = [ {content = Fields.mapSelect "" conMap (Some "Add Connection") None (fun v -> v |> (AddConnection >> dispatch)); className = ""; isHeader = true; colSpan = 2 } ]; className = "no-border"; header = None }]
div []
[
TableControl.createTable None rows
-1
View File
@@ -8,7 +8,6 @@ open Fable.Import.React
open Global
open Fable
open JSMap
open Fable
let text (lbl:string) (dv:string) (attrs:IHTMLProp list) onchange =
div [ ClassName "field" ]
+39
View File
@@ -0,0 +1,39 @@
module Modal
open Fable.Core
open Fable.Core.JsInterop
open Fable.Helpers.React
open Fable.Helpers.React.Props
open Fable.Import.React
open Global
open Fable
let show (title:string) (content:ReactElement) (footerContent:ReactElement) (onCloseClick: MouseEvent -> Unit) =
div [ Class "modal is-active" ]
[ div [ Class "modal-background" ][ ]
div [ Class "modal-card" ]
[ header [ Class "modal-card-head" ]
[ p [ Class "modal-card-title" ][ str title ]
button [ Class "delete"; OnClick onCloseClick ] [ ]
]
section [ Class "modal-card-body" ] [ content ]
footer [ Class "modal-card-foot" ] [ footerContent ]
]
]
let showCreate title content (onCreateClick: MouseEvent -> Unit) (onCloseClick: MouseEvent -> Unit) =
show
title
content
(div [] [ button [ Class "button is-success"; OnClick onCreateClick ]
[ str "Create" ]
button [ Class "button"; OnClick onCloseClick ]
[ str "Cancel" ]
])
(onCloseClick: MouseEvent -> Unit)
+5 -3
View File
@@ -6,7 +6,9 @@ open Fable.Helpers.React
open Fable.Helpers.React.Props
open Fable.Import.React
type cell = { content:ReactElement; className:string; isHeader:bool }
type cell = { content:ReactElement; className:string; isHeader:bool; colSpan:int }
let Cell (content:ReactElement) = { content = content; className = ""; isHeader = false; colSpan = 0 }
type row = {
cells:cell list
@@ -16,8 +18,8 @@ type row = {
let createCell (c:cell) =
match c.isHeader with
| false -> td [ ClassName c.className ] [ c.content ]
| true -> th [ ClassName c.className ] [ c.content ]
| false -> td [ ClassName c.className; ColSpan (float c.colSpan) ] [ c.content ]
| true -> th [ ClassName c.className; ColSpan (float c.colSpan) ] [ c.content ]
let createRow (r:row) =
match r.header with
+1
View File
@@ -1,6 +1,7 @@
module Global
open Fable.Core
let Class = Fable.Helpers.React.Props.HTMLAttr.ClassName
type AlertLevel =
| Info
+29 -15
View File
@@ -85,6 +85,7 @@ let init (context:ServiceContext.ServiceContext) : Model * Cmd<Msg> =
userList = []
accountsList = []
scratchUser = defaultScratchUser
PreviousScratchUser = defaultScratchUser
currentTab = Tab.General
admin = admin
adminData = None
@@ -96,13 +97,31 @@ let rec removeUser (user:BTSUser) (userList:BTSUser list) =
| hd::tl -> hd::removeUser user tl
| _ -> []
let getCurrentUser (user:BTSUser) (userList:BTSUser list) =
List.tryFind (fun (x:BTSUser) -> x.id = user.id) userList
let getFirstUser usrlst =
let head = (List.sortBy (fun (u:BTSUser) -> u.name.ToLower()) usrlst) |> List.tryHead
match head with
| None -> defaultScratchUser
| Some hd -> hd
let userIsdirty (initialState:BTSUser) (scratch:BTSUser) (existing:BTSUser option) =
match existing with
| None -> (scratch <> initialState)
| Some ex -> (scratch <> ex) && (scratch <> initialState)
let findUserInList (user:BTSUser) (userList:BTSUser list) =
List.tryFind (fun (x:BTSUser) ->
x.id = user.id
|| (user.id = 0u && x.name = user.name)
)
userList
let getCurrentOrFirstUser (model:Model) (userList:BTSUser list) =
match (findUserInList model.scratchUser userList), (findUserInList model.PreviousScratchUser userList) with
| Some cu, _ -> cu
| None, Some pu -> pu
| None, None -> getFirstUser userList
let getCurrentUser (model:Model) = getCurrentOrFirstUser model model.userList
let userIsdirty (initialState:BTSUser) (scratch:BTSUser) (existing:BTSUser) = (scratch <> existing) && (scratch <> initialState)
let userIsSelected (initialState:BTSUser) (scratch:BTSUser) = scratch <> initialState
@@ -180,16 +199,11 @@ let update msg (model:Users.Types.Model) : Model * Cmd<Msg> =
| UpdateAdminData adm ->
let usrlst = Seq.toList (Seq.filter (fun (usr:BTSUser) -> usr.isPresent) adm.users)
let cusr =
match getCurrentUser model.scratchUser usrlst with
| None -> (List.sortBy (fun (u:BTSUser) -> u.name.ToLower()) usrlst).Head
| Some cu -> cu
{model with
adminData = Some adm
userList = usrlst
accountsList = (Seq.toList adm.accounts)
scratchUser = cusr
scratchUser = (getCurrentOrFirstUser model usrlst)
}, Cmd.Empty
| RpcResponse str ->
printf "%s" str
@@ -213,16 +227,16 @@ let update msg (model:Users.Types.Model) : Model * Cmd<Msg> =
| false -> model, Cmd.Empty
| AddUser ->
{ model with scratchUser = { defaultScratchUser with name = "New User" } }, Cmd.ofMsg (SwitchTab General)
{ model with PreviousScratchUser = model.scratchUser; scratchUser = { defaultScratchUser with name = "New User" } }, Cmd.ofMsg (SwitchTab General)
| SwitchTab tb ->
{model with currentTab = tb}, Cmd.Empty
| SwitchUser usr ->
{ model with scratchUser = usr }, Cmd.Empty
{ model with PreviousScratchUser = model.scratchUser; scratchUser = usr }, Cmd.Empty
| SaveUser ->
// match on id = 0 to see if it's a new user
match model.scratchUser.id with
| 0u -> model, Cmd.ofExistingPromise (createUser model.admin model.scratchUser) id (box>>ErrorOccured)
| 0u -> { model with PreviousScratchUser = model.scratchUser}, Cmd.ofExistingPromise (createUser model.admin model.scratchUser) id (box>>ErrorOccured)
| _ -> model, Cmd.ofExistingPromise (saveUser model.admin model.scratchUser) id (box>>ErrorOccured)
| ErrorOccured msg ->
//Browser.console.log msg
+1
View File
@@ -13,6 +13,7 @@ type Model = {
userList: BTSUser list
accountsList: BTSAccount list
scratchUser:BTSUser
PreviousScratchUser:BTSUser
currentTab:Tab
admin : IAdminService
adminData : AdminData option
+35 -21
View File
@@ -27,18 +27,28 @@ let isActiveUser id (currentUser:BTSUser) =
let saveButton (model:Users.Types.Model) dispatch =
let buttonText = if State.isNewAccount model.scratchUser then "Create" else "Save"
let dirty = State.userIsdirty State.defaultScratchUser model.scratchUser (State.getCurrentUser model.scratchUser model.userList)
let dirty = State.userIsdirty State.defaultScratchUser model.scratchUser (State.getCurrentUser model)
let enabled = if dirty then Button.Enabled else Button.Disabled
let flavor = if dirty then "is-success" else "is-light"
Button.button buttonText flavor "" enabled (fun _ -> dispatch SaveUser)
let generalTabContent (model:Model) (dispatch:Msg->Unit) =
let modal = Modal.showCreate
"Create User"
(div [][
Fields.text "Name" model.scratchUser.name [ ] (Name >> UpdateField >> dispatch)
Fields.password "Password" model.scratchUser.password [ ] (Password >> UpdateField >> dispatch)
Fields.checkbox "Active" "Allows the user to login and access information" model.scratchUser.isActive [] (IsActive >> UpdateField >> dispatch)
])
(fun _ -> dispatch SaveUser)
(fun _ -> SwitchUser (State.getCurrentUser model) |> dispatch)
div [] [
p [ Style [FontWeight "Bold"] ] [ str ("ID: " + model.scratchUser.id.ToString())]
br []
Fields.text "Name" model.scratchUser.name [ ] (Name >> UpdateField >> dispatch)
Fields.password "Password" model.scratchUser.password [ ] (Password >> UpdateField >> dispatch)
Fields.checkbox "Active" "Allows the user to login and access information" model.scratchUser.isActive [] (IsActive >> UpdateField >> dispatch)
(if State.isNewAccount model.scratchUser then modal else span[][])
]
let accountNameLookup (accountsList:BTSAccount list) (id:string) =
@@ -52,19 +62,25 @@ let accountsTabContent (model:Model) (dispatch:Msg->Unit) =
let keys =
Global.Reflection.keys accountPermissions
|> Array.map (fun i -> i, accountNameLookup model.accountsList i)
let headerRow:TableControl.row =
{
cells =
[
TableControl.Cell(str "")
TableControl.Cell(str "Account")
TableControl.Cell(str "Trading Permission")
TableControl.Cell(str "Admin Permission")
]
className = ""
header = None
}
let rws:TableControl.row list =
[for (key,name) in keys do yield {
cells = [
{
content = Button.fabutton "" "times" Button.Enabled (fun _ -> key |> (RemoveAccount >> UpdateField >> dispatch))
className = ""
isHeader = false
}
{
content = str name
className = ""
isHeader = false
}
TableControl.Cell(Button.fabutton "" "times" Button.Enabled (fun _ -> key |> (RemoveAccount >> UpdateField >> dispatch)))
TableControl.Cell(str name)
TableControl.Cell( str "Full")
TableControl.Cell(str "Full")
]
className=""
header = None
@@ -79,12 +95,13 @@ let accountsTabContent (model:Model) (dispatch:Msg->Unit) =
|> Map.ofList
let rows =
if not (Map.isEmpty accMap) then
rws @ [{cells = [ {content = Fields.mapSelect "" accMap (Some "Add Account") None (AddAccount >> UpdateField >> dispatch); className = ""; isHeader = true } ]; className = ""; header = None }]
else rws
rws @ [{cells = [
{ content = Fields.mapSelect "" accMap (Some "Add Account") None (AddAccount >> UpdateField >> dispatch); className = ""; isHeader = false; colSpan = 4 }
]; className = ""; header = None }]
div []
[
TableControl.createTable None rows
TableControl.createTable (Some headerRow) rows
]
let mdEntitlementsTabContent (model:Model) (dispatch:Msg->Unit) =
@@ -190,11 +207,8 @@ let usersMenu (usersList:BTSUser list) (currentUser:BTSUser) (dispatch:Msg->Unit
]
let root (model:Model) (dispatch:Msg->Unit) =
let definitelyAUser =
match (State.getCurrentUser model.scratchUser model.userList) with
| Some useThis -> useThis
| None -> State.defaultScratchUser
let dirty = State.userIsdirty State.defaultScratchUser model.scratchUser (State.getCurrentUser model.scratchUser model.userList)
let dirty = State.userIsdirty State.defaultScratchUser model.scratchUser (State.getCurrentUser model)
(match model.adminData with
| None -> div[][str "Loading user list..."]
@@ -229,7 +243,7 @@ let root (model:Model) (dispatch:Msg->Unit) =
div [ ClassName "column is-narrow" ]
[
yield saveButton model dispatch
yield Button.button "Revert" "is-light" "" (if dirty then Button.Enabled else Button.Disabled) (fun _ -> SwitchUser definitelyAUser |> dispatch)
yield Button.button "Revert" "is-light" "" (if dirty then Button.Enabled else Button.Disabled) (fun _ -> SwitchUser (State.getCurrentUser model) |> dispatch)
]
]
tabContent model dispatch
+1
View File
@@ -40,6 +40,7 @@
<Compile Include="Controls/DataGrid.fs" />
<Compile Include="Controls/Fields.fs" />
<Compile Include="Controls/Table.fs" />
<Compile Include="Controls/Modal.fs" />
<Compile Include="Access.fs" />
<!-- Login -->
<Compile Include="Login/Login.fs" />
+2
View File
@@ -2,4 +2,6 @@
setlocal
@call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\Tools\VsDevCmd.bat" > nul
cd /d %~dp0/../
setlocal
set path=%spark_build_python_path%;%path%
msbuild /m /nologo /p:debugtype=portable /p:platform=x64 /p:GenerateFullPaths=true /clp:verbosity=minimal /clp:summary %* spark_client.sln
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
cd %~dp0\..
pushd %~dp0\..
+6
View File
@@ -0,0 +1,6 @@
@ echo off
REM wow....doskey is still around...
doskey bc=pskill spark.exe ^& buildspark
doskey bs=killsparkd ^& buildsparkd
doskey ba=pskill spark.exe ^& buildspark ^&^& killsparkd ^& buildsparkd
+2 -1
View File
@@ -3,4 +3,5 @@ setlocal
cd %~dp0\..
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\Tools\VsDevCmd.bat" > nul
cd /d %~dp0\..
cmd /C start "" "C:\Program Files\Microsoft VS Code\Code.exe" "%*" -n
setlocal
cmd /C start "" "C:\Program Files\Microsoft VS Code\Code.exe" "%*"