Compare commits

..
101 Commits
Author SHA1 Message Date
John Colvin 1ec2423ad2 SPARK-1215 fills performance - do not copy dictionaries in FillTupleSource 2018-06-07 21:17:58 -04:00
Taha Afzal 1a88e52a08 subtle change in the QuoterWaitingForAck where book changes on the hedging side make it dissapear, hence order needs to be pulled 2018-06-07 20:06:40 -04:00
Taha Afzal a1b52d083d SPARK-1200 2018-06-07 17:50:09 -04:00
Michael Harding f364317cfa Better window border compromise 2018-06-07 17:10:16 -04:00
Michael Harding f474af1925 Update a few manual tests 2018-06-07 16:09:53 -04:00
Michael Harding 03ac5035fc AdminUI: Minor tweaks and fixes 2018-06-07 16:09:53 -04:00
Michael Harding e78d8208fa AdminUI: Add application name to login page 2018-06-07 16:09:53 -04:00
Pedro Pinto 7f8c591f75 Instrumenting server for perf measurements. 2018-06-07 13:58:02 -04:00
Taha Afzal 7d9685f995 SPARK-1211 cor id 2018-06-07 13:11:27 -04:00
John Colvin 699736ff52 Merged in badge-style-row-height-unification (pull request #35)
SPARK-1204 Ladder/Badge - row height needs to fit different badge styles

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2018-06-07 16:36:03 +00:00
John Colvin 8d0e7bc467 pr finishing touch 2018-06-07 12:35:03 -04:00
Michael Harding df784e9961 SPARK-1201: Fix and style table headers 2018-06-07 10:21:14 -04:00
Michael Harding af39aab153 AdminUI: Fix default security symbol for new rows 2018-06-07 10:21:13 -04:00
Taha Afzal 885c1ad9f8 SPARK-1152 remove limit in release.... 2018-06-06 23:26:07 -04:00
Eric Harding 91fdd6ec58 Improve generated structs by adding C# 7.2 readonly keyword 2018-06-06 20:21:20 -04:00
John Colvin 3adfd7056a Merge branch 'master' into badge-style-row-height-unification 2018-06-06 18:48:18 -04:00
Kevin Darby 5f845f1355 python 2.7 vs 3.6 blues 2018-06-06 17:56:53 -04:00
Pedro Pinto 5d4bb1d421 More server side perf instrumentation. 2018-06-06 17:53:18 -04:00
John Colvin 9836e46dd9 SPARK-1092 Inconsitencies in changing spreader params / probably lack of persistence 2018-06-06 17:21:42 -04:00
John Colvin bf9d5c2c6d ExecutionStrategyEditor - fix vertical grid line adorner 2018-06-06 17:21:42 -04:00
Taha Afzal ef1c3a7959 SPARK-1159 admin tool strange state 2018-06-06 16:59:35 -04:00
Taha Afzal b07f6aba59 comments, code style 2018-06-06 16:59:35 -04:00
Eric Harding a7b3b0fa6d SPARK-1197 MarketSegmentID 2018-06-06 16:55:03 -04:00
Jake Norbut f7a3b3ebc1 Fixing badge height overlapping level line 2018-06-06 15:49:46 -05:00
Michael Harding d291e4a022 SPARK-1201: Buttons, Transforms, and Stars 2018-06-06 16:13:51 -04:00
Pedro Pinto 292e5575d0 Made sim not look for secure logon file. 2018-06-06 16:06:43 -04:00
Pedro Pinto 1ad57dbf45 Instrumenting server side for performance tracking. 2018-06-06 16:06:43 -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
John Colvin 837aa51adf SPARK-1204 pr edits 2018-06-05 18:02:32 -04: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
John Colvin 410e17aec5 SPARK-1204 Ladder/Badge - row height needs to fit different badge styles 2018-06-05 16:46:50 -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
142 changed files with 4652 additions and 1239 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) ^
@@ -15,34 +15,33 @@
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
<ResourceDictionary Source="/Modules/Spreader/ParameterSectionView.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="GroupLevelIndent">18</sys:Double>
<sys:Double x:Key="RowHeaderWidth">285</sys:Double>
<sys:Double x:Key="RowHeight">16</sys:Double>
<sys:Double x:Key="NegativeRowHeight">-16</sys:Double>
<sys:Double x:Key="ItemsHostCanBeDisabledCollapsedHeight">15</sys:Double>
<Thickness
x:Key="GroupLevelMargin"
Left="{StaticResource GroupLevelIndent}"
Left="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
/>
<Thickness
x:Key="GroupContentAlignedWithHeaderMargin"
Left="{StaticResource GroupLevelIndent}"
Left="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
Top="{StaticResource NegativeRowHeight}"
/>
<cv:SyntheticPriceCalcDisplayTextConverter x:Key="SyntheticPriceCalcDisplayConverter"/>
<cv:TickFactorsTextConverter x:Key="TickFactorsTextConverter"/>
<usp:RowHeaderGroupIndentConverter
x:Key="RowHeaderGroupIndentConverter"
Indent="{StaticResource GroupLevelIndent}"
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
/>
<usp:RowHeaderGroupIndentConverter
x:Key="ReverseRowHeaderGroupIndentConverter"
Indent="{StaticResource GroupLevelIndent}"
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
Reverse="True"
/>
<usp:RowGroupExpanderWidthConverter
x:Key="RowGroupExpanderWidthConverter"
Indent="{StaticResource GroupLevelIndent}"
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
Offset="{StaticResource RowHeight}"
RowHeaderWidth="{StaticResource RowHeaderWidth}"
/>
@@ -293,7 +292,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 +307,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 +320,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,13 @@ 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";
public const double GroupLevelIndent = 18;
static ExecutionStrategyEditor() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExecutionStrategyEditor), new FrameworkPropertyMetadata(typeof(ExecutionStrategyEditor)));
@@ -196,7 +202,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 +210,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 +246,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;
}
}
@@ -25,7 +25,10 @@ namespace mbts.ui.spark
void _items_presenter_SizeChanged(object sender, SizeChangedEventArgs e) { InvalidateVisual(); }
protected override void draw_vertical_line(DrawingContext dc, double x, DataGridExVerticalLine line) {
if (_items_presenter != null) dc.DrawLine(line.pen.Value, new Point(x - 10, 58), new Point(x - 10, _items_presenter.ActualHeight + 58));
x -= GroupLevelIndent;
var start = _grid.TranslatePoint(new Point(x, _grid.super_header_height + _col_header_presenter.ActualHeight), this);
var stop = _grid.TranslatePoint(new Point(x, _grid.ActualHeight), this);
dc.DrawLine(line.pen.Value, start, stop);
}
}
}
@@ -24,13 +24,13 @@ namespace mbts.spark {
public FillsVM(IBus bus, OrdersModel model, StartupData state)
: this(bus, model, _get_observable(bus, model, state), state.sorting_criteria, state.filters) { }
FillsVM(IBus bus, OrdersModel model, IObservable<ITableUpdateTransform<FillTuple, string>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
: base(RXCollection.CreateTableUpdate(fills_source), sorting_criteria) {
FillsVM(IBus bus, OrdersModel model, IObservable<IIncrementalUpdate<FillTuple, string>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
: base(RXCollection.CreateIncrementalUpdate(fills_source), sorting_criteria) {
_model = model;
account_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_accounts(), r => r.account, nameof(Row.account));
user_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_users(), r => r.user, nameof(Row.user));
security_filter = DiscreteValuesFilterVM.Create<Row, SecurityImmutable, SecurityCriteria>(
fills_source.with_respect_to(ft => ft.security ?? SecurityDataHelper.Defaults.SecurityImmutable, s => s.Id, ft => ft.key).Select(t => (t.added, t.removed)),
fills_source.with_respect_to(ft => ft.security ?? SecurityDataHelper.Defaults.SecurityImmutable, s => s.Id, ft => ft.key).Select(t => (t.added.Values, t.removed.Values)),
r => r.security,
SecurityCriteria.from_securities,
SecurityCriteria.get_to_securities(bus),
@@ -126,7 +126,7 @@ namespace mbts.spark {
protected override Row row_factory(FillTuple row_model) => new Row(row_model, _model);
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.fill_time), false); }
static IObservable<ITableUpdateTransform<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
static IObservable<IIncrementalUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
if (state.orders != null && state.orders.Length == 0) {
var orders_hash = new HashSet<uint>(state.orders);
return model.on_fill_tuples.filter(f => orders_hash.Contains(f.fill.OrderKey));
@@ -77,6 +77,7 @@ namespace bts.shell {
s.ask_break_line = LadderSettings.Default.ask_break_line;
if (s.mid_break_line == null)
s.mid_break_line = LadderSettings.Default.mid_break_line;
s.row_height = LadderRender.measure_row_height(s);
return s;
}
}
+92 -64
View File
@@ -7,18 +7,19 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
namespace mbts.spark {
class FillTupleSource : IDisposable {
public static IObservable<ITableUpdateTransform<FillTuple, string>> create_observable(
public static IObservable<IIncrementalUpdate<FillTuple, string>> create_observable(
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
IObservable<TableUpdate<FillImmutable, string>> fills
) {
FillTupleSource source = null;
int ref_count = 0;
return Observable.Create<TableUpdateTransform<FillTuple, string>>(observer => {
return Observable.Create<IIncrementalUpdate<FillTuple, string>>(observer => {
if (Interlocked.Increment(ref ref_count) == 1) {
SparkUtils.lazy_assert(source == null, () => fmt_assert_msg("source should be null, there must be a leak."));
source = new FillTupleSource(securities, orders, fills);
@@ -40,35 +41,38 @@ namespace mbts.spark {
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
IObservable<TableUpdate<FillImmutable, string>> fills) {
State state = new State();
var src = Observable.FromEvent<Action<TableUpdateTransform<FillTuple, string>>, TableUpdateTransform<FillTuple, string>>(cb => _updated += cb, cb => _updated -= cb);
fill_tuples = Observable.Create<TableUpdateTransform<FillTuple, string>>(observer => {
var init_state = state.fill_tuples.state.ToDictionary();
observer.OnNext(new TableUpdateTransform<FillTuple, string>(init_state, init_state.Values.ToList(), new List<FillTuple>(0)));
return src.Subscribe(observer);
fill_tuples = Observable.Create<IIncrementalUpdate<FillTuple, string>>(observer => {
observer.OnNext(new IncrementalUpdate<FillTuple, string>(state.full_state.ToDictionary(), new Dictionary<string, FillTuple>(0)));
return _subject.Subscribe(observer);
});
_dispose = Disposable.Create(
securities.ObserveOnDispatcher().Subscribe(s => update(state.update_securities(s))),
orders.ObserveOnDispatcher().Subscribe(o => update(state.update_orders(o))),
fills.ObserveOnDispatcher().Subscribe(f => update(state.update_fills(f)))
);
void update(State new_state) {
state = new_state;
_updated?.Invoke(state.fill_tuples);
}
securities.ObserveOnDispatcher().Subscribe(s => {
state.update_securities(s);
_subject.OnNext(state.last_update);
}),
orders.ObserveOnDispatcher().Subscribe(o => {
state.update_orders(o);
_subject.OnNext(state.last_update);
}),
fills.ObserveOnDispatcher().Subscribe(f => {
state.update_fills(f);
_subject.OnNext(state.last_update);
}));
}
event Action<TableUpdateTransform<FillTuple, string>> _updated;
readonly Subject<IIncrementalUpdate<FillTuple, string>> _subject = new Subject<IIncrementalUpdate<FillTuple, string>>();
IObservable<TableUpdateTransform<FillTuple, string>> fill_tuples { get; }
IObservable<IIncrementalUpdate<FillTuple, string>> fill_tuples { get; }
public void Dispose() =>
Disposable.dispose(ref _dispose);
class State {
readonly IReadOnlyDictionary<string, FillImmutable> _fills;
readonly IReadOnlyDictionary<uint, OrderRecordImmutable> _orders;
readonly IReadOnlyDictionary<uint, SecurityMD> _securities;
readonly TableUpdateTransform<FillTuple, string> _fill_tuples;
IReadOnlyDictionary<string, FillImmutable> _fills;
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders;
IReadOnlyDictionary<uint, SecurityMD> _securities;
IncrementalUpdate<FillTuple, string> _last_update;
readonly Dictionary<string, FillTuple> _state;
readonly SetMap<uint, string> _order_fills;
readonly SetMap<uint, string> _security_fills;
@@ -76,7 +80,8 @@ namespace mbts.spark {
new Dictionary<string, FillImmutable>(),
new Dictionary<uint, OrderRecordImmutable>(),
new Dictionary<uint, SecurityMD>(),
new TableUpdateTransform<FillTuple, string>(new Dictionary<string, FillTuple>(), new List<FillTuple>(), new List<FillTuple>()),
new IncrementalUpdate<FillTuple, string>(new Dictionary<string, FillTuple>(), new Dictionary<string, FillTuple>()),
new Dictionary<string, FillTuple>(),
new SetMap<uint, string>(),
new SetMap<uint, string>()
) { }
@@ -85,94 +90,117 @@ namespace mbts.spark {
IReadOnlyDictionary<string, FillImmutable> fills,
IReadOnlyDictionary<uint, OrderRecordImmutable> orders,
IReadOnlyDictionary<uint, SecurityMD> securities,
TableUpdateTransform<FillTuple, string> fill_tuples,
IncrementalUpdate<FillTuple, string> last_update,
Dictionary<string, FillTuple> state,
SetMap<uint, string> order_fill_tuples,
SetMap<uint, string> security_fill_tuples
) {
_fills = fills;
_orders = orders;
_securities = securities;
_fill_tuples = fill_tuples;
_last_update = last_update;
_state = state;
_order_fills = order_fill_tuples;
_security_fills = security_fill_tuples;
}
public TableUpdateTransform<FillTuple, string> fill_tuples => _fill_tuples;
public IncrementalUpdate<FillTuple, string> last_update => _last_update;
public IReadOnlyDictionary<string, FillTuple> full_state => _state;
public State update_fills(TableUpdate<FillImmutable, string> fills) {
var removed = fills.removed.Select(get_fill_tuple).ToList();
var added = fills.added.Select(get_fill_tuple).ToList();
var order_fills = _order_fills.clone();
var security_fills = _security_fills.clone();
public void update_fills(TableUpdate<FillImmutable, string> fills) {
var removed = fills.removed.ToDictionary(f => f.FillKey, get_fill_tuple);
var added = fills.added.ToDictionary(f => f.FillKey, get_fill_tuple);
HashSet<string> added_keys = new HashSet<string>(fills.added.Select(f => f.FillKey)),
removed_keys = new HashSet<string>(fills.removed.Select(f => f.FillKey));
foreach (var ft in removed) {
string k = ft.fill.FillKey;
if (!added_keys.Contains(k)) {
order_fills.remove(ft.fill.OrderKey, k);
security_fills.remove(ft.fill.SecurityKey, k);
foreach (var kv in removed) {
_state.Remove(kv.Key);
if (!added_keys.Contains(kv.Key)) {
_order_fills.remove(kv.Value.fill.OrderKey, kv.Key);
_security_fills.remove(kv.Value.fill.SecurityKey, kv.Key);
}
}
foreach (var ft in added) {
string k = ft.fill.FillKey;
if (!removed_keys.Contains(k)) {
order_fills.add(ft.fill.OrderKey, k);
security_fills.add(ft.fill.SecurityKey, k);
foreach (var kv in added) {
_state.Add(kv.Key, kv.Value);
if (!removed_keys.Contains(kv.Key)) {
_order_fills.add(kv.Value.fill.OrderKey, kv.Key);
_security_fills.add(kv.Value.fill.SecurityKey, kv.Key);
}
}
return new State(fills.state, _orders, _securities, _fill_tuples.update(removed, added, f => f.key), order_fills, security_fills);
_fills = fills.state;
_last_update = new IncrementalUpdate<FillTuple, string>(added, removed);
//
FillTuple get_fill_tuple(FillImmutable fill) =>
new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(),
_securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
}
public State update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
List<FillTuple> added = new List<FillTuple>(), removed = new List<FillTuple>();
orders.detect_changed_keys(o => o.Key, out HashSet<uint> removed_keys, out HashSet<uint> added_keys, out HashSet<uint> changed_keys);
foreach (var k in removed_keys) {
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
removed.AddRange(get_fill_tuples(_orders[k], fill_keys));
added.AddRange(get_fill_tuples(null, fill_keys));
public void update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
Dictionary<string, FillTuple> added = new Dictionary<string, FillTuple>(), removed = new Dictionary<string, FillTuple>();
Dictionary<uint, (OrderRecordImmutable oldval, OrderRecordImmutable newval)> changed_orders = new Dictionary<uint, (OrderRecordImmutable oldval, OrderRecordImmutable newval)>();
foreach (var o in orders.removed) {
if (orders.try_get(o.Key, out OrderRecordImmutable newval))
changed_orders.Add(o.Key, (o, newval));
else if (_order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
foreach (FillTuple ft in get_fill_tuples(o, fill_keys))
removed.Add(ft.key, ft);
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
added.Add(ft.key, ft);
}
}
foreach (var k in added_keys) {
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
removed.AddRange(get_fill_tuples(null, fill_keys));
added.AddRange(get_fill_tuples(orders.get(k), fill_keys));
foreach (var o in orders.added) {
if (!changed_orders.ContainsKey(o.Key) && _order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
removed.Add(ft.key, ft);
foreach (FillTuple ft in get_fill_tuples(o, fill_keys))
added.Add(ft.key, ft);
}
}
foreach (var k in changed_keys) {
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
removed.AddRange(get_fill_tuples(_orders[k], fill_keys));
added.AddRange(get_fill_tuples(orders.get(k), fill_keys));
foreach (var kv in changed_orders) {
if (_order_fills.try_lookup(kv.Key, out ISet<string> fill_keys)) {
var fs_pairs = get_fill_security_pairs(fill_keys).ToList();
foreach (FillTuple ft in fs_pairs.Select(fsp => new FillTuple(fsp.fill, kv.Value.oldval, fsp.security)))
removed.Add(ft.key, ft);
foreach (FillTuple ft in fs_pairs.Select(fsp => new FillTuple(fsp.fill, kv.Value.newval, fsp.security)))
added.Add(ft.key, ft);
}
}
return new State(_fills, orders.state, _securities, _fill_tuples.update(removed, added, ft => ft.key), _order_fills, _security_fills);
_orders = orders.state;
_last_update = new IncrementalUpdate<FillTuple, string>(added, removed);
//
IEnumerable<FillTuple> get_fill_tuples(OrderRecordImmutable? o, ISet<string> fill_keys) =>
fill_keys.Select(k => {
var fill = _fills[k];
return new FillTuple(fill, o, _securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
});
IEnumerable<(FillImmutable fill, SecurityImmutable? security)> get_fill_security_pairs(ISet<string> fill_keys) =>
fill_keys.Select(k => {
var fill = _fills[k];
return (fill, _securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
});
}
public State update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
List<FillTuple> added = new List<FillTuple>(), removed = new List<FillTuple>();
public void update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
Dictionary<string, FillTuple> added = new Dictionary<string, FillTuple>(), removed = new Dictionary<string, FillTuple>();
_securities.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
foreach (var k in removed_keys) {
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
removed.AddRange(get_fill_tuples(_securities[k].security, fill_keys));
added.AddRange(get_fill_tuples(null, fill_keys));
foreach (FillTuple ft in get_fill_tuples(_securities[k].security, fill_keys))
removed.Add(ft.key, ft);
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
added.Add(ft.key, ft);
}
}
foreach (var k in added_keys) {
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
removed.AddRange(get_fill_tuples(null, fill_keys));
added.AddRange(get_fill_tuples(securities[k].security, fill_keys));
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
removed.Add(ft.key, ft);
foreach (FillTuple ft in get_fill_tuples(securities[k].security, fill_keys))
added.Add(ft.key, ft);
}
}
return new State(_fills, _orders, securities, _fill_tuples.update(removed, added, f => f.key), _order_fills, _security_fills);
_securities = securities;
_last_update = new IncrementalUpdate<FillTuple, string>(added, removed);
//
IEnumerable<FillTuple> get_fill_tuples(SecurityImmutable? s, ISet<string> fill_keys) =>
fill_keys.Select(k => {
@@ -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();
@@ -1,15 +1,17 @@
using bts.api;
using bts.api.orderman;
using mbts.ui;
using mbts.ui.controls;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
private class SpreaderOrderVM : OrderTypeVMBase<SpreaderOrderDefinition>, IOrderTypeVM, IDisposable {
readonly Property<SpreaderOrderDefinitionImmutable?> _template;
readonly StrategyEditorWrapper _strategy_editor_wrapper;
IDisposable _dispose;
SyntheticEditorVM _strategy_editor;
public SpreaderOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
: this(model, security_source, security_source.SelectMany(s => model.spreader.security_strategy(s.Id)
@@ -26,15 +28,15 @@ namespace mbts.spark {
IObservable<InlineUserProperty<SpreaderOrderDefinitionImmutable>> order_template_source)
: base(model) {
_template = backing(nameof(template), new SpreaderOrderDefinitionImmutable?());
_strategy_editor_wrapper = new StrategyEditorWrapper(this);
_dispose = Disposable.Create(
_strategy_editor_wrapper,
_template.ObserveOnDispatcher().Subscribe(vals => {
_validate();
}),
order_template_source.ObserveOnDispatcher().Subscribe(prop => {
if (prop.force_override || (!template.HasValue && prop.has_value)) {
_template.Value = prop.value;
if (_strategy_editor != null)
_strategy_editor.set_synthetic(prop.value.Security, prop.value.ExecutionStrategy);
_validate();
}
})
@@ -43,23 +45,14 @@ namespace mbts.spark {
}
public SpreaderOrderDefinitionImmutable? template => _template.Value;
public SyntheticEditorVM strategy_editor {
get {
if (_strategy_editor == null && template.HasValue) {
var def = template.Value;
_strategy_editor = new SyntheticEditorVM(model.spreader, def.Security, def.ExecutionStrategy, model.security_has_working_orders(def.Security.Id));
}
return _strategy_editor;
}
}
public SyntheticEditorVM strategy_editor => _strategy_editor_wrapper.vm;
public IEnumerable<DialogSkirtItem> skirt_items => _strategy_editor_wrapper.skirt_items;
public override bool editable_properties_open {
get => base.editable_properties_open;
set {
if (!value && template.HasValue && _strategy_editor != null) {
if (!value && template.HasValue && strategy_editor != null) {
var mutable_template = template.Value.CloneToMutable();
mutable_template.ExecutionStrategy = _strategy_editor.get_strategy().CloneToMutable();
mutable_template.ExecutionStrategy = _strategy_editor_wrapper.vm.get_strategy().CloneToMutable();
_template.Value = new SpreaderOrderDefinitionImmutable(mutable_template);
}
base.editable_properties_open = value;
@@ -80,6 +73,39 @@ namespace mbts.spark {
set_error(nameof(template), "Waiting for security and/or strategy.", true);
else clear_error(nameof(template));
}
// lazily initializes SyntheticEditorVM and associated dialog skirt items
class StrategyEditorWrapper : IDisposable {
readonly SpreaderOrderVM _owner;
SyntheticEditorVM _vm;
DialogSkirtItem _save_strategy_item;
public StrategyEditorWrapper(SpreaderOrderVM owner) {
_owner = owner;
_owner._template.Changed += _order_template_changed;
}
public SyntheticEditorVM vm { get { _init(); return _vm; } }
public IEnumerable<DialogSkirtItem> skirt_items { get { _init(); yield return _save_strategy_item; } }
public void Dispose() {
Disposable.dispose(ref _vm);
_owner._template.Changed -= _order_template_changed;
}
void _init() {
if (_vm == null && _owner.template.HasValue) {
var def = _owner.template.Value;
_vm = new SyntheticEditorVM(_owner.model.spreader, def.Security, def.ExecutionStrategy, _owner.model.security_has_working_orders(def.Security.Id));
_save_strategy_item = new DialogSkirtItem("Save Strategy", _vm.commit);
}
}
void _order_template_changed(SpreaderOrderDefinitionImmutable? oldval, SpreaderOrderDefinitionImmutable? newval) {
if (_vm != null && newval.HasValue)
_vm.set_synthetic(newval.Value.Security, newval.Value.ExecutionStrategy);
}
}
}
}
}
@@ -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));
}
}
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ namespace mbts.spark {
public SpreaderModel spreader => _spreader;
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_orders { get; }
public IObservable<TableUpdate<FillImmutable, string>> on_fills { get; }
public IObservable<ITableUpdateTransform<FillTuple, string>> on_fill_tuples { get; }
public IObservable<IIncrementalUpdate<FillTuple, string>> on_fill_tuples { get; }
public IObservable<IReadOnlyDictionary<uint,OrderRecordImmutable>> working_orders { get; }
public IObservable<ITableUpdateTransform<OrderRecordImmutable, uint>> on_security_orders(uint security) =>
@@ -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 {
@@ -54,5 +54,33 @@
rv |= try_convert(args[1], out arg2);
return rv;
}
public static bool try_convert_args<T1, T2, T3>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3) {
if (args == null || args.Length != 3) {
arg1 = default;
arg2 = default;
arg3 = default;
return false;
}
bool rv = try_convert(args[0], out arg1);
rv |= try_convert(args[1], out arg2);
rv |= try_convert(args[2], out arg3);
return rv;
}
public static bool try_convert_args<T1, T2, T3, T4>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) {
if (args == null || args.Length != 4) {
arg1 = default;
arg2 = default;
arg3 = default;
arg4 = default;
return false;
}
bool rv = try_convert(args[0], out arg1);
rv |= try_convert(args[1], out arg2);
rv |= try_convert(args[2], out arg3);
rv |= try_convert(args[3], out arg4);
return rv;
}
}
}
@@ -7,12 +7,27 @@ using System.Linq;
using System.Reactive.Linq;
namespace mbts.utils {
interface IIncrementalUpdate<T, TKey> where TKey : IEquatable<TKey> {
IReadOnlyDictionary<TKey, T> added { get; }
IReadOnlyDictionary<TKey, T> removed { get; }
}
interface ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
IReadOnlyDictionary<TKey, T> state { get; }
IReadOnlyList<T> added { get; }
IReadOnlyList<T> removed { get; }
}
class IncrementalUpdate<T, TKey> : IIncrementalUpdate<T, TKey> where TKey : IEquatable<TKey> {
public IncrementalUpdate(IReadOnlyDictionary<TKey, T> added, IReadOnlyDictionary<TKey, T> removed) {
this.added = added;
this.removed = removed;
}
public IReadOnlyDictionary<TKey, T> added { get; }
public IReadOnlyDictionary<TKey, T> removed { get; }
}
class TableUpdateTransform<T, TKey> : ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
readonly Dictionary<TKey, T> _state;
readonly IReadOnlyList<T> _added, _removed;
@@ -44,6 +59,15 @@ namespace mbts.utils {
table._state.Add(key(item), item);
return table;
}
public TableUpdateTransform<T, TKey> update(IIncrementalUpdate<T, TKey> update, Func<T, TKey> key) {
var table = new TableUpdateTransform<T, TKey>(new Dictionary<TKey, T>(_state), added, removed);
foreach (var k in update.removed.Keys)
table._state.Remove(k);
foreach (var kv in update.added)
table._state.Add(kv.Key, kv.Value);
return table;
}
}
[Browsable(false)]
@@ -69,6 +93,33 @@ namespace mbts.utils {
source.Scan(new PivotTableUpdateTransform<T, TKey, TResult, TResultKey>(selector, result_key, key),
(pivot, table) => pivot.update(table));
/// <summary>
/// Converts sequence to show incremental changes with respect to the [TResult] value returned by result_selector.
/// </summary>
public static IObservable<IIncrementalUpdate<TResult, TResultKey>> with_respect_to<T, TKey, TResult, TResultKey>(
this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key)
where TKey : IEquatable<TKey>
where TResultKey : IEquatable<TResultKey> =>
//
source.Scan(new PivotIncrementalUpdate<T, TKey, TResult, TResultKey>(selector, result_key, key),
(pivot, table) => pivot.update(table));
/// <summary>
/// Uses a Scan operator to accumulate state in the resulting ITableUpdateTransform sequence.
/// </summary>
public static IObservable<ITableUpdateTransform<T, TKey>> table<T, TKey>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TKey> key) where TKey : IEquatable<TKey> =>
source.Scan(
new TableUpdateTransform<T, TKey>(),
(table, update) => table.update(update, key));
/// <summary>
/// Filters the incremental added/removed lists by a predicate. Observers are only notified when the filtered added/removed lists have elements.
/// </summary>
public static IObservable<IIncrementalUpdate<T, TKey>> filter<T, TKey>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, bool> predicate)
where TKey : IEquatable<TKey> => source
.Select(u => new IncrementalUpdate<T, TKey>(u.added.Where(kv => predicate(kv.Value)).ToDictionary(), u.removed.Where(kv => predicate(kv.Value)).ToDictionary()))
.Where(u => u.added.Count > 0 || u.removed.Count > 0);
class FilteredTableUpdateTransform<T, TKey> : ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
internal FilteredTableUpdateTransform(ITableUpdateTransform<T, TKey> source, Func<T, bool> predicate) {
state = source.state;
@@ -81,6 +132,57 @@ namespace mbts.utils {
public IReadOnlyList<T> removed { get; }
}
class PivotIncrementalUpdate<T, TKey, TResult, TResultKey> : IIncrementalUpdate<TResult, TResultKey> where TKey : IEquatable<TKey> where TResultKey : IEquatable<TResultKey> {
readonly Dictionary<TResultKey, TResult> _state;
readonly SetMap<TResultKey, TKey> _key_map;
readonly Func<T, TResult> _selector;
readonly Func<TResult, TResultKey> _result_key;
readonly Func<T, TKey> _key;
internal PivotIncrementalUpdate(Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key) :
this(new Dictionary<TResultKey, TResult>(), new Dictionary<TResultKey, TResult>(0), new Dictionary<TResultKey, TResult>(0), new SetMap<TResultKey, TKey>(), selector, result_key, key) { }
PivotIncrementalUpdate(Dictionary<TResultKey, TResult> state, IReadOnlyDictionary<TResultKey, TResult> added, IReadOnlyDictionary<TResultKey, TResult> removed,
SetMap<TResultKey, TKey> key_map, Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key) {
//
_state = state;
this.added = added;
this.removed = removed;
_key_map = key_map;
_selector = selector;
_result_key = result_key;
_key = key;
}
public IReadOnlyDictionary<TResultKey, TResult> state => _state;
public IReadOnlyDictionary<TResultKey, TResult> added { get; }
public IReadOnlyDictionary<TResultKey, TResult> removed { get; }
public PivotIncrementalUpdate<T, TKey, TResult, TResultKey> update(IIncrementalUpdate<T, TKey> table) {
Dictionary<TResultKey, TResult> removed = new Dictionary<TResultKey, TResult>(), added = new Dictionary<TResultKey, TResult>();
var state_clone = new Dictionary<TResultKey, TResult>(_state);
foreach (var kv in table.removed) {
var result = _selector(kv.Value);
var rkey = _result_key(result);
if (_key_map.removes_key(rkey, kv.Key))
removed.Add(rkey, result);
}
foreach (var kv in table.added) {
var result = _selector(kv.Value);
var rkey = _result_key(result);
if (_key_map.adds_key(rkey, kv.Key)) {
if (!removed.Remove(rkey))
added.Add(rkey, result);
}
}
foreach (var k in removed.Keys)
state_clone.Remove(k);
foreach (var kv in added)
state_clone.Add(kv.Key, kv.Value);
return new PivotIncrementalUpdate<T, TKey, TResult, TResultKey>(state_clone, added, removed, _key_map, _selector, _result_key, _key);
}
}
class PivotTableUpdateTransform<T, TKey, TResult, TResultKey> : ITableUpdateTransform<TResult, TResultKey> where TKey : IEquatable<TKey> where TResultKey : IEquatable<TResultKey> {
readonly Dictionary<TResultKey, TResult> _state;
readonly SetMap<TResultKey, TKey> _key_map;
+191 -112
View File
@@ -1,141 +1,220 @@
using mbts.spark;
using mbts.ui.extensions;
using bts.api;
using mbts.spark;
using mbts.ui.utility.text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Data;
using System.Windows.Media;
using static mbts.config.Configuration;
using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.ui.controls {
public class Badge: Control {
readonly static double _dpi = get<bts.config.System>().dpi_ratio;
public class Badge : Control {
public const double LINE_SPACING = 1;
// Storing the defaults here for readability
// Warning: these properties are used to calculate the ladder's row_height. Please change the values below if you want to change properties that affect the layout.
// ** - value is not the actual PropertyMetadata.DefaultValue
public static readonly Thickness
DefaultMargin = new Thickness(4, 2, 4, 2), // **
DefaultPadding = new Thickness(1),
DefaultBorderThickness = new Thickness(2);
public static readonly double
DefaultCornerRadius = 1,
DefaultFontSize = 10,
DefaultWidth = 63; // **
public static readonly FontFamily DefaultFontFamily = new FontFamily("Consolas");
readonly GlyphContentRect _gcr = new GlyphContentRect();
Brush _background, _foreground, _border_brush;
Typeface _face;
FormattedText _center_text;
FormattedText _top_text;
FormattedText _bottom_text;
static Badge() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(typeof(Badge)));
WidthProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(15.0));
HeightProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(10.0));
FontSizeProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender /*| FrameworkPropertyMetadataOptions.AffectsMeasure*/, update_text));
ForegroundProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.White, update_text));
BackgroundProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender));
BorderThicknessProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(new Thickness(1)));
BorderBrushProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.Black));
FontFamilyProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(new FontFamily("Consolas"), FrameworkPropertyMetadataOptions.AffectsRender /*| FrameworkPropertyMetadataOptions.AffectsMeasure*/, update_text));
//TextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
//TopTextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
//BottomTextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
// MinWidth
WPFHelper.override_metadata<double, Badge>(
MinWidthProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.min_width = v,
0);
// Width
WPFHelper.override_metadata<double, Badge>(
WidthProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.width = v,
double.NaN);
// Max Width
WPFHelper.override_metadata<double, Badge>(
MaxWidthProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.max_width = v,
double.MaxValue);
// Min Height
WPFHelper.override_metadata<double, Badge>(
MinHeightProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.min_height = v,
0);
// Height
WPFHelper.override_metadata<double, Badge>(
HeightProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.height = v,
double.NaN);
// Max Height
WPFHelper.override_metadata<double, Badge>(
MaxHeightProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.max_height = v,
double.MaxValue);
// FontSize
WPFHelper.override_metadata<double, Badge>(
FontSizeProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.font_size = v,
DefaultFontSize);
// Foreground
WPFHelper.override_metadata<Brush, Badge>(
ForegroundProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._foreground = v,
Brushes.White);
// Background
WPFHelper.override_metadata<Brush, Badge>(
BackgroundProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._background = v,
Brushes.Transparent);
// BorderThickness
WPFHelper.override_metadata<Thickness, Badge>(
BorderThicknessProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.border_thickness = v,
DefaultBorderThickness);
// BorderBrush
WPFHelper.override_metadata<Brush, Badge>(
BorderBrushProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._border_brush = v,
Brushes.Black);
// FontFamily
WPFHelper.override_metadata<FontFamily, Badge>(
FontFamilyProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.glyph_context = b._gcr.glyph_context.with(v),
DefaultFontFamily);
// HoriontalAlignment
WPFHelper.override_metadata<HorizontalAlignment, Badge>(
HorizontalAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.horizontal_alignment = v,
HorizontalAlignment.Stretch);
// HoriontalContentAlignment
WPFHelper.override_metadata<HorizontalAlignment, Badge>(
HorizontalContentAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.horizontal_content_alignment = v,
HorizontalAlignment.Center);
// VerticalAlignment
WPFHelper.override_metadata<VerticalAlignment, Badge>(
VerticalAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.vertical_alignment = v,
VerticalAlignment.Center);
// VerticalContentAlignment
WPFHelper.override_metadata<VerticalAlignment, Badge>(
VerticalContentAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.vertical_content_alignment = v,
VerticalAlignment.Center);
// Padding
WPFHelper.override_metadata<Thickness, Badge>(
PaddingProperty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(p, v) => p._gcr.padding = v,
DefaultPadding);
}
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
format_text((b,v) => b._center_text= v)));
public string TopText {
get { return (string)GetValue(TopTextProperty); }
set { SetValue(TopTextProperty, value); }
}
public static readonly DependencyProperty TopTextProperty =
DependencyProperty.Register(nameof(TopText), typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
format_text((b, v) => b._top_text = v)));
public string BottomText {
get { return (string)GetValue(BottomTextProperty); }
set { SetValue(BottomTextProperty, value); }
public Badge() {
_gcr.min_width = MinWidth;
_gcr.width = Width;
_gcr.max_width = MaxWidth;
_gcr.min_height = MinHeight;
_gcr.height = Height;
_gcr.max_height = Height;
_gcr.padding = Padding;
_gcr.border_thickness = BorderThickness;
_gcr.horizontal_alignment = HorizontalAlignment;
_gcr.horizontal_content_alignment = HorizontalContentAlignment;
_gcr.vertical_alignment = VerticalAlignment;
_gcr.vertical_content_alignment = VerticalContentAlignment;
_background = Background;
_foreground = Foreground;
_border_brush = BorderBrush;
_gcr.font_size = FontSize;
_gcr.text_lines = TextLines;
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
}
public static readonly DependencyProperty BottomTextProperty =
DependencyProperty.Register(nameof(BottomText), typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
format_text((b,v)=>b._bottom_text=v)));
public static readonly DependencyProperty TextLinesProperty =
WPFHelper.create_dp<IReadOnlyList<string>, Badge>(
nameof(TextLines),
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(b, v) => b._gcr.text_lines = v,
new string[0]);
public IReadOnlyList<string> TextLines {
get => GetValue(TextLinesProperty) as IReadOnlyList<string>;
set => SetValue(TextLinesProperty, value);
}
public double CornerRadius {
get { return (double)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(double), typeof(Badge), new PropertyMetadata(3.0));
WPFHelper.create_dp<double, Badge>(nameof(CornerRadius), DefaultCornerRadius);
static PropertyChangedCallback format_text(Action<Badge,FormattedText> f) {
return (d, e) => {
Badge b = (Badge)d;
ensure_fontface(b);
string text = e.NewValue as string;
if (string.IsNullOrEmpty(text)) f(b, null);
f(b, new FormattedText(text,
System.Globalization.CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
b._face, b.FontSize, b.Foreground, _dpi));
b.InvalidateVisual();
};
protected override Size ArrangeOverride(Size finalSize) => finalSize;
protected override Size MeasureOverride(Size availableSize) {
_gcr.available_size = availableSize;
return _gcr.layout_size;
}
static FormattedText format_text(Badge badge, DependencyProperty source) {
FormattedText result = null;
var value = badge.GetValue(source);
format_text((b, v) => result = v)(badge, new DependencyPropertyChangedEventArgs(source, value, value));
return result;
}
static void update_text(DependencyObject d, DependencyPropertyChangedEventArgs args) {
// Force formatted text regeneration when font/color changes
Badge badge = (Badge)d;
badge._face = null;
ensure_fontface(badge);
badge._center_text = format_text(badge, TextProperty);
badge._top_text = format_text(badge, TopTextProperty);
badge._bottom_text = format_text(badge, BottomTextProperty);
}
private static void ensure_fontface(Badge b) {
b._face = b._face ?? new Typeface(b.FontFamily, b.FontStyle, b.FontWeight, b.FontStretch);
}
//protected override Size MeasureOverride(Size availableSize) {
// Double calc_height = 0.0;
// Double calc_width = 0.0;
// Size size = new Size(0, 0);
// Size center_size = new Size(_center_text.Width, _center_text.Height);
// Size top_size = new Size(_top_text.Width, _top_text.Height);
// Size bottom_size = new Size(_bottom_text.Width, _bottom_text.Height);
// calc_width = Math.Max(center_size.Width, top_size.Width);
// calc_width = Math.Max(calc_width, bottom_size.Width);
// calc_height += center_size.Height;
// calc_height += top_size.Height;
// calc_height += bottom_size.Height;
// size.Width = calc_width;
// size.Height = calc_height;
// return size;
//}
protected override void OnRender(DrawingContext dc) {
base.OnRender(dc);
double border_thickness = BorderThickness.Top;
var pen = new Pen(BorderBrush, border_thickness);
dc.DrawRoundedRectangle(Background, pen, new Rect(0, 0 + border_thickness, ActualWidth, ActualHeight - 2 * border_thickness), CornerRadius, CornerRadius);
if (_center_text != null)
dc.DrawText(_center_text, new Point((ActualWidth-_center_text.Width)/2, (ActualHeight-_center_text.Height)/2));
if (_top_text != null)
dc.DrawText(_top_text, new Point(border_thickness, border_thickness));
if (_bottom_text != null)
dc.DrawText(_bottom_text, new Point(border_thickness, ActualHeight - _bottom_text.Height - border_thickness));
dc.DrawRoundedRectangle(Background, pen, new Rect(0, border_thickness, _gcr.layout_size.Width, _gcr.layout_size.Height), CornerRadius, CornerRadius);
var gr = _gcr.glyph_run;
if (gr != null)
dc.DrawGlyphRun(_foreground, gr);
}
}
}
class BadgeTextLinesConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (ObjectHelper.try_convert_args(values, out Side side, out uint quantity, out uint filled, out mbts.spark.LadderSettings.BadgeStyle style))
return BadgeRender.format(side, quantity, filled, style);
else return new string[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
}
@@ -49,7 +49,7 @@ namespace mbts.spark.filters {
) => new DynamicItemsFilter<TRow, TValue, TState>(items, get_row_val, get_state, load_state, prop_name, equality_comparer, comparer);
public static IDataGridColumnPersistentFilter<TRow, TState> Create<TRow, TValue, TState>(
IObservable<(IReadOnlyList<TValue> added, IReadOnlyList<TValue> removed)> items,
IObservable<(IEnumerable<TValue> added, IEnumerable<TValue> removed)> items,
Func<TRow, TValue> get_row_val,
Func<ISet<TValue>, TState> get_state,
Func<TState, IObservable<IEnumerable<TValue>>> load_state,
@@ -122,7 +122,7 @@ namespace mbts.spark.filters {
}
public DynamicItemsFilter(
IObservable<(IReadOnlyList<TValue> added, IReadOnlyList<TValue> removed)> items,
IObservable<(IEnumerable<TValue> added, IEnumerable<TValue> removed)> items,
Func<TRow, TValue> get_row_val,
Func<ISet<TValue>, TState> get_state,
Func<TState, IObservable<IEnumerable<TValue>>> load_state,
@@ -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,21 +361,31 @@
/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
</usp:OrderModificationTemplateSelector.IcebergOrderTemplate>
<usp:OrderModificationTemplateSelector.SpreaderTemplate>
<DataTemplate>
<usp:ExecutionStrategyEditor
HorizontalAlignment="Stretch" Margin="6"
DataContext="{Binding strategy_editor}"
SyntheticName="{Binding name}"
IsNew="{Binding is_new}"
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
PriceCalc="{Binding price_calc}"
ParameterRows="{Binding rows}"
LegTuples="{Binding leg_tuples}"
RemoveLegCommand="{Binding remove_leg}"
ContractPickerModel="{Binding contract_picker_model}"
/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<usp:ExecutionStrategyEditor
Margin="6"
HorizontalAlignment="Stretch"
DataContext="{Binding strategy_editor}"
SyntheticName="{Binding name}"
TickFactors="{Binding tick_factors}"
AvailableTickFactors="{Binding available_tick_factors_view}"
IsNew="False"
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
PriceCalc="{Binding price_calc}"
ParameterRows="{Binding rows}"
LegTuples="{Binding leg_tuples}"
RemoveLegCommand="{Binding remove_leg}"
ContractPickerModel="{Binding contract_picker_model}"
/>
<c:DialogSkirt Grid.Row="1" Margin="-4" RightItems="{Binding skirt_items}"/>
</Grid>
</DataTemplate>
</usp:OrderModificationTemplateSelector.SpreaderTemplate>
</usp:OrderModificationTemplateSelector>
@@ -15,7 +15,7 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<c:Badge Background="{Binding Metadata, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource OrderTypeBrushConverter}}" Width="15" Height="10" HorizontalAlignment="Left" />
<c:Badge Background="{Binding Metadata, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource OrderTypeBrushConverter}}" BorderThickness="1" Width="15" Height="10" HorizontalAlignment="Left" />
<TextBlock Text="{Binding OrderType, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource OrderTypeDisplayTextConverter}}" Margin="20,0,0,0" HorizontalAlignment="Left" />
</Grid>
</Border>
@@ -22,7 +22,7 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Loaded="style_loaded" x:Name="window_border" u:WindowStyle.IsNonClientArea="true">
<Border BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Loaded="style_loaded" x:Name="window_border" u:WindowStyle.IsNonClientArea="true">
<Grid Margin="{TemplateBinding Margin}">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
@@ -134,8 +134,8 @@
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource self}}" Value="Normal">
<Setter Property="Padding" Value="0,0,0,0" />
<Setter Property="BorderThickness" Value="7,1,2.66,2.66" TargetName="window_border" />
<Setter Property="Padding" Value="0,0,2,2" />
<Setter Property="BorderThickness" Value="7,1,1,1" TargetName="window_border" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -149,6 +149,15 @@ namespace mbts.spark {
}
}
public static string abbreviate(this Side side) {
switch (side) {
case Side.Buy: return "B";
case Side.Sell: return "S";
case Side.Undefined:
default: return "E";
}
}
public static string order_sub_type_display_name(OrderSubType sub_type, bool use_long_name) {
switch(sub_type) {
case OrderSubType.HedgingLeg:
@@ -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,25 +58,26 @@ 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;
Brush get_foreground(LadderSettings s) => s.orders_column.foreground.rgb_brush();
_text = _bottom_line = _top_line = string.Empty;
//
add_order(o);
_dispose = Disposable.Create(
_bs.Subscribe( badge => {
_bs.Subscribe(badge => {
_current_badge_style = badge;
_set_badge_text();
})
);
//
add_order(o);
}
void _init() {
public void update() {
// update brushes
Color color;
switch (side) {
@@ -94,27 +97,23 @@ 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();
}
private void _set_badge_text() {
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;
case LadderSettings.BadgeStyle.Normal:
default:
text = $"{filled}/{quantity}"; break;
}
}
text_lines = BadgeRender.format(side, quantity, filled, _current_badge_style);
RaisePropertyChanged(nameof(text_lines));
}
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) {
@@ -137,57 +136,9 @@ namespace mbts.spark {
}
}
string _buy_sell_indicator {
get {
switch (side) {
case Side.Buy: return "B";
case Side.Sell: return "S";
case Side.Undefined:
default:
return "E";
}
}
}
IEnumerable<OrderRecordImmutable> _parent_and_child_not_sharing_slot() => _orders.Values.Where(o => !_orders.ContainsKey(o.ParentKey));
private string _text;
public string text {
get { return _text; }
private set {
_text = value;
if (value != string.Empty) {
top_line = string.Empty;
bottom_line = string.Empty;
}
RaisePropertyChanged(); }
}
private string _top_line;
public string top_line {
get { return _top_line; }
private set {
_top_line = value;
if (value != string.Empty) {
text = string.Empty;
}
RaisePropertyChanged();
}
}
private string _bottom_line;
public string bottom_line {
get { return _bottom_line; }
private set {
_bottom_line = value;
if (value != string.Empty) {
text = string.Empty;
}
RaisePropertyChanged();
}
}
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 IReadOnlyList<string> text_lines { get; private set; }
public bool has_parent => _orders.Values.Any( order => order.has_parent());
public bool has_phantom_price => !double.IsNaN(_phantom_price);
@@ -204,10 +155,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 +191,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()}";
}
}
}
}
@@ -143,8 +143,9 @@ namespace mbts.spark {
return price_cell.panel;
else if (_price_list != null && !double.IsNaN(_row_height)) {
var panel = new StackPanel() {
Margin = Badge.DefaultMargin,
Orientation = Orientation.Horizontal,
MaxWidth = _col_width,
MaxWidth = _col_width
//MaxHeight = _row_height,
//ClipToBounds = true
};
@@ -1,8 +1,8 @@
using bts.spark;
using mbts.spark.utilities;
using bts.api;
using mbts.ui;
using mbts.ui.controls.color_picker;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -14,6 +14,7 @@ namespace mbts.spark {
class LadderSettingsVM : ViewModelBase, IWindowSettingsVM<LadderSettings> {
readonly Property<LadderSettings.BadgeStyle> _badge_style;
readonly Property<uint> _alternate_order_size;
readonly DelegateProperty<double> _row_height;
LadderSettings _current;
LadderColumnSettingsVM
_orders = new OrdersColumnSettingsVM(),
@@ -30,6 +31,13 @@ namespace mbts.spark {
_badge_style = backing<LadderSettings.BadgeStyle>(nameof(badge_style));
_badge_style.Changed += _badge_style_Changed;
_alternate_order_size = backing<uint>(nameof(alternate_order_size));
_row_height = backing(nameof(row_height), () => LadderRender.measure_row_height(get_result()));
_orders.font_size_changed += on_font_size_changed;
_bid.font_size_changed += on_font_size_changed;
_price.font_size_changed += on_font_size_changed;
_ask.font_size_changed += on_font_size_changed;
_last.font_size_changed += on_font_size_changed;
void on_font_size_changed(double oldval, double newval) => _row_height.refresh();
}
private void _badge_style_Changed(LadderSettings.BadgeStyle oldval, LadderSettings.BadgeStyle newval) {
@@ -38,6 +46,7 @@ namespace mbts.spark {
} else if (newval == LadderSettings.BadgeStyle.Normal && _orders.font_size == 7) {
_orders.font_size = 10;
}
_row_height.refresh();
}
public LadderSettings.BadgeStyle badge_style {
@@ -50,6 +59,8 @@ namespace mbts.spark {
set => _alternate_order_size.Value = value;
}
public double row_height => _row_height.Value;
public IEnumerable<LadderColumnSettingsVM> column_items {
get {
yield return _orders;
@@ -89,7 +100,6 @@ namespace mbts.spark {
}
public LadderSettings get_result() {
const double row_padding = 12;
var clone = SparkUtils.clone_proto(_current);
clone.badge_style = badge_style;
clone.alternate_order_size = alternate_order_size;
@@ -101,13 +111,7 @@ namespace mbts.spark {
clone.bid_break_line = _bid_mkt_line.get_settings();
clone.ask_break_line = _ask_mkt_line.get_settings();
clone.mid_break_line = _mid_mkt_line.get_settings();
clone.row_height = new[] {
clone.orders_column,
clone.bid_column,
clone.price_column,
clone.ask_column,
clone.last_column
}.Max(cs => cs.font_size) + row_padding;
clone.row_height = LadderRender.measure_row_height(clone);
return clone;
}
@@ -131,6 +135,7 @@ namespace mbts.spark {
_background = backing(nameof(background), Colors.White);
_font_display_name = backing(nameof(font_display_name),
() => TextUtils.display_name(font_family, font_weight, font_size), nameof(font_family), nameof(font_weight), nameof(font_size));
_font_size.Changed += (oldval, newval) => font_size_changed?.Invoke(oldval, newval);
}
public string name { get; }
@@ -145,6 +150,7 @@ namespace mbts.spark {
public Color background { get => _background.Value; set => _background.Value = value; }
public ObservableCollection<ColorPaletteVM> foreground_palettes { get; }
public ObservableCollection<ColorPaletteVM> background_palettes { get; }
public event Action<double, double> font_size_changed;
public virtual string sample_text => "999-56789.01234";
@@ -178,7 +184,9 @@ namespace mbts.spark {
public class OrdersColumnSettingsVM : LadderColumnSettingsVM, ITemplateSelectorKey {
public OrdersColumnSettingsVM() : base("Orders") { }
public override string sample_text => "12/50";
public uint sample_quantity { get; } = 50;
public uint sample_filled { get; } = 12;
public Side sample_side { get; } = Side.Buy;
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
public Brush badge_background { get; } = Brushes.White;
@@ -213,4 +221,4 @@ namespace mbts.spark {
};
}
}
}
}
@@ -11,6 +11,7 @@
TextElement.FontSize="{x:Static u:FontSizes.Small}"
>
<UserControl.Resources>
<c:BadgeTextLinesConverter x:Key="BadgeTextLinesConverter"/>
<u:KeyedTemplateSelector x:Key="ColumnSampleTemplateSelector">
<u:KeyedTemplateSelector.Default>
<DataTemplate>
@@ -28,18 +29,23 @@
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsView.OrdersColumnSampleTemplateKey}">
<DataTemplate>
<c:Badge
Width="50"
Height="20"
Background="{Binding badge_background}"
BorderBrush="{Binding badge_border}"
BorderThickness="3"
FontFamily="{Binding font_family}"
FontSize="{Binding font_size}"
FontWeight="{Binding font_weight}"
Foreground="{Binding foreground, Converter={StaticResource ColorToBrushConverter}}"
Text="{Binding sample_text}"
VerticalAlignment="Center"
/>
Width="{x:Static c:Badge.DefaultWidth}"
>
<c:Badge.TextLines>
<MultiBinding Converter="{StaticResource BadgeTextLinesConverter}">
<Binding Path="sample_side"/>
<Binding Path="sample_quantity"/>
<Binding Path="sample_filled"/>
<Binding Path="DataContext.badge_style" RelativeSource="{RelativeSource AncestorType=usp:LadderSettingsView}"/>
</MultiBinding>
</c:Badge.TextLines>
</c:Badge>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
@@ -82,7 +88,7 @@
<ItemsControl Grid.Row="1" ItemsSource="{Binding column_items}" Margin="8,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="27">
<Grid Height="{Binding DataContext.row_height, Mode=OneWay, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition SharedSizeGroup="c1"/>
@@ -124,7 +130,7 @@
<c:FontPicker SelectedFontSize="{Binding font_size}" Typeface="{Binding typeface}"/>
</c:PopupControl>
<Border Grid.Column="4" Background="{Binding background, Converter={StaticResource ColorToBrushConverter}}" BorderBrush="Black" BorderThickness="2,0,0,0" Padding="4,0">
<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource ColumnSampleTemplateSelector}"/>
<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource ColumnSampleTemplateSelector}" VerticalContentAlignment="Center"/>
</Border>
<Rectangle Grid.Column="1" Grid.ColumnSpan="5" Fill="DimGray" Height="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
</Grid>
+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">
@@ -422,15 +448,9 @@
<Setter Property="FontFamily" Value="{Binding font_family}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Foreground" Value="{Binding foreground}"/>
<Setter Property="Text" Value="{Binding text}" />
<Setter Property="TopText" Value="{Binding top_line}" />
<Setter Property="BottomText" Value="{Binding bottom_line}" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Width" Value="63" />
<Setter Property="Height" Value="23" />
<Setter Property="Margin" Value="4,0,0,0" />
<Setter Property="TextLines" Value="{Binding text_lines}" />
<Setter Property="Width" Value="{x:Static controls:Badge.DefaultWidth}"/>
<Setter Property="ToolTip" Value="{Binding description}" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="ContextMenu">
<Setter.Value>
@@ -481,4 +501,4 @@
</Button>
</Grid>
</AdornerDecorator>
</UserControl>
</UserControl>
@@ -1,20 +1,61 @@
using bts.api;
using mbts.co2;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.utility.text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace mbts.spark {
static class BadgeRender {
// IMPORTANT: the return values for this method MUST BE IN SYNC with the "format" method, else "measure_height" and "LadderRender.measure_row_height" will be broken.
static int _line_count_for_style(LadderSettings.BadgeStyle badge_style) => badge_style == LadderSettings.BadgeStyle.Alternate ? 2 : 1;
// IMPORTANT: at the risk of being redundant, see above comment.
public static IReadOnlyList<string> format(Side side, uint quantity, uint filled, LadderSettings.BadgeStyle style) {
switch (style) {
case LadderSettings.BadgeStyle.Alternate:
return new[] {
$"{side.abbreviate()}:{filled}",
$"W:{quantity - filled}"
};
default: return new[] { $"{filled}/{quantity}" };
}
}
public static double measure_height(LadderSettings.BadgeStyle badge_style, Thickness border_thickness, Thickness padding, double font_size) =>
measure_height(_line_count_for_style(badge_style), border_thickness, padding, font_size);
public static double measure_height(int line_count, Thickness border_thickness, Thickness padding, double font_size) =>
WPFHelper.control_base_height(border_thickness, padding) + font_size * line_count + Badge.LINE_SPACING * (line_count - 1);
}
static class LadderRender {
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
// (see how it is incorporated in measure_row_height() below)
public static readonly Thickness row_padding = new Thickness(6);
// IMPORTANT: this method ONLY WORKS if Badges use the BadgeRender.margin/border_thickness/padding values.
// TODO: elminate the dependency on BadgeRender.margin/border_thickness/padding by integrating them into settings.
public static double measure_row_height(LadderSettings settings) =>
Math.Max(new[] {
settings.bid_column.font_size,
settings.price_column.font_size,
settings.ask_column.font_size,
settings.last_column.font_size
}.Max() + row_padding.Top + row_padding.Bottom,
BadgeRender.measure_height(
settings.badge_style, Badge.DefaultBorderThickness, Badge.DefaultPadding, settings.orders_column.font_size
) + Badge.DefaultMargin.Top + Badge.DefaultMargin.Bottom);
public static int index_of(double[] prices, double price, IComparer<double> comparer) =>
Array.BinarySearch(prices, price, comparer);
public static GlyphRun format_text(string text, int row, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) =>
glyph_context.get_glyph_run(text, settings.font_size, settings.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, row * row_height);
glyph_context.get_glyph_run(text, settings.font_size, settings.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, row * row_height, true);
public static double[] gen_prices(double mid, int row_count, ContractSpecificationImmutable spec) {
// require odd number
@@ -1,4 +1,5 @@
using bts.api;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
@@ -25,7 +26,7 @@ namespace mbts.spark
public IObservable<List<PnLData>> on_pnl_data =>
// combine w/ Securities to get SecurityMD
_order_model.on_fill_tuples.CombineLatest(_bus.subscribe(SparkTags.SecuritiesTag), (fills, securities) => {
_order_model.on_fill_tuples.table(ft => ft.key).CombineLatest(_bus.subscribe(SparkTags.SecuritiesTag), (fills, securities) => {
List<PnLData> pnl_data = new List<PnLData>(fills.state.Count);
foreach (var ft in fills.state.Values) {
if (ft.security.HasValue && securities.TryGetValue(ft.security.Value.Id, out SecurityMD sec_md)) {
@@ -41,7 +41,7 @@ namespace mbts.spark {
partial class CVOrderBookVM {
public class CVOrderBookColumnCollection : ObservableCollection<ColumnBase>, IDisposable {
public CVOrderBookColumnCollection(IBus bus, OrdersModel model, OrderBookStartupData startup_data, IObservable<(IReadOnlyList<SecurityImmutable> added, IReadOnlyList<SecurityImmutable> removed)> order_securities) {
public CVOrderBookColumnCollection(IBus bus, OrdersModel model, OrderBookStartupData startup_data, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
// security
Add(security_column = new SecurityColumn(this,
DiscreteValuesFilterVM.Create<CVOrderBookRowVM, SecurityImmutable, SecurityCriteria>(
@@ -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);
}
@@ -24,8 +24,8 @@ namespace mbts.spark {
// copying the collection locally avoids constantly casting rows_view to ICollectionViewLiveShaping.
readonly ObservableCollection<string> _live_sorting_props;
readonly Property<CVOrderBookRowVM> _current_row;
readonly Subject<(IReadOnlyList<SecurityImmutable> added, IReadOnlyList<SecurityImmutable> removed)> _order_securities_subject =
new Subject<(IReadOnlyList<SecurityImmutable> added, IReadOnlyList<SecurityImmutable> removed)>();
readonly Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> _order_securities_subject =
new Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)>();
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders_cache = new Dictionary<uint, OrderRecordImmutable>();
Dictionary<uint, CVOrderBookRowVM> _order_vms_cache = new Dictionary<uint, CVOrderBookRowVM>();
@@ -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");
}
}
+65 -3
View File
@@ -53,7 +53,7 @@ namespace mbts.ui.utility.text {
public GlyphRun get_glyph_run(
string text, double font_size, double available_width, double available_height,
HorizontalAlignment x_alignment, double x_offset, VerticalAlignment y_alignment, double y_offset) {
HorizontalAlignment x_alignment, double x_offset, VerticalAlignment y_alignment, double y_offset, bool use_layout_rounding = false) {
//
if (string.IsNullOrEmpty(text))
return null;
@@ -61,7 +61,7 @@ namespace mbts.ui.utility.text {
var advance_widths = new double[text.Length];
if (!_try_get_glyph_params(text, font_size, ref glyph_indices, ref advance_widths, out double glyph_width))
return null;
var origin = _calculate_glyph_run_origin(font_size, available_width, available_height, glyph_width, x_alignment, x_offset, y_alignment, y_offset);
var origin = _calculate_glyph_run_origin(font_size, available_width, available_height, glyph_width, x_alignment, x_offset, y_alignment, y_offset, use_layout_rounding);
return new GlyphRun(_glyph_typeface, 0, false, font_size, HighDpiExtensions.fdpi_ratio, glyph_indices, origin, advance_widths, null, null, null, null, null, null);
}
@@ -87,7 +87,7 @@ namespace mbts.ui.utility.text {
static Point _calculate_glyph_run_origin(
double font_size, double available_width, double available_height, double glyph_width,
HorizontalAlignment x_alignment, double x_offset, VerticalAlignment y_alignment, double y_offset ) {
HorizontalAlignment x_alignment, double x_offset, VerticalAlignment y_alignment, double y_offset, bool use_layout_rounding = false) {
//
double x, y;
switch (x_alignment) {
@@ -104,6 +104,10 @@ namespace mbts.ui.utility.text {
default: y = available_height - 0.5 * (available_height - font_size); break;
}
y += y_offset;
if (use_layout_rounding) {
x = Math.Round(x);
y = Math.Round(y);
}
return new Point(x, y);
}
@@ -148,6 +152,64 @@ namespace mbts.ui.utility.text {
return true;
}
#region MultiLine
public bool arrange_glyph_run(
IReadOnlyList<string> lines, double line_spacing, TextAlignment text_alignment, double font_size,
out ushort[] glyph_indices, out double[] advance_widths, out Point[] glyph_offsets, out double glyph_width, out double glyph_height) {
//
glyph_width = 0;
glyph_height = 0;
if (lines.is_null_or_empty()) {
glyph_indices = new ushort[0];
advance_widths = new double[0];
glyph_offsets = new Point[0];
return false;
}
int size = lines.Sum(s => s.Length);
glyph_indices = new ushort[size];
advance_widths = new double[size];
glyph_offsets = new Point[size];
switch (text_alignment) {
case TextAlignment.Left:
return _try_get_glyph_params_left_aligned(lines, line_spacing, font_size, ref glyph_indices, ref advance_widths, ref glyph_offsets, out glyph_width, out glyph_height);
case TextAlignment.Center: // TODO
case TextAlignment.Right: // TODO
default: goto case TextAlignment.Left;
}
}
public GlyphRun from_arranged(double font_size, Point origin, ushort[] indices, double[] advance_widths, Point[] offsets) =>
new GlyphRun(_glyph_typeface, 0, false, font_size, HighDpiExtensions.fdpi_ratio, indices, origin, advance_widths, offsets, null, null, null, null, null);
bool _try_get_glyph_params_left_aligned(
IReadOnlyList<string> lines, double line_spacing, double font_size, ref ushort[] glyph_indices, ref double[] advance_widths, ref Point[] glyph_offsets, out double glyph_width, out double glyph_height) {
//
glyph_width = 0;
glyph_height = 0;
double x_offset = 0;
int n = 0;
foreach (string text in lines) {
double line_width = 0;
foreach (char c in text) {
if (!_char_map.TryGetValue(c, out (ushort index, double width) gc))
return false;
glyph_indices[n] = gc.index;
double w = gc.width * font_size;
advance_widths[n] = w;
line_width += w;
glyph_offsets[n] = new Point(x_offset, glyph_height);
n++;
}
x_offset -= line_width;
if (line_width > glyph_width)
glyph_width = line_width;
glyph_height += (font_size + line_spacing);
}
glyph_height -= line_spacing; // remove the last line space
return true;
}
#endregion
static ReadOnlyDictionary<int, (ushort index, double width)> _get_glyph_char_map(GlyphTypeface glyph_typeface) =>
_glyph_char_maps.GetOrAdd(glyph_typeface, gt => new Lazy<ReadOnlyDictionary<int, (ushort index, double width)>>(() => {
var widths = glyph_typeface.AdvanceWidths;
@@ -0,0 +1,185 @@
using mbts.ui.utility.text;
using mbts.utils;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
namespace mbts.ui {
interface IVisualContentRect {
Size layout_size { get; }
Rect content_area { get; }
}
// the "Control" in 'IControlVisualContentRect' is a double entendre:
// it hosts a set of input-properties inspired by the System.Windows.Control class,
// and it gives the caller "control" to modify the layout and content_area.
interface IControlVisualContentRect : IVisualContentRect {
Thickness border_thickness { get; set; }
Thickness padding { get; set; }
HorizontalAlignment horizontal_alignment { get; set; }
HorizontalAlignment horizontal_content_alignment { get; set; }
VerticalAlignment vertical_alignment { get; set; }
VerticalAlignment vertical_content_alignment { get; set; }
double content_width { get; set; }
double content_height { get; set; }
double min_width { get; set; }
double width { get; set; }
double max_width { get; set; }
double min_height { get; set; }
double height { get; set; }
double max_height { get; set; }
Size available_size { get; set; }
}
/// <summary>
/// Uses a set of standard r/w properties to measure and arrange the layout of a rectangular border-region with content inside.
/// </summary>
class VisualContentRect : IControlVisualContentRect {
Thickness
_padding,
_border_thickness;
HorizontalAlignment
_horizontal_alignment,
_horizontal_content_alignment = HorizontalAlignment.Center;
VerticalAlignment
_vertical_alignment,
_vertical_content_alignment = VerticalAlignment.Center;
double
_content_width,
_content_height,
_min_width,
_width,
_max_width,
_min_height,
_height,
_max_height;
Size _available_size, _layout_size;
Rect _content_area;
bool _dirty;
public Thickness border_thickness { get => _border_thickness; set => _set(ref _border_thickness, value); }
public Thickness padding { get => _padding; set => _set(ref _padding, value); }
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => _set(ref _horizontal_alignment, value); }
public HorizontalAlignment horizontal_content_alignment { get => _horizontal_content_alignment; set => _set(ref _horizontal_content_alignment, value); }
public VerticalAlignment vertical_alignment { get => _vertical_alignment; set => _set(ref _vertical_alignment, value); }
public VerticalAlignment vertical_content_alignment { get => _vertical_content_alignment; set => _set(ref _vertical_content_alignment, value); }
public double content_width { get => _content_width; set => _set(ref _content_width, value); }
public double content_height { get => _content_height; set => _set(ref _content_height, value); }
public double min_width { get => _min_width; set => _set(ref _min_width, value); }
public double width { get => _width; set => _set(ref _width, value); }
public double max_width { get => _max_width; set => _set(ref _max_width, value); }
public double min_height { get => _min_height; set => _set(ref _min_height, value); }
public double height { get => _height; set => _set(ref _height, value); }
public double max_height { get => _max_height; set => _set(ref _max_height, value); }
public Size available_size { get => _available_size; set => _set(ref _available_size, value, _size_comparer); }
public Size layout_size { get { _ensure_current(); return _layout_size; } }
public Rect content_area { get { _ensure_current(); return _content_area; } }
void _ensure_current() {
if (_dirty) {
WPFHelper.measure_visual_content(
_border_thickness, _padding, _horizontal_alignment, _horizontal_content_alignment, _vertical_alignment, _vertical_content_alignment,
_min_width, _width, _max_width, _min_height, _height, _max_height,
_content_width, _content_height, _available_size, out _content_area, out _layout_size, true);
_dirty = false;
}
}
void _set<T>(ref T field, T value) =>
_set(ref field, value, EqualityComparer<T>.Default);
void _set<T>(ref T field, T value, IEqualityComparer<T> comparer) {
if (!comparer.Equals(field, value)) {
field = value;
_dirty = true;
}
}
static readonly IEqualityComparer<Size> _size_comparer = EqualityComparer.Create<Size>((a, b) => a == b);
}
// this class was designed to organize/contain the littany of properties needed to arrange glyph content
// in addition to the usual-suspects (width, horizontalalignment, etc.), it stores the glyph-arrays: glyph_indices, advance_widths, and glyph_offsets
class GlyphContentRect : IControlVisualContentRect {
readonly VisualContentRect _vcr = new VisualContentRect();
ushort[] _glyph_indices;
double[] _advance_widths;
Point[] _glyph_offsets;
bool _arrange_dirty;
// fields of public properties
double _font_size, _line_spacing;
IReadOnlyList<string> _text_lines;
GlyphContext _glyph_context;
GlyphRun _glyph_run;
public GlyphContext glyph_context { get => _glyph_context; set => _set(ref _glyph_context, value); }
public IReadOnlyList<string> text_lines { get => _text_lines; set => _set(ref _text_lines, value); }
public double font_size { get => _font_size; set => _set(ref _font_size, value); }
public double line_spacing { get => _line_spacing; set => _set(ref _line_spacing, value); }
public Thickness border_thickness { get => _vcr.border_thickness; set => _vcr.border_thickness = value; }
public Thickness padding { get => _vcr.padding; set => _vcr.padding = value; }
public HorizontalAlignment horizontal_alignment { get => _vcr.horizontal_alignment; set => _vcr.horizontal_alignment = value; }
public HorizontalAlignment horizontal_content_alignment { get => _vcr.horizontal_content_alignment; set => _vcr.horizontal_content_alignment = value; }
public VerticalAlignment vertical_alignment { get => _vcr.vertical_alignment; set => _vcr.vertical_alignment = value; }
public VerticalAlignment vertical_content_alignment { get => _vcr.vertical_content_alignment; set => _vcr.vertical_content_alignment = value; }
public double content_width { get { _ensure_current(); return _vcr.content_width; } set { /*TODO: maybe add a Clip property and set it here?*/ } }
public double content_height { get { _ensure_current(); return _vcr.content_height; } set { /*TODO: maybe add a Clip property and set it here?*/ } }
public double min_width { get => _vcr.min_width; set => _vcr.min_width = value; }
public double width { get => _vcr.width; set => _vcr.width = value; }
public double max_width { get => _vcr.max_width; set => _vcr.max_width = value; }
public double min_height { get => _vcr.min_height; set => _vcr.min_height = value; }
public double height { get => _vcr.height; set => _vcr.height = value; }
public double max_height { get => _vcr.max_height; set => _vcr.max_height = value; }
public Size available_size { get => _vcr.available_size; set => _vcr.available_size = value; }
public Size layout_size { get { _ensure_current(); return _vcr.layout_size; } }
public Rect content_area { get { _ensure_current(); return _vcr.content_area; } }
public GlyphRun glyph_run {
get {
if (_text_lines.is_null_or_empty())
return null;
_ensure_current();
Point pt = _vcr.content_area.BottomLeft;
if (_glyph_run == null) {
_glyph_run = _glyph_context.from_arranged(_font_size, pt, _glyph_indices, _advance_widths, _glyph_offsets);
} else if (_glyph_run.BaselineOrigin != pt) {
_glyph_run.BaselineOrigin = pt;
}
return _glyph_run;
}
}
void _ensure_current() {
if (_arrange_dirty) {
if (_text_lines.is_null_or_empty()) {
_vcr.content_width = 0;
_vcr.content_height = 0;
} else {
_glyph_context.arrange_glyph_run(_text_lines, _line_spacing, TextAlignment.Left, _font_size, out _glyph_indices, out _advance_widths, out _glyph_offsets, out double glyph_width, out double glyph_height);
_vcr.content_width = glyph_width;
_vcr.content_height = glyph_height;
}
_glyph_run = null;
_arrange_dirty = false;
}
}
void _set<T>(ref T field, T value) =>
_set(ref field, value, EqualityComparer<T>.Default);
void _set<T>(ref T field, T value, IEqualityComparer<T> comparer) {
if (!comparer.Equals(field, value)) {
field = value;
_arrange_dirty = true;
}
}
}
static class VisualContentRectExtensions {
// since the Margin is an external factor - an amount of space outside of the control's render-region - it is not included directly in the layout_size
public static Size layout_size_plus_margin(this IVisualContentRect vcr, Thickness margin) {
var sz = vcr.layout_size;
return new Size(margin.Left + sz.Width + margin.Right, margin.Top + sz.Height + margin.Bottom);
}
}
}
@@ -59,6 +59,9 @@ namespace mbts.ui.utility {
public static ITableUpdateCollection<T, TKey> CreateTableUpdate<T, TKey>(IObservable<ITableUpdateTransform<T, TKey>> source, Func<T, bool> collection_filter) where TKey : IEquatable<TKey> =>
new TableUpdateTransformCollection<T, TKey>(source, collection_filter ?? new Func<T, bool>(_ => true));
public static IRXCollection<T> CreateIncrementalUpdate<T, TKey>(IObservable<IIncrementalUpdate<T, TKey>> source) where TKey : IEquatable<TKey> =>
new IncrementalUpdateCollection<T, TKey>(source);
class AnonymousRXCollection<T, TData> : ObservableCollection<T>, IRXCollection<T> {
IDisposable _sub;
@@ -103,5 +106,20 @@ namespace mbts.ui.utility {
public bool TryGetValue(TKey key, out T result) => _raw_state.TryGetValue(key, out result);
public void Dispose() => Disposable.dispose(ref _sub);
}
class IncrementalUpdateCollection<T, TKey> : ObservableCollection<T>, IRXCollection<T> where TKey : IEquatable<TKey> {
IDisposable _sub;
internal IncrementalUpdateCollection(IObservable<IIncrementalUpdate<T, TKey>> source) {
_sub = source.ObserveOnDispatcher().Subscribe(u => {
foreach (T val in u.removed.Values)
Remove(val);
foreach (T val in u.added.Values)
Add(val);
});
}
public void Dispose() => Disposable.dispose(ref _sub);
}
}
}
@@ -1,3 +1,4 @@
using mbts.spark.utilities;
using mbts.utils;
using System;
using System.Collections;
@@ -222,5 +223,96 @@ namespace mbts.ui {
public static double control_base_height(Thickness border_thickness, Thickness padding) =>
border_thickness.Top + padding.Top + padding.Bottom + border_thickness.Bottom;
public static void measure_visual_content(
Thickness border_thickness,
Thickness padding,
HorizontalAlignment x_align,
HorizontalAlignment x_content_align,
VerticalAlignment y_align,
VerticalAlignment y_content_align,
double min_width,
double width,
double max_width,
double min_height,
double height,
double max_height,
double content_width,
double content_height,
Size available_size,
out Rect content_area,
out Size resultant_size,
bool use_layout_rounding = false) {
//
double result_width, content_left;
if (!double.IsNaN(width)) {
result_width = MathUtils.within_range(min_width, max_width, width);
switch (x_content_align) {
case HorizontalAlignment.Left: content_left = border_thickness.Left + padding.Left; break;
case HorizontalAlignment.Center: content_left = 0.5 * (result_width - content_width); break;
case HorizontalAlignment.Right: content_left = result_width - border_thickness.Right - padding.Right; break;
default: goto case HorizontalAlignment.Center;
}
} else {
switch (x_align) {
case HorizontalAlignment.Left:
case HorizontalAlignment.Center:
case HorizontalAlignment.Right:
result_width = MathUtils.within_range(min_width, max_height, border_thickness.Left + padding.Left + content_width + padding.Right + border_thickness.Right);
content_left = border_thickness.Left + padding.Left;
break;
case HorizontalAlignment.Stretch: {
result_width = MathUtils.within_range(min_width, max_height, available_size.Width);
switch (x_content_align) {
case HorizontalAlignment.Left: content_left = border_thickness.Left + padding.Left; break;
case HorizontalAlignment.Center: content_left = 0.5 * (result_width - content_width); break;
case HorizontalAlignment.Right: content_left = result_width - content_width - border_thickness.Right - padding.Right; break;
default: goto case HorizontalAlignment.Center;
}
}
break;
default: goto case HorizontalAlignment.Center;
}
}
double result_height, content_top;
if (!double.IsNaN(height)) {
result_height = MathUtils.within_range(min_height, max_height, height);
switch (y_content_align) {
case VerticalAlignment.Top: content_top = border_thickness.Top + padding.Top; break;
case VerticalAlignment.Center: content_top = 0.5 * (result_height - content_height); break;
case VerticalAlignment.Bottom: content_top = result_height - border_thickness.Bottom - padding.Bottom; break;
default: goto case VerticalAlignment.Center;
}
} else {
switch (y_align) {
case VerticalAlignment.Top:
case VerticalAlignment.Center:
case VerticalAlignment.Bottom:
result_height = border_thickness.Top + padding.Top + content_height + padding.Bottom + border_thickness.Bottom;
content_top = border_thickness.Top + padding.Top;
break;
case VerticalAlignment.Stretch:
result_height = available_size.Height;
switch (y_content_align) {
case VerticalAlignment.Top: content_top = border_thickness.Top + padding.Top; break;
case VerticalAlignment.Center: content_top = 0.5 * (result_height - content_height); break;
case VerticalAlignment.Bottom: content_top = result_height - content_height - border_thickness.Bottom - padding.Bottom; break;
default: goto case VerticalAlignment.Center;
}
break;
default: goto case VerticalAlignment.Center;
}
}
if (use_layout_rounding) {
content_left = Math.Round(content_left);
content_top = Math.Round(content_top);
content_width = Math.Round(content_width);
content_height = Math.Round(content_height);
result_width = Math.Round(result_width);
result_height = Math.Round(result_height);
}
content_area = new Rect(content_left, content_top, content_width, content_height);
resultant_size = new Size(result_width, result_height);
}
}
}
-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());
}
}
}
@@ -11,7 +11,7 @@ namespace {{package}} {
partial class {{name}} : bts.api.IImmutableMessage {
MessageDescriptor bts.api.IImmutableMessage.Descriptor => {{name}}.Descriptor;
}
public partial struct {{name}}Immutable : bts.api.IImmutableMessage {
public readonly partial struct {{name}}Immutable : bts.api.IImmutableMessage {
readonly {{name}} _impl;
public static MessageDescriptor Descriptor => {{name}}.Descriptor;
@@ -45,7 +45,7 @@ const bts::api::admin::ExchangeConnection* AdminServiceUtils::find_default_accou
const bts::api::admin::BTSAccount& account,
bts::api::Venue exchange){
//todo:get from parent accounts if not overridden
if(account.has_connections())
if(account.has_connections())
for (const std::string& conn_name : account.connections().exchange_connection_names()){
const bts::api::admin::ExchangeConnection* conn = find_connection(data, conn_name);
if (conn!=nullptr && has_exchange(*conn, exchange))
@@ -53,6 +53,44 @@ const bts::api::admin::ExchangeConnection* AdminServiceUtils::find_default_accou
}
return nullptr;
}
const bts::api::admin::ExchangeConnection*
AdminServiceUtils::resolve_cme_connection(const bts::api::admin::AdminData& data,
const bts::api::admin::BTSAccount& account,
const bts::api::Security& sec){
if(account.has_connections())
for (const std::string& conn_name : account.connections().exchange_connection_names()){
const bts::api::admin::ExchangeConnection* conn = find_connection(data, conn_name);
if (conn!=nullptr && conn->has_cme_connection())
if (conn->cme_connection().has_cgw_connection() ||
(sec.has_glbx() && conn->cme_connection().has_msgw_connection() &&
conn->cme_connection().msgw_connection().market_segment_id() == sec.glbx().market_segment_id()))
return conn;
}
return nullptr;
}
const bts::api::admin::ExchangeConnection*
AdminServiceUtils::resolve_connection(const bts::api::admin::AdminData& data,
const bts::api::admin::BTSAccount& account,
const bts::api::Security& sec){
const bts::api::admin::ExchangeConnection* rv = nullptr;
switch(sec.exchange()){
case api::Venue::VENUE_GLBX:
rv = resolve_cme_connection(data, account, sec);
break;
case api::Venue::VENUE_UNSET:
case api::Venue::VENUE_BTS:
case api::Venue::VENUE_CBOE:
case api::Venue::VENUE_CBOECFE:
case api::Venue::VENUE_ICE:
case api::Venue::Venue_INT_MIN_SENTINEL_DO_NOT_USE_:
case api::Venue::Venue_INT_MAX_SENTINEL_DO_NOT_USE_:
rv = nullptr;
break;
}
return rv;
}
////////////////////////////////////////////
// connection mutators /////////////////////
////////////////////////////////////////////
@@ -71,7 +109,10 @@ void AdminServiceUtils::fill_connections_from_config(bts::api::admin::AdminData&
uint32_t new_id = find_max_connection_id(admin_data)+1;
connection.set_id(new_id);
connection.set_name(c.name());
connection.mutable_cme_connection()->mutable_msgw_connection();
if (c.cme_market_segment_id()==0)
connection.mutable_cme_connection()->mutable_cgw_connection();
else
connection.mutable_cme_connection()->mutable_msgw_connection()->set_market_segment_id(c.cme_market_segment_id());
add_connection(admin_data,connection);
}
for(int i=0;i<admin_data.accounts_size();i++)
@@ -267,7 +308,8 @@ bts::api::orderman::RoutingRecord AdminServiceUtils::get_routing_record(const bt
out_result.set_error_text("Could not find user");
return rv;
}
const bts::api::admin::ExchangeConnection* conn_ptr = find_default_account_connection(data, *account_ptr, sec.exchange());
const bts::api::admin::ExchangeConnection* conn_ptr = //find_default_account_connection(data, *account_ptr, sec.exchange());
resolve_connection(data,*account_ptr,sec);
if(conn_ptr == nullptr){
out_result.set_is_success(false);
out_result.set_error_text("Could not find connection");
@@ -285,6 +327,22 @@ bts::api::orderman::RoutingRecord AdminServiceUtils::get_routing_record(const bt
out_result.set_is_success(false);
out_result.set_error_text("Could not find CME data for account");
return rv;
}
// check for data corectness
if (cme_user_data->operator_id().length() == 0){
out_result.set_is_success(false);
out_result.set_error_text("Operator ID is not set");
return rv;
}
if (cme_account_data->clearing_account().length() == 0){
out_result.set_is_success(false);
out_result.set_error_text("Clearing Account is not set");
return rv;
}
if (cme_account_data->cti()==bts::api::orderman::CTICode::CTI_CODE_UNDEFINED){
out_result.set_is_success(false);
out_result.set_error_text("CTI Code is not set");
return rv;
}
rv.set_session(conn_ptr->name());
rv.set_operator_id(cme_user_data->operator_id());
@@ -29,6 +29,12 @@ public:
static const bts::api::admin::ExchangeConnection* find_default_account_connection(const bts::api::admin::AdminData& data,
const bts::api::admin::BTSAccount& account,
bts::api::Venue exchange);
static const bts::api::admin::ExchangeConnection* resolve_cme_connection(const bts::api::admin::AdminData& data,
const bts::api::admin::BTSAccount& account,
const bts::api::Security& sec);
static const bts::api::admin::ExchangeConnection* resolve_connection(const bts::api::admin::AdminData& data,
const bts::api::admin::BTSAccount& account,
const bts::api::Security& sec);
////////////////////////////////////////////
// connection mutators /////////////////////
////////////////////////////////////////////
+1
View File
@@ -235,6 +235,7 @@ message CMEMSGWConnection{
string password = 30;
string secret_key = 40;
string access_key_id = 50;
uint32 market_segment_id = 60;
}
message CMEAccountData{
+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
View File
@@ -254,6 +254,7 @@ JetFIX {
end_time: "07:48:00"
start_time: "07:52:00"
raw_data: "1JE9X"
use_cme_secure_logon: false
seconds_to_pause : 1
}
}
+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];
}
+1
View File
@@ -58,6 +58,7 @@ message GLBXExchangeData {
uint32 main_fraction = 30;
uint32 sub_fraction = 31;
uint32 price_display_format = 32;
uint32 market_segment_id = 33;
}
message CBOEExchangeData {
int64 exchange_id = 10;
-1
View File
@@ -97,7 +97,6 @@ void configure_logging(bts::config::Logging const&cfg)
{log::persist,"persist"},
{log::User,"User"},
{log::OrderMan,"OrderMan"},
{log::Latency,"Latency"},
{log::Assert,"Assert"},
{log::Engine,"Engine"},
{log::Ui,"UI"},
+1 -2
View File
@@ -34,8 +34,7 @@ constexpr log::Subsystem Api {md.ordinal+1 };
constexpr log::Subsystem persist {Api.ordinal+1 };
constexpr log::Subsystem User {persist.ordinal+1 };
constexpr log::Subsystem OrderMan {User.ordinal+1 };
constexpr log::Subsystem Latency {OrderMan.ordinal+1 };
constexpr log::Subsystem Assert {Latency.ordinal+1 };
constexpr log::Subsystem Assert {OrderMan.ordinal+1 };
constexpr log::Subsystem Engine {Assert.ordinal+1 };
constexpr log::Subsystem Ui {Engine.ordinal+1 };
constexpr log::Subsystem ExSim {Ui.ordinal+1 };
+7 -2
View File
@@ -22,11 +22,14 @@ FixedSecurity::FixedSecurity() :
type(api::SECURITYTYPE_STOCK),
exchange(api::VENUE_UNSET),
display_factor(0),
cboe_class_key(0),
cme_market_segment_id(0),
price_break(0),
low_tick(0),
high_tick(0),
cboe_class_key(0),
cme_market_segment_id(0),
cme_main_fraction(0),
cme_sub_fraction(0),
cme_price_display_format(0),
_symbol(),
_cfi_code(),
_security_desc(),
@@ -157,6 +160,7 @@ void convert_security(const FixedSecurity& source, Security* presult) {
dest.mutable_glbx()->set_security_desc(source.security_desc());
dest.mutable_glbx()->set_cfi_code(source.cfi_code());
dest.mutable_glbx()->set_cme_symbol(source.alt_symbol());
if (source.cme_market_segment_id != 0) dest.mutable_glbx()->set_market_segment_id(source.cme_market_segment_id);
if (source.cme_main_fraction != 0) dest.mutable_glbx()->set_main_fraction(source.cme_main_fraction);
if (source.cme_sub_fraction != 0) dest.mutable_glbx()->set_sub_fraction(source.cme_sub_fraction);
if (source.cme_price_display_format) dest.mutable_glbx()->set_price_display_format(source.cme_price_display_format);
@@ -257,6 +261,7 @@ void convert_security(const Security& src, FixedSecurity& dest, bool set_securit
dest.security_desc(src.glbx().security_desc());
dest.cfi_code(src.glbx().cfi_code());
dest.alt_symbol(src.glbx().cme_symbol());
dest.cme_market_segment_id = src.glbx().market_segment_id();
dest.cme_main_fraction = src.glbx().main_fraction();
dest.cme_sub_fraction = src.glbx().sub_fraction();
dest.cme_price_display_format = src.glbx().price_display_format();
+2 -4
View File
@@ -45,15 +45,13 @@ public:
int8_t display_factor;
uint32_t cboe_class_key;
uint32_t cme_market_segment_id;
double price_break;
double low_tick;
double high_tick;
double cash_per_point;
uint32_t cboe_class_key;
uint8_t cme_market_segment_id;
uint8_t cme_main_fraction;
uint8_t cme_sub_fraction;
uint8_t cme_price_display_format;
+1 -1
View File
@@ -1 +1 @@
set(sources cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/Help.cpp;cmds/Md.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/TestCeloxica.cpp;dspark/mirage.cpp;dspark/mirage/routing/Router.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/WebServer.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;main.cpp;md/platforms/celoxica/CXListener.cpp;md/platforms/celoxica/CXMain.cpp;md/platforms/celoxica/tests/cx_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/MDP3Utils.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;mercury/actors/Actor.cpp;mercury/broker/AsyncEventBroker.cpp;mercury/broker/CoreEventBroker.cpp;mercury/broker/IEventBroker.cpp;mercury/events/TimerEvent.cpp;mercury/mercury_utils/Executor.cpp;mercury/mercury_utils/IPollableActor.cpp;mercury/mercury_utils/mercury_common.cpp;mercury/synopsis/SynopsisBuilder.cpp;mercury/topics/ITopic.cpp;orderman/actors/AsyncFactory.cpp;orderman/actors/ExecutionGateway.cpp;orderman/actors/FillStore.cpp;orderman/actors/OrderStore.cpp;orderman/actors/SecuritySrc.cpp;orderman/actors/ServiceBridge.cpp;orderman/actors/order_manager/OrderBuilder.cpp;orderman/actors/order_manager/OrderManager.cpp;orderman/actors/order_manager/OrderServicesData.cpp;orderman/events/CancelRejectEvent.cpp;orderman/events/NewExecutionReportEvent.cpp;orderman/limiter/LimitStore.cpp;orderman/limiter/Limiter.cpp;orderman/limiter/ProductLimitNode.cpp;orderman/limiter/ProductLimitTree.cpp;orderman/limiter/SecurityLimitNode.cpp;orderman/limiter/SecurityLimitTree.cpp;orderman/limiter/tests/limit_tests.cpp;orderman/order_factory/OrderFactory.cpp;orderman/order_router/OrderRouter.cpp;orderman/orderman_utils/fix_order_codec.cpp;orderman/orders/IcebergOrder.cpp;orderman/orders/NativeOrder.cpp;orderman/orders/Order.cpp;orderman/orders/TimeBoxOrder.cpp;orderman/orders/spreader_orders/SpreaderOrder.cpp;orderman/orders/spreader_orders/spreader/LegPropertyData.cpp;orderman/orders/spreader_orders/spreader/QuoteLegPriceQuantityState.cpp;orderman/orders/spreader_orders/spreader/SingleFillHedger.cpp;orderman/orders/spreader_orders/spreader/SingleLegQuoter.cpp;orderman/orders/spreader_orders/spreader/SpreaderBookkeepingData.cpp;orderman/orders/spreader_orders/spreader/SpreaderChildOrderState.cpp;orderman/orders/spreader_orders/spreader/SpreaderLegData.cpp;orderman/orders/spreader_orders/spreader/SpreaderUtils.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderServices.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderPreActivationOrderServices.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerEndStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerStartStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForCancelAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForFirstAckCancelledStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForFirstAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWorkingStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterEndStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterStartStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForCancelAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForFirstAckCancelledWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForFirstAckWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForSizeStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWorkingStateWorker.cpp;orderman/orders/spreader_orders/spreader_states/SpreaderEndStateWorker.cpp;orderman/orders/spreader_orders/spreader_states/SpreaderStartStateWorker.cpp;orderman/orders/spreader_orders/spreader_states/SpreaderWaitLine truncated
set(sources cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/Help.cpp;cmds/Md.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/TestCeloxica.cpp;dspark/mirage.cpp;dspark/mirage/routing/Router.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/WebServer.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;main.cpp;md/platforms/celoxica/CXListener.cpp;md/platforms/celoxica/CXMain.cpp;md/platforms/celoxica/tests/cx_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/MDP3Utils.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;mercury/actors/Actor.cpp;mercury/broker/AsyncEventBroker.cpp;mercury/broker/CoreEventBroker.cpp;mercury/broker/IEventBroker.cpp;mercury/events/TimerEvent.cpp;mercury/mercury_utils/Executor.cpp;mercury/mercury_utils/IPollableActor.cpp;mercury/mercury_utils/mercury_common.cpp;mercury/synopsis/SynopsisBuilder.cpp;mercury/topics/ITopic.cpp;orderman/actors/AsyncFactory.cpp;orderman/actors/ExecutionGateway.cpp;orderman/actors/FillStore.cpp;orderman/actors/OrderStore.cpp;orderman/actors/SecuritySrc.cpp;orderman/actors/ServiceBridge.cpp;orderman/actors/order_manager/OrderBuilder.cpp;orderman/actors/order_manager/OrderManager.cpp;orderman/actors/order_manager/OrderServicesData.cpp;orderman/events/CancelRejectEvent.cpp;orderman/events/NewExecutionReportEvent.cpp;orderman/limiter/LimitStore.cpp;orderman/limiter/Limiter.cpp;orderman/limiter/ProductLimitNode.cpp;orderman/limiter/ProductLimitTree.cpp;orderman/limiter/SecurityLimitNode.cpp;orderman/limiter/SecurityLimitTree.cpp;orderman/limiter/tests/limit_tests.cpp;orderman/order_factory/OrderFactory.cpp;orderman/order_router/OrderRouter.cpp;orderman/orderman_utils/fix_order_codec.cpp;orderman/orders/IcebergOrder.cpp;orderman/orders/NativeOrder.cpp;orderman/orders/Order.cpp;orderman/orders/TimeBoxOrder.cpp;orderman/orders/book_algos/BookAlgo.cpp;orderman/orders/book_algos/JoinOrder.cpp;orderman/orders/book_algos/LeanOrder.cpp;orderman/orders/book_algos/TickOrder.cpp;orderman/orders/book_algos/TopOrder.cpp;orderman/orders/spreader_orders/SpreaderOrder.cpp;orderman/orders/spreader_orders/spreader/LegPropertyData.cpp;orderman/orders/spreader_orders/spreader/QuoteLegPriceQuantityState.cpp;orderman/orders/spreader_orders/spreader/SingleFillHedger.cpp;orderman/orders/spreader_orders/spreader/SingleLegQuoter.cpp;orderman/orders/spreader_orders/spreader/SpreaderBookkeepingData.cpp;orderman/orders/spreader_orders/spreader/SpreaderChildOrderState.cpp;orderman/orders/spreader_orders/spreader/SpreaderLegData.cpp;orderman/orders/spreader_orders/spreader/SpreaderUtils.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderServices.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderPreActivationOrderServices.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerEndStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerStartStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForCancelAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForFirstAckCancelledStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForFirstAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWorkingStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterEndStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterStartStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForCancelAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForFirstAckCancelledWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForFirstAckWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForSizeStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWorkingStateWorker.cpp;orderman/Line truncated
@@ -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) {
@@ -3,6 +3,8 @@
#include "server/sparkd/jetfix/store/session_name.h"
#include "lib/logging/logging.h"
#include "lib/utils/TimeStamp.h"
#include "server/sparkd/tracing/bts_trace.h"
#include "lib/utils/helper_macros.h"
#include <arpa/inet.h>
#include <sys/types.h>
@@ -101,6 +103,7 @@ int SocketConnection::read (char* to_buffer,size_t read_count)
int SocketConnection::write (WriteVec const&vec,size_t vec_size,size_t cid)
{
UNUSED(cid); // clang
bts_assert (_fd>=0);
struct iovec iv [MaxWriteVecSz];
@@ -109,9 +112,9 @@ int SocketConnection::write (WriteVec const&vec,size_t vec_size,size_t cid)
iv[i].iov_len = vec[i].second;
}
LOG(Latency,Info,"SocketConnection_write_pre_write {} {}", cid, utils::TimeStamp::now().to_uint64());
tracepoint (bts_trace,trace_order,(char*) "SocketConnection.pre_write", 0,cid);
auto result = ::writev (_fd,iv,(int) vec_size);
LOG(Latency,Info,"SocketConnection_write_post_write {} {}", cid, utils::TimeStamp::now().to_uint64());
tracepoint (bts_trace,trace_order,(char*) "SocketConnection.post_write", 0,cid);
if (_log && result >0) {
for (size_t i = 0; i < vec_size;i++)
_out_log.write (vec[i].first, vec[i].second);
@@ -11,6 +11,7 @@
#include "lib/utils/datetime.h"
#include "lib/utils/bts_chrono.h"
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
#include "server/sparkd/tracing/bts_trace.h"
#include <thread>
#include <atomic>
@@ -160,7 +161,7 @@ CoreGateway::~CoreGateway ()
void CoreGateway::send (orderman::TickToTradeCorID cid, OutFIXPtr const &fix)
{
LOG(Latency,Info,"CoreGateway_send {} {}", cid, utils::TimeStamp::now().to_uint64());
tracepoint (bts_trace,trace_order,(char*) "CoreGateway.send", 0,cid);
me->event_queue.enqueue (EventData (&CoreGateway::sync_send,cid,fix));
}
@@ -277,7 +278,7 @@ void CoreGateway::sync_stop (EventData const&)
void CoreGateway::sync_send (EventData const &ev)
{
LOG(Latency,Info,"CoreGateway_sync_send {} {}", ev.cid, utils::TimeStamp::now().to_uint64());
tracepoint (bts_trace,trace_order,(char*) "CoreGateway.sync_send", 0,ev.cid);
switch (me->state)
{
case State::OFF:
+4 -5
View File
@@ -12,6 +12,7 @@
#include "server/sparkd/jetfix/connection/IConnection.h"
#include "server/sparkd/jetfix/gateway/ICoreGatewayEventSink.h"
#include "server/sparkd/jetfix/store/IFIXStore.h"
#include "server/sparkd/tracing/bts_trace.h"
#include "lib/config/protos/jetfix.pb.h"
@@ -105,6 +106,7 @@ void Session::start() {
}
void Session::send(orderman::TickToTradeCorID cid, OutFIXPtr const &msgs) {
tracepoint (bts_trace,trace_order,(char*) "Session.send", 0,cid);
switch (me->state) {
case State::READY:
case State::CERT_MODE:
@@ -561,15 +563,12 @@ void Session::internal_send(orderman::TickToTradeCorID cid,
data_vec[i].first = s.data();
data_vec[i].second = s.size();
}
LOG(Latency, Info, "Session_internal_send_pre_write {} {}", cid,
utils::TimeStamp::now().to_uint64());
tracepoint (bts_trace,trace_order,(char*) "Session.internal_send.pre_write", 0,cid);
if (me->connection.write(data_vec, msgs->fix.size(), cid) < 0) {
terminate_session(JetFIXError::CONNECTION_ERROR);
return;
}
LOG(Latency, Info, "Session_internal_send_post_write {} {}", cid,
utils::TimeStamp::now().to_uint64());
tracepoint (bts_trace,trace_order,(char*) "Session.internal_send.post_write", 0,cid);
me->last_sent_msg_timestamp = me->now();
me->store.store(msgs);
msgs->set_ts(OutFIX::POST_STORE, utils::TimeStamp::now());
@@ -1,6 +1,8 @@
#include "MDP3Listener.h"
#include "lib/utils/datetime.h"
#include <vector>
#include "server/sparkd/tracing/bts_trace.h"
#include "lib/utils/helper_macros.h"
using namespace std;
using namespace bts::co2;
@@ -104,15 +106,19 @@ MDP3Listener::~MDP3Listener() {
//
// Warnings/Errors
void MDP3Listener::onError(onixs::Handler&, const onixs::ErrorArgs& error) {
tracepoint (bts_trace,trace_order,(char*) "onError", 0, _latest_sequence);
LOG(md,Warn, "OnixS Error: {} - {}", error.description(), error.source());
}
void MDP3Listener::onWarning(onixs::Handler&, const onixs::WarningArgs& warning) {
tracepoint (bts_trace,trace_order,(char*) "onWarning", 0, _latest_sequence);
LOG(md,Warn, "OnixS Warn: {} - {}", warning.description(), warning.source());
}
void MDP3Listener::onInstrumentRecovery(onixs::Handler& h) {
tracepoint (bts_trace,trace_order,(char*) "onInstrumentRecovery", 0, _latest_sequence);
LOG(md,Info, "Beginning security definition recovery for channel {}", h.settings().channel());
}
void MDP3Listener::onEndOfInstrumentRecovery(onixs::Handler& h, const onixs::RecoveryCompletionArgs&) {
tracepoint (bts_trace,trace_order,(char*) "onEndOfInstrumentRecovery", 0, _latest_sequence);
LOG(md,Info, "Finished Security definition recovery for channel {}", h.settings().channel());
}
// void MDP3Listener::onGap(onixs::Handler& h, const Security&, unsigned) {
@@ -123,18 +129,22 @@ void MDP3Listener::onEndOfInstrumentRecovery(onixs::Handler& h, const onixs::Rec
//
// Gap Recovery
void MDP3Listener::onMarketRecovery(onixs::Handler& h) {
tracepoint (bts_trace,trace_order,(char*) "onMarketRecovery", 0, _latest_sequence);
update_all_status(COCONUTSECURITYSTATUS_PACKET_LOSS);
LOG(md,Info, "Beginning market recovery for channel {}", h.settings().channel());
}
void MDP3Listener::onEndOfMarketRecovery(onixs::Handler& h, const onixs::RecoveryCompletionArgs&) {
tracepoint (bts_trace,trace_order,(char*) "onEndOfMarketRecovery", 0, _latest_sequence);
update_all_status(COCONUTSECURITYSTATUS_OK);
LOG(md,Info, "Finished market recovery for channel {}", h.settings().channel());
}
void MDP3Listener::onTcpRecovery(onixs::Handler& h) {
tracepoint (bts_trace,trace_order,(char*) "onTcpRecovery", 0, _latest_sequence);
update_all_status(COCONUTSECURITYSTATUS_PACKET_LOSS);
LOG(md,Info, "Beginning tcp recovery for channel {}", h.settings().channel());
}
void MDP3Listener::onEndOfTcpRecovery(onixs::Handler& h, const onixs::RecoveryCompletionArgs&) {
tracepoint (bts_trace,trace_order,(char*) "onEndOfTcpRecovery", 0, _latest_sequence);
update_all_status(COCONUTSECURITYSTATUS_OK);
LOG(md,Info, "Finished tcp recovery for channel {}", h.settings().channel());
}
@@ -152,9 +162,21 @@ void MDP3Listener::onPacket(onixs::Handler&, const onixs::PacketArgs& args) {
}
}
_latest_sequence = seq;
auto onixs_zero = args.receiveTime().sinceEpoch();
auto onixs_now = OnixS::CME::MDH::UtcWatch::now().sinceEpoch();
// needed for clang compilation
UNUSED(onixs_zero);
UNUSED(onixs_now);
// was needed for clang compilation
tracepoint (bts_trace,trace_order,(char*) "onPacket",
_packet_arrived.to_uint64() - (onixs_now-onixs_zero),_latest_sequence);
}
void MDP3Listener::onEndOfPacket(onixs::Handler&, const onixs::PacketArgs&) {
tracepoint (bts_trace,trace_order,(char*) "onEndOfPacket.begin", 0,_latest_sequence);
flush_books();
tracepoint (bts_trace,trace_order,(char*) "onEndOfPacket.end", 0,_latest_sequence);
}
//
@@ -208,6 +230,7 @@ void set_security_common(const Definition& def, FixedSecurity& fsec) {
}
void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security& osec, const onixs::InstrumentDefinitionFutureArgs& def) {
tracepoint (bts_trace,trace_order,(char*) "onDefinition.future", 0,_latest_sequence);
const auto& sec = def.message();
auto exchange_id = sec.securityId();
// LOG(Info, MarketData) << "onDefinition future " << exchange_id;
@@ -232,6 +255,7 @@ void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security& osec, co
add_security(fsec);
}
void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security&, const onixs::InstrumentDefinitionOptionArgs& def) {
tracepoint (bts_trace,trace_order,(char*) "onDefinition.option", 0,_latest_sequence);
const auto& sec = def.message();
auto exchange_id = sec.securityId();
// LOG(Info, MarketData) << "onDefinition option " << exchange_id;
@@ -264,7 +288,7 @@ void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security&, const o
}
void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security&, const onixs::InstrumentDefinitionSpreadArgs& spread_args) {
// tracepoint (bts_trace,quoter,(char*) "onSecurityDefnition", 0, _latest_sequence);
tracepoint (bts_trace,trace_order,(char*) "onDefinition.spread", 0,_latest_sequence);
const auto& sec = spread_args.message();
auto exchange_id = sec.securityId();
// LOG(Info, MarketData) << "onDefinition spread " << exchange_id;
@@ -309,14 +333,13 @@ void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security&, const o
}
add_security(fsec);
_index.add_spread_legs(fsec.numeric_id, leg_exchange_ids);
// tracepoint (bts_trace,quoter,(char*) "onSecurityDefnition", 1, _latest_sequence);
}
//
// Book
void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, const onixs::ConsolidatedBook& book) {
// tracepoint (bts_trace,quoter,(char*) "onConsolidatedBookBegin", sec.id(), _latest_sequence);
tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", 0,_latest_sequence);
auto data = _index.get_security(sec.id());
if (data == nullptr) return;
@@ -354,7 +377,6 @@ void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, con
for (size_t x = asks_size; x < COCONUT_MAX_DEPTH; ++x) {
cache.asks[x] = PriceSize::NO_MARKET;
}
// tracepoint (bts_trace,quoter,(char*) "onConsolidatedBookEnd", sec.id(), _latest_sequence);
}
//
// Trades
@@ -367,6 +389,7 @@ void on_trade(SecurityData* data, const onixs::PRICE& price, int size, const oni
}
void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& arg) {
tracepoint (bts_trace,trace_order,(char*) "onTrade.recovery", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != NULL) {
int size = 0;
@@ -377,6 +400,7 @@ void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const on
}
}
void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const onixs::TradeSummaryArgs& arg) {
tracepoint (bts_trace,trace_order,(char*) "onTrade.summary", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != NULL) {
auto size = arg.entry().entrySize();
@@ -385,6 +409,7 @@ void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const on
}
}
void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const onixs::TradeArgs& arg) {
tracepoint (bts_trace,trace_order,(char*) "onTrade.trade_args", 0,_latest_sequence);
if (arg.entry().updateAction() == onixs::UpdateAction::New) {
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
@@ -398,6 +423,7 @@ void MDP3Listener::onTrade(onixs::Handler&, const onixs::Security& sec, const on
//
// RFQ
void MDP3Listener::onQuoteRequest(onixs::Handler&, const onixs::Security& sec, const onixs::QuoteRequestRelatedSymArgs& arg) {
tracepoint (bts_trace,trace_order,(char*) "onQuoteRequest", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
auto ts = utils::TimeStamp::now();
@@ -446,6 +472,7 @@ void MDP3Listener::onQuoteRequest(onixs::Handler&, const onixs::Security& sec, c
//
// Volume
void MDP3Listener::onElectronicVolume(onixs::Handler&, const onixs::Security& sec, const onixs::VolumeArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onElectronicVolume.volume", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
bts_assert_d( args.entry().entryType().value() == 'e');
@@ -455,6 +482,7 @@ void MDP3Listener::onElectronicVolume(onixs::Handler&, const onixs::Security& se
}
}
void MDP3Listener::onElectronicVolume(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onElectronicVolume.recovery", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data) {
int32_t size;
@@ -467,6 +495,7 @@ void MDP3Listener::onElectronicVolume(onixs::Handler&, const onixs::Security& se
//
// Misc
void MDP3Listener::onSettlementPrice(onixs::Handler&, const onixs::Security& sec, const onixs::DailyStatisticsArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onSettlementPrice.daily", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
onixs::PRICE price;
@@ -481,6 +510,7 @@ void MDP3Listener::onSettlementPrice(onixs::Handler&, const onixs::Security& sec
}
}
void MDP3Listener::onSettlementPrice(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onSettlementPrice.recovery", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
onixs::PRICE price;
@@ -495,6 +525,7 @@ void MDP3Listener::onSettlementPrice(onixs::Handler&, const onixs::Security& sec
}
}
void MDP3Listener::onOpenInterest(onixs::Handler&, const onixs::Security& sec, const onixs::DailyStatisticsArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onOpenInterest.daily", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
onixs::PRICE price;
@@ -504,6 +535,7 @@ void MDP3Listener::onOpenInterest(onixs::Handler&, const onixs::Security& sec, c
}
}
void MDP3Listener::onOpenInterest(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onOpenInterest.recovery", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
onixs::PRICE price;
@@ -513,6 +545,7 @@ void MDP3Listener::onOpenInterest(onixs::Handler&, const onixs::Security& sec, c
}
}
void MDP3Listener::onOpeningPrice(onixs::Handler&, const onixs::Security& sec, const onixs::SessionStatisticsArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onOpeningPrice.session", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
onixs::PRICE price = args.entry().entryPx();
@@ -525,6 +558,7 @@ void MDP3Listener::onOpeningPrice(onixs::Handler&, const onixs::Security& sec, c
}
}
void MDP3Listener::onOpeningPrice(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onOpeningPrice.recovery", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
onixs::PRICE price;
@@ -539,6 +573,7 @@ void MDP3Listener::onOpeningPrice(onixs::Handler&, const onixs::Security& sec, c
}
}
void MDP3Listener::onHighTradePrice(onixs::Handler&, const onixs::Security& sec, const onixs::SessionStatisticsArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onHighTradePrice.session", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
auto display_factor = data->handle.security().display_factor;
@@ -548,6 +583,7 @@ void MDP3Listener::onHighTradePrice(onixs::Handler&, const onixs::Security& sec,
}
}
void MDP3Listener::onHighTradePrice(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onHighTradePrice.recovery", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
auto display_factor = data->handle.security().display_factor;
@@ -559,6 +595,7 @@ void MDP3Listener::onHighTradePrice(onixs::Handler&, const onixs::Security& sec,
}
}
void MDP3Listener::onLowTradePrice(onixs::Handler&, const onixs::Security& sec, const onixs::SessionStatisticsArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onLowTradePrice.session", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
auto display_factor = data->handle.security().display_factor;
@@ -568,6 +605,7 @@ void MDP3Listener::onLowTradePrice(onixs::Handler&, const onixs::Security& sec,
}
}
void MDP3Listener::onLowTradePrice(onixs::Handler&, const onixs::Security& sec, const onixs::RecoveryEntryArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onLowTradePrice.recovery", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
auto display_factor = data->handle.security().display_factor;
@@ -579,6 +617,7 @@ void MDP3Listener::onLowTradePrice(onixs::Handler&, const onixs::Security& sec,
}
}
void MDP3Listener::onHighestBid(onixs::Handler&, const onixs::Security& sec, const onixs::SessionStatisticsArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onHighestBid.session", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
auto display_factor = data->handle.security().display_factor;
@@ -588,6 +627,7 @@ void MDP3Listener::onHighestBid(onixs::Handler&, const onixs::Security& sec, con
}
}
void MDP3Listener::onLowestOffer(onixs::Handler&, const onixs::Security& sec, const onixs::SessionStatisticsArgs& args) {
tracepoint (bts_trace,trace_order,(char*) "onLowestOffer.session", 0,_latest_sequence);
auto* data = _index.get_security(sec.id());
if (data != nullptr) {
auto display_factor = data->handle.security().display_factor;
@@ -650,3 +690,4 @@ md::MDP3Listener::BookData::BookData(MDProducerHandle & handle) : handle(handle)
}}//namespace
@@ -7,7 +7,7 @@
#include "server/sparkd/orderman/events/NewExecutionReportEvent.h"
#include "server/sparkd/orderman/events/NewSecuritiesEvent.h"
#include "server/sparkd/orderman/orderman_utils/fix_order_codec.h"
#include "server/sparkd/tracing/bts_trace.h"
#include "lib/logging/logging.h"
#include <boost/pool/object_pool.hpp>
@@ -265,8 +265,7 @@ void ExecutionGateway::on_outgoing_fix(
mercury::Update<OutFIXMsgEvent> const &msg) {
if (msg.events)
for (auto const &ev : *(msg.events)) {
LOG(Latency, Info, "Gateway {} {}", ev.cid,
utils::TimeStamp::now().to_uint64());
tracepoint (bts_trace,trace_order,(char*) "on_outgoing_fix", 0,ev.cid);
me->gw.send(ev.cid, ev.msg);
}
}
+2 -2
View File
@@ -9,6 +9,7 @@
#include "server/sparkd/mercury/mercury_utils/IPollableActor.h"
#include "server/sparkd/orderman/events/BookUpdateEvent.h"
#include "server/sparkd/orderman/events/MDRequestEvent.h"
#include "server/sparkd/tracing/bts_trace.h"
namespace bts {
namespace orderman {
@@ -64,8 +65,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
md::HandleReadResult::SUCCESS) {
book._security_key = SecurityKey(handle->key());
corid = handle->sequence();
LOG(Latency, Info, "MDSource {} {}", corid,
utils::TimeStamp::now().to_uint64());
tracepoint (bts_trace,trace_order,(char*) "MDSource", 0, corid);
publish(_book_update, book, corid);
}
}
@@ -47,34 +47,34 @@ public:
bool is_active() const {return _active;}
void activate(Price p, Quantity q)
void activate(Price p, Quantity q, TickToTradeCorID cid = 0)
{
bts_assert(_order);
bts_assert(!_active);
_order_services->send_activate(*_order,p,q);
_order_services->send_activate(*_order,p,q, cid);
_active = true;
}
void update(Price p, Quantity q)
void update(Price p, Quantity q, TickToTradeCorID cid = 0)
{
bts_assert(_order);
bts_assert(_active);
_order_services->send_update(_order->key(),p,q);
_order_services->send_update(_order->key(),p,q,cid);
}
void crack()
void crack(TickToTradeCorID cid = 0)
{
bts_assert(_order);
bts_assert(_active);
_order_services->send_crack(_order->key());
_order_services->send_crack(_order->key(),cid);
}
void cancel()
void cancel(TickToTradeCorID cid = 0)
{
bts_assert(_order);
bts_assert(_active);
_order_services->send_cancel(_order->key());
_order_services->send_cancel(_order->key(),cid);
}
private:
@@ -102,7 +102,8 @@ void OrderManager::on_order_recovery(
auto serv_ptr = oi.order_services.get();
me->orders[order.key()] = std::move(oi);
order.on_activate(Price(ostate.price()),
Quantity(ostate.quantity()), *serv_ptr);
Quantity(ostate.quantity()), *serv_ptr,
TickToTradeCorID(0));
}
// signal that we are ready
@@ -133,7 +134,7 @@ void OrderManager::on_execution_report(
auto serv_ptr = oi.order_services.get();
me->orders[order->key()] = std::move(oi);
order->on_activate(report.data->price, report.data->quantity,
*serv_ptr);
*serv_ptr, TickToTradeCorID(0));
}
}
}
@@ -148,7 +149,8 @@ void OrderManager::on_activate_order(
OrderInfo oi{*order, me->services_data};
auto serv_ptr = oi.order_services.get();
me->orders[k] = std::move(oi);
order->on_activate(ev.price, ev.quantity, *serv_ptr);
order->on_activate(ev.price, ev.quantity, *serv_ptr,
ev.correlation_id);
}
}
@@ -158,7 +160,8 @@ void OrderManager::on_update_order(const mercury::Update<UpdateOrderEvent> &m) {
auto k = ev.key();
auto it = me->orders.find(k);
if (it != me->orders.end())
it->second.order->on_update(ev.price, ev.quantity);
it->second.order->on_update(ev.price, ev.quantity,
ev.correlation_id);
else {
LOG(OrderMan, Error,
"Received an update request for an unknown order {}. "
@@ -174,7 +177,7 @@ void OrderManager::on_cancel_order(const mercury::Update<CancelOrderEvent> &m) {
auto k = ev.key();
auto it = me->orders.find(k);
if (it != me->orders.end())
it->second.order->on_cancel();
it->second.order->on_cancel(ev.correlation_id);
else {
LOG(OrderMan, Error,
"Received a cancel request for an unknown order {}. "
@@ -190,7 +193,7 @@ void OrderManager::on_crack_order(const mercury::Update<CrackOrderEvent> &m) {
auto k = ev.key();
auto it = me->orders.find(k);
if (it != me->orders.end())
it->second.order->on_crack();
it->second.order->on_crack(ev.correlation_id);
else {
LOG(OrderMan, Error,
"Received a crack request for an unknown order {}. "
@@ -112,17 +112,17 @@ class OrderServices {
friend class ChildOrder;
void send_activate(Order &o, Price p, Quantity q) {
_data.owner.publish(_data.activate_order_pub, o, p, q);
void send_activate(Order &o, Price p, Quantity q, TickToTradeCorID cid = 0) {
_data.owner.publish(_data.activate_order_pub, o, p, q,cid);
}
void send_update(OrderKey k, Price p, Quantity q) {
_data.owner.publish(_data.update_order_pub, k, p, q);
void send_update(OrderKey k, Price p, Quantity q, TickToTradeCorID cid = 0) {
_data.owner.publish(_data.update_order_pub, k, p, q,cid);
}
void send_crack(OrderKey k) {
_data.owner.publish(_data.crack_order_pub, k);
void send_crack(OrderKey k, TickToTradeCorID cid = 0) {
_data.owner.publish(_data.crack_order_pub, k, cid);
}
void send_cancel(OrderKey k) {
_data.owner.publish(_data.cancel_order_pub, k);
void send_cancel(OrderKey k, TickToTradeCorID cid = 0) {
_data.owner.publish(_data.cancel_order_pub, k, cid);
}
Order &_owner;
@@ -15,12 +15,17 @@ public:
using SynopsisPolicy = mercury::NoSynopsis;
ActivateOrderEvent (Order &o,Price p, Quantity q) :
order (&o),price(p),quantity(q)
order (&o),price(p),quantity(q), correlation_id(0)
{}
ActivateOrderEvent (Order &o,Price p, Quantity q,TickToTradeCorID cid) :
order (&o),price(p),quantity(q), correlation_id(cid)
{}
Order* order;
Price price;
Quantity quantity;
TickToTradeCorID correlation_id;
};
}}
@@ -13,11 +13,13 @@ class CancelOrderEvent : public mercury::Event<CancelOrderEvent>
public:
using SynopsisPolicy = mercury::NoSynopsis;
CancelOrderEvent (OrderKey k) : k (k) {}
CancelOrderEvent (OrderKey k) : k (k), correlation_id(0) {}
CancelOrderEvent (OrderKey k,TickToTradeCorID cid) : k (k), correlation_id(cid) {}
// used by ContentSubscription
OrderKey key() const {return k;}
OrderKey k;
TickToTradeCorID correlation_id;
};
}}
@@ -2,23 +2,23 @@
#include <memory>
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
#include "server/sparkd/mercury/events/Event.h"
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
namespace bts {
namespace orderman {
class CrackOrderEvent : public mercury::Event<CrackOrderEvent>
{
public:
class CrackOrderEvent : public mercury::Event<CrackOrderEvent> {
public:
using SynopsisPolicy = mercury::NoSynopsis;
CrackOrderEvent(OrderKey k) : k (k) {}
CrackOrderEvent(OrderKey k, TickToTradeCorID cid = 0)
: k(k), correlation_id(cid) {}
// used by ContentSubscription
OrderKey key() const {return k;}
// used by ContentSubscription
OrderKey key() const { return k; }
OrderKey k;
TickToTradeCorID correlation_id;
};
}}
}
}
@@ -14,7 +14,11 @@ public:
using SynopsisPolicy = mercury::NoSynopsis;
UpdateOrderEvent (OrderKey k, Price p, Quantity q) :
k(k), price(p), quantity(q)
k(k), price(p), quantity(q), correlation_id(0)
{}
UpdateOrderEvent (OrderKey k, Price p, Quantity q,TickToTradeCorID cid) :
k(k), price(p), quantity(q), correlation_id(cid)
{}
// used by ContentSubscription
@@ -23,6 +27,7 @@ public:
OrderKey k;
Price price;
Quantity quantity;
TickToTradeCorID correlation_id;
};
}}
@@ -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());
@@ -79,7 +79,7 @@ public:
return result;
}
Limiter::LimiterQueryResult can_update_order(api::Side side, Quantity from, Quantity to) const{
Limiter::LimiterQueryResult result;
Limiter::LimiterQueryResult result;result.ok=true;
if (to.value() > _clip_size){
result.ok = false;
result.error_msg = fmt::format("Security Clip Size (Max {} Qty {})", _clip_size, to.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;
@@ -88,7 +88,7 @@ void IcebergOrder::on_init_recovered(api::orderman::OrderRecord const &rec,
}
}
void IcebergOrder::on_activate(Price p, Quantity q, OrderServices &services) {
void IcebergOrder::on_activate(Price p, Quantity q, OrderServices &services, TickToTradeCorID) {
bts_assert(!me->services);
me->services = &services;
me->child_builder.activate(services);
@@ -125,7 +125,7 @@ void IcebergOrder::on_activate(Price p, Quantity q, OrderServices &services) {
report_state();
}
void IcebergOrder::on_update(Price p, Quantity q) {
void IcebergOrder::on_update(Price p, Quantity q, TickToTradeCorID) {
switch (me->state.status()) {
case ORDER_STATUS_WORKING:
case ORDER_STATUS_PENDING_WORKING:
@@ -151,9 +151,9 @@ void IcebergOrder::on_update(Price p, Quantity q) {
report_state();
}
void IcebergOrder::on_crack() {}
void IcebergOrder::on_crack(TickToTradeCorID) {}
void IcebergOrder::on_cancel() {
void IcebergOrder::on_cancel(TickToTradeCorID) {
switch (me->state.status()) {
case ORDER_STATUS_WORKING:
LOG(OrderMan, Info, "Canceling Iceberg Order {}", key().value());
@@ -19,10 +19,10 @@ public:
const PreActivationOrderServices&) override;
void on_init_recovered(api::orderman::OrderRecord const&,
const PreActivationOrderServices &) override;
void on_activate(Price,Quantity,OrderServices &) override ;
void on_update(Price , Quantity ) override ;
void on_crack() override ;
void on_cancel() override ;
void on_activate(Price,Quantity,OrderServices &, TickToTradeCorID) override ;
void on_update(Price , Quantity,TickToTradeCorID ) override ;
void on_crack(TickToTradeCorID) override ;
void on_cancel(TickToTradeCorID) override ;
private:
void report_state();
@@ -11,6 +11,7 @@
#include "server/sparkd/orderman/orderman_utils/fix_order_codec.h"
#include "server/sparkd/orderman/orders/PreActivationOrderServices.h"
#include "server/sparkd/securities/ISecurityDB.h"
#include "server/sparkd/tracing/bts_trace.h"
#include "server/sparkd/orderman/actors/order_manager/OrderServices.h"
@@ -78,10 +79,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;
}
@@ -266,7 +264,9 @@ void NativeOrder::on_init_from_exe_report(
NativeOrder::~NativeOrder() {}
void NativeOrder::on_activate(Price p, Quantity q,
bts::orderman::OrderServices &services) {
bts::orderman::OrderServices &services,
TickToTradeCorID cid) {
tracepoint (bts_trace,trace_order,(char*) "on_activate", 0,cid);
bts_assert(!me->services);
me->services = &services;
@@ -343,10 +343,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;
}
@@ -361,7 +358,7 @@ void NativeOrder::on_activate(Price p, Quantity q,
auto &f = stream.new_msg(jetfix::MessageTypes::NewOrderSingle);
f.append(me->create_encoding);
stream.close();
me->session_services->send_fix_buffer(0, buffer);
me->session_services->send_fix_buffer(cid, buffer);
me->state.set_status(ORDER_STATUS_PENDING_WORKING);
me->state.set_price(p.value());
me->state.set_quantity(q.value());
@@ -428,7 +425,8 @@ void NativeOrder::on_activate(Price p, Quantity q,
report_state();
}
void NativeOrder::on_update(Price p, Quantity q) {
void NativeOrder::on_update(Price p, Quantity q, TickToTradeCorID cid) {
tracepoint (bts_trace,trace_order,(char*) "on_update", 0,cid);
switch (me->state.status()) {
case ORDER_STATUS_WORKING: {
@@ -458,7 +456,7 @@ void NativeOrder::on_update(Price p, Quantity q) {
transition(me->state.status(), msg);
else {
me->state.set_last_msg(msg);
on_cancel();
on_cancel(cid);
}
} else {
@@ -478,7 +476,7 @@ void NativeOrder::on_update(Price p, Quantity q) {
auto &f = stream.new_msg(jetfix::MessageTypes::CancelReplace);
f.append(me->update_encoding);
stream.close();
me->session_services->send_fix_buffer(0, buffer);
me->session_services->send_fix_buffer(cid, buffer);
if (me->state.last_msg().size())
me->state.set_last_msg("");
}
@@ -503,9 +501,9 @@ void NativeOrder::on_update(Price p, Quantity q) {
report_state();
}
void NativeOrder::on_crack() {}
void NativeOrder::on_crack(TickToTradeCorID) {}
void NativeOrder::on_cancel() {
void NativeOrder::on_cancel(TickToTradeCorID cid) {
switch (me->state.status()) {
case ORDER_STATUS_WORKING: {
@@ -518,7 +516,7 @@ void NativeOrder::on_cancel() {
auto &f = stream.new_msg(jetfix::MessageTypes::Cancel);
f.append(me->cancel_encoding);
stream.close();
me->session_services->send_fix_buffer(0, buffer);
me->session_services->send_fix_buffer(cid, buffer);
break;
}
@@ -829,10 +827,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 +970,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;
}
@@ -23,10 +23,10 @@ public:
const PreActivationOrderServices&) override;
void on_init_recovered(api::orderman::OrderRecord const&,
const PreActivationOrderServices &) override;
void on_activate(Price,Quantity,OrderServices &) override ;
void on_update(Price , Quantity ) override ;
void on_crack() override ;
void on_cancel() override ;
void on_activate(Price,Quantity,OrderServices &, TickToTradeCorID) override ;
void on_update(Price , Quantity,TickToTradeCorID ) override ;
void on_crack(TickToTradeCorID) override ;
void on_cancel(TickToTradeCorID) override ;
void on_init_from_exe_report(ExecutionReport const&,const PreActivationOrderServices &);
private:
+27 -19
View File
@@ -1,18 +1,25 @@
#pragma once
#include <memory>
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
#include <memory>
// fwd
namespace bts {
namespace securities {class ISecurityDB;}
namespace orderman {class IOrderFactory;}
namespace securities {
class ISecurityDB;
}
namespace orderman {
class IOrderFactory;
}
namespace api {
namespace orderman {
class OrderDefinition;
class OrderRecord;
class OrderState;
}}}
}
}
}
// end fwd
namespace bts {
namespace orderman {
@@ -20,32 +27,33 @@ namespace orderman {
class PreActivationOrderServices;
class OrderServices;
class Order
{
public:
class Order {
public:
virtual ~Order();
OrderKey key() const {return _key;}
OrderKey parent_key() const {return _parent_key;}
OrderKey key() const { return _key; }
OrderKey parent_key() const { return _parent_key; }
virtual api::orderman::OrderDefinition const&definition() const = 0;
virtual api::orderman::OrderDefinition const &definition() const = 0;
// construction - can be slow
virtual void on_init_new(api::orderman::OrderDefinition const&, const PreActivationOrderServices&) = 0;
virtual void on_init_recovered(api::orderman::OrderRecord const&, const PreActivationOrderServices &) = 0;
virtual void on_init_new(api::orderman::OrderDefinition const &,
const PreActivationOrderServices &) = 0;
virtual void on_init_recovered(api::orderman::OrderRecord const &,
const PreActivationOrderServices &) = 0;
// activation - cannot be slow
virtual void on_activate(Price, Quantity,OrderServices &) = 0;
virtual void on_activate(Price, Quantity, OrderServices &, TickToTradeCorID) = 0;
// order is running, maintenance
virtual void on_update(Price , Quantity ) = 0;
virtual void on_crack() = 0;
virtual void on_cancel() = 0;
virtual void on_update(Price, Quantity, TickToTradeCorID) = 0;
virtual void on_crack(TickToTradeCorID) = 0;
virtual void on_cancel(TickToTradeCorID) = 0;
private:
private:
OrderKey _key;
OrderKey _parent_key;
friend class OrderFactory;
};
}}
}
}
@@ -83,7 +83,7 @@ void TimeBoxOrder::on_init_recovered(api::orderman::OrderRecord const &rec,
}
}
void TimeBoxOrder::on_activate(Price p, Quantity q, OrderServices &services) {
void TimeBoxOrder::on_activate(Price p, Quantity q, OrderServices &services,TickToTradeCorID) {
bts_assert(!me->services);
me->services = &services;
me->child_builder.activate(services);
@@ -121,7 +121,7 @@ void TimeBoxOrder::on_activate(Price p, Quantity q, OrderServices &services) {
report_state();
}
void TimeBoxOrder::on_update(Price p, Quantity q) {
void TimeBoxOrder::on_update(Price p, Quantity q,TickToTradeCorID) {
LOG(OrderMan, Info, "Updating TimeBox Order {}", key().value());
switch (me->state.status()) {
@@ -181,9 +181,9 @@ void TimeBoxOrder::on_update(Price p, Quantity q) {
report_state();
}
void TimeBoxOrder::on_crack() {}
void TimeBoxOrder::on_crack(TickToTradeCorID) {}
void TimeBoxOrder::on_cancel() {
void TimeBoxOrder::on_cancel(TickToTradeCorID) {
LOG(OrderMan, Info, "Canceling TimeBox Order {}", key().value());
switch (me->state.status()) {
@@ -19,10 +19,10 @@ public:
const PreActivationOrderServices&) override;
void on_init_recovered(api::orderman::OrderRecord const&,
const PreActivationOrderServices &) override;
void on_activate(Price,Quantity,OrderServices &) override ;
void on_update(Price , Quantity ) override ;
void on_crack() override ;
void on_cancel() override ;
void on_activate(Price,Quantity,OrderServices &, TickToTradeCorID) override ;
void on_update(Price , Quantity,TickToTradeCorID ) override ;
void on_crack(TickToTradeCorID) override ;
void on_cancel(TickToTradeCorID) override ;
private:
void report_state();
@@ -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,TickToTradeCorID)
{
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,TickToTradeCorID)
{
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(TickToTradeCorID) {}
void BookAlgo::on_cancel(TickToTradeCorID)
{
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 &, TickToTradeCorID) override ;
void on_update(Price , Quantity,TickToTradeCorID ) override ;
void on_crack(TickToTradeCorID) override ;
void on_cancel(TickToTradeCorID) 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;
};
}}
@@ -6,98 +6,88 @@
#include "server/sparkd/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderLegData.h"
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderBookkeepingData.h"
#include "server/sparkd/tracing/bts_trace.h"
namespace bts {
namespace orderman {
/**************************Cons/Dest*******************************/
SpreaderOrder::SpreaderOrder()
:
_cancelled (false),
_cancel_timeout (5000),// TODO:get it from config
_active(true),
_bookkeeping_data_ptr(nullptr),
_order_services(nullptr),
_reported_state(),
_state_worker(nullptr),
_start_state_worker(this), _working_state_worker(this), _waiting_for_cancel_all_state_worker(this), _end_state_worker(this)
{
:
_cancelled (false),
_cancel_timeout (5000),// TODO:get it from config
_active(true),
_bookkeeping_data_ptr(),
_order_services(nullptr),
_reported_state(),
_state_worker(nullptr),
_start_state_worker(this), _working_state_worker(this), _waiting_for_cancel_all_state_worker(this), _end_state_worker(this){
_transform_to_START();
}
SpreaderOrder::~SpreaderOrder () {}
/***********************************************************************/
/******************************Order Derived****************************/
/***********************************************************************/
const api::orderman::OrderDefinition& SpreaderOrder::definition() const
{
// Order Derived ////////////////////
/////////////////////////////////////
const api::orderman::OrderDefinition& SpreaderOrder::definition() const{
bts_assert(_inited());
return _bookkeeping_data().order_def();
}
void SpreaderOrder::on_init_new(const api::orderman::OrderDefinition& order_def, const PreActivationOrderServices& pao_services)
{
if (!_active) return;
void SpreaderOrder::on_init_new(const api::orderman::OrderDefinition& order_def,
const PreActivationOrderServices& pao_services){
if (!_active) return;
_on_init_new(order_def, SpreaderPreActivationOrderServices(&pao_services));
_report_state ();
}
void SpreaderOrder::on_init_recovered(const bts::api::orderman::OrderRecord& order_record, const bts::orderman::PreActivationOrderServices& pao_services)
{
void SpreaderOrder::on_init_recovered(const bts::api::orderman::OrderRecord& order_record,
const PreActivationOrderServices& pao_services){
LOG(OrderMan, Info, "SpreaderOrder.on_init_recovered order_key: {}", key().value());
_state_worker->on_init_recovered(order_record, SpreaderPreActivationOrderServices(&pao_services));
_report_state();
}
void SpreaderOrder::on_activate(Price price, Quantity quantity, OrderServices& services)
{
if (!_active) return;
void SpreaderOrder::on_activate(Price price, Quantity quantity,
OrderServices& services, TickToTradeCorID){
if (!_active) return;
SpreaderOrderServices spreader_order_services (&services);
_on_activate(price, quantity, spreader_order_services);
_report_state();
}
void SpreaderOrder::on_update(Price price, Quantity quantity)
{
void SpreaderOrder::on_update(Price price, Quantity quantity,TickToTradeCorID){
if (!_active) return;
LOG(OrderMan, Info, "SpreaderOrder.on_update key: {} price: {} quantity: {}", key().value(), price.value(), quantity.value());
_state_worker->on_update(price, quantity);
_report_state();
}
void SpreaderOrder::on_cancel()
{
void SpreaderOrder::on_cancel(TickToTradeCorID){
if (!_active) return;
LOG(OrderMan, Info, "SpreaderOrder.on_cancel key: {}", key().value());
_state_worker->on_cancel();
_report_state();
}
void SpreaderOrder::on_crack()
{
void SpreaderOrder::on_crack(TickToTradeCorID){
if (!_active) return;
LOG(OrderMan, Info, "SpreaderOrder.on_crack key: {}", key().value());
_state_worker->on_crack();
_report_state();
}
/***********************************************************************/
/****************************ISpreaderManager***************************/
/***********************************************************************/
void SpreaderOrder::setup_new_quoter_order(size_t leg_index)
{
// ISpreaderManager ////////////////////
////////////////////////////////////////
void SpreaderOrder::setup_new_quoter_order(size_t leg_index){
LOG(OrderMan, Info, "SpreaderOrder.setup_new_quoter_order key: {} leg: {}", key().value(), leg_index);
SpreaderLegData& leg_data = _bookkeeping_data().get_leg_data(leg_index);
bts::orderman::ChildOrder child_order = leg_data.quoter_order_builder().build();
leg_data.order_update_message().re_init(child_order);
}
/***********************************************************************/
/******************************Event Handlers***************************/
/***********************************************************************/
void SpreaderOrder::_on_time(const bts::mercury::TimerEvent& t_event)
{
// events from orders system ////////////////////
/////////////////////////////////////////////////
void SpreaderOrder::_on_time(const bts::mercury::TimerEvent& t_event){
_curr_timestamp = t_event.ts;
if (!_active) return;
_state_worker->on_time (_curr_timestamp);
}
void SpreaderOrder::_on_book_update(const bts::orderman::BookUpdateEvent& book_event)
{
void SpreaderOrder::_on_book_update(const BookUpdateEvent& book_event){
if (!_active) return;
tracepoint (bts_trace,trace_order,(char*) "_on_book_update", 0,book_event.correlation_id);
_state_worker->on_book (book_event.correlation_id, book_event.book);
}
void SpreaderOrder::_on_child_order_update(const bts::orderman::OrderStateUpdateEvent& ord_ev)
{
void SpreaderOrder::_on_child_order_update(const OrderStateUpdateEvent& ord_ev){
if (!_active) return;
SecurityKey sec_key = SecurityKey(ord_ev.definition->security_key());
OrderKey order_key = ord_ev.key;
@@ -105,21 +95,19 @@ void SpreaderOrder::_on_child_order_update(const bts::orderman::OrderStateUpdate
Quantity filled_qty = Quantity (ord_ev.state.filled_quantity());
Price last_price = Price(ord_ev.state.last_price());
LOG(OrderMan, Diag, "SpreaderOrder::_on_child_order_update key: {} sec_key: {} order_key: {} status: {} filled_qty: {} last_price: {} msg: {}", key(), sec_key, order_key, status, filled_qty, last_price, ord_ev.state.last_msg ());
if (!SpreaderUtils::order_acknowledged(status))
{
if (!SpreaderUtils::order_acknowledged(status)){
LOG(OrderMan, Diag, "SpreaderOrder::_on_child_order_update, unack'ed callback for key: {} sec_key: {} order_key: {} status: {} ignoring", key(), sec_key, order_key, status);
bts_assert(filled_qty.value() == 0);
return;
}
SpreaderLegData* leg_data_ptr = _bookkeeping_data().find_leg_from_quoter_orderkey(order_key); // can also be found through sec_key
if (leg_data_ptr != nullptr)
{
if (leg_data_ptr != nullptr){
SpreaderLegData& leg_data = *leg_data_ptr;
ChildOrderError order_error = _get_order_error (ord_ev, leg_data.quoter().expecting_cancel_update());
LOG(OrderMan, Info, "SpreaderOrder::_on_child_order_update quoter order key: {} sec_key: {} order_key: {} status: {} filled_qty: {} last_price: {} msg: {} child_order_error: {}", key(), sec_key, order_key, status, filled_qty, last_price, ord_ev.state.last_msg (), static_cast<int>(order_error));
LOG(OrderMan, Info, "SpreaderOrder::_on_child_order_update quoter order key: {} sec_key: {} order_key: {} status: {} filled_qty: {} last_price: {} msg: {} child_order_error: {}",
key(), sec_key, order_key, status, filled_qty, last_price, ord_ev.state.last_msg (), static_cast<int>(order_error));
leg_data.order_update_message().on_order_update(ord_ev);
if (order_error == ChildOrderError::NONE)
{
if (order_error == ChildOrderError::NONE){
if (leg_data.order_update_message().last_update_filled_quantity().value() > 0)
_state_worker->on_quoter_order_fill (leg_data);
else
@@ -128,98 +116,87 @@ void SpreaderOrder::_on_child_order_update(const bts::orderman::OrderStateUpdate
else
_state_worker->on_quoter_order_error (leg_data);
}
else
{
else{
// should be a hedge order then
SingleFillHedger* hedger = _bookkeeping_data().find_hedger_from_orderkey(order_key);
if (hedger != nullptr)
{
ChildOrderError order_error = _get_order_error (ord_ev, hedger->expecting_cancel_update());
LOG(OrderMan, Info, "SpreaderOrder::_on_child_order_update hedger order: key: {} sec_key: {} order_key: {} status: {} filled_qty: {} last_price: {} msg: {} child_order_error: {}", key(), sec_key, order_key, status, filled_qty, last_price, ord_ev.state.last_msg (), static_cast<int>(order_error));
if (hedger != nullptr){
ChildOrderError order_error = _get_order_error (ord_ev, hedger->expecting_cancel_update());
LOG(OrderMan, Info, "SpreaderOrder::_on_child_order_update hedger order: key: {} sec_key: {} order_key: {} status: {} filled_qty: {} last_price: {} msg: {} child_order_error: {}",
key(), sec_key, order_key, status, filled_qty, last_price, ord_ev.state.last_msg (), static_cast<int>(order_error));
hedger->order_state().on_order_update(ord_ev);
if (order_error == ChildOrderError::NONE)
_state_worker->on_hedger_order_update (*hedger);
_state_worker->on_hedger_order_update (*hedger);
else
_state_worker->on_hedger_order_error (*hedger);
}
else
{
LOG(OrderMan, Error, "SpreaderOrder::_on_child_order_update unknown order key: {} sec_key: {} order_key: {} status: {} filled_qty: {} last_price: {} msg: {}", key(), sec_key, order_key, status, filled_qty, last_price, ord_ev.state.last_msg ());
}
LOG(OrderMan, Error, "SpreaderOrder::_on_child_order_update unknown order key: {} sec_key: {} order_key: {} status: {} filled_qty: {} last_price: {} msg: {}",
key(), sec_key, order_key, status, filled_qty, last_price, ord_ev.state.last_msg ());
}
_state_worker->on_check_termination ();
_report_state();
}
/***********************************************************************/
/*************************State Transformations*************************/
/***********************************************************************/
void SpreaderOrder::_transform_to_START()
{
// State Transformations ////////////////////
/////////////////////////////////////////////
void SpreaderOrder::_transform_to_START(){
bts_assert (_state_worker == nullptr);
_switch_state (&_start_state_worker);
}
void SpreaderOrder::_transform_to_WORKING()
{
void SpreaderOrder::_transform_to_WORKING(){
bts_assert(_state_worker->state() == SpreaderState::START);
_switch_state(&_working_state_worker);
}
void SpreaderOrder::_transform_to_WAITING_FOR_CANCEL_ALL()
{
void SpreaderOrder::_transform_to_WAITING_FOR_CANCEL_ALL(){
bts_assert(_state_worker->state() == SpreaderState::WORKING);
_switch_state(&_waiting_for_cancel_all_state_worker);
}
void SpreaderOrder::_transform_to_END(std::string*)
{
void SpreaderOrder::_transform_to_END(std::string*){
bts_assert(_state_worker->state() != SpreaderState::END);
_switch_state(&_end_state_worker);
}
void SpreaderOrder::_switch_state (ISpreaderStateWorker* new_state)
{
LOG(OrderMan, Info, "SpreaderOrder::_switch_state key: {}, {} -> {} ", key().value(), (_state_worker == nullptr ? -1 : (int)_state_worker->state()), (int)new_state->state());
void SpreaderOrder::_switch_state (ISpreaderStateWorker* new_state){
LOG(OrderMan, Info, "SpreaderOrder::_switch_state key: {}, {} -> {} ",
key().value(),
(_state_worker == nullptr ? -1 : (int)_state_worker->state()),
(int)new_state->state());
_state_worker = new_state;
_state_worker->on_transition();
_report_state();
}
/***********************************************************************/
/*********************************Misc**********************************/
/***********************************************************************/
void SpreaderOrder::_on_init_new(const bts::api::orderman::OrderDefinition& order_def, const SpreaderPreActivationOrderServices& spreader_pao_services)
{
LOG(OrderMan, Info, "SpreaderOrder.on_init_new key: {} order_def: {}", key().value(), bts::utils::pb::to_string(order_def));
void SpreaderOrder::_on_init_new(const bts::api::orderman::OrderDefinition& order_def,
const SpreaderPreActivationOrderServices& spreader_pao_services){
LOG(OrderMan, Info, "SpreaderOrder.on_init_new key: {} order_def: {}",
key().value(), bts::utils::pb::to_string(order_def));
bts_assert(!_inited());
_state_worker->on_init_new(order_def, spreader_pao_services);
}
void SpreaderOrder::_on_activate(Price price, Quantity quantity, SpreaderOrderServices& order_services)
{
void SpreaderOrder::_on_activate(Price price, Quantity quantity, SpreaderOrderServices& order_services){
bts_assert(_inited());
bts_assert(!_activated());
LOG(OrderMan, Info, "SpreaderOrder.on_activate key: {} price: {} quantity: {}", key().value(), price.value(), quantity.value());
LOG(OrderMan, Info, "SpreaderOrder.on_activate key: {} price: {} quantity: {}",
key().value(), price.value(), quantity.value());
_order_services = new SpreaderOrderServices(order_services);
_state_worker->on_activate(price, quantity, *_order_services);
}
void SpreaderOrder::_cancel_orders(bool cancel_quotes, bool cancel_hedges)
{
LOG(OrderMan, Info, "SpreaderOrder::_cancel_orders key: {}, cancel_quotes: {} cancel_hedges: {}", key().value(), cancel_quotes, cancel_hedges);
for (size_t q_idx = 0; q_idx < _bookkeeping_data().n_legs(); q_idx++)
{
void SpreaderOrder::_cancel_orders(bool cancel_quotes, bool cancel_hedges){
LOG(OrderMan, Info, "SpreaderOrder::_cancel_orders key: {}, cancel_quotes: {} cancel_hedges: {}",
key().value(), cancel_quotes, cancel_hedges);
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 (cancel_quotes)
{
if (cancel_quotes){
if (leg_data.leg_property_data().is_active_quoting())
leg_data.quoter().cancel_order();
}
if (cancel_hedges)
for (size_t h_idx = 0; h_idx < leg_data.n_hedgers(); h_idx++)
{
for (size_t h_idx = 0; h_idx < leg_data.n_hedgers(); h_idx++){
SingleFillHedger& hedger = leg_data.get_hedger(h_idx);
hedger.cancel_hedge();
}
}
}
bool SpreaderOrder::_all_quoter_orders_in_terminal_state()
{
for (size_t q_idx = 0; q_idx < _bookkeeping_data().n_legs(); q_idx++)
{
bool SpreaderOrder::_all_quoter_orders_in_terminal_state(){
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())
if (!leg_data.quoter().is_terminal_state())
@@ -227,14 +204,10 @@ bool SpreaderOrder::_all_quoter_orders_in_terminal_state()
}
return true;
}
// same as _all_quoter_orders_in_terminal_state, for hedgers
bool SpreaderOrder::_all_hedger_orders_in_terminal_state()
{
for (size_t q_idx = 0; q_idx < _bookkeeping_data().n_legs(); q_idx++)
{
bool SpreaderOrder::_all_hedger_orders_in_terminal_state(){
for (size_t q_idx = 0; q_idx < _bookkeeping_data().n_legs(); q_idx++){
SpreaderLegData& leg_data = _bookkeeping_data().get_leg_data(q_idx);
for (size_t h_idx = 0; h_idx < leg_data.n_hedgers(); h_idx++)
{
for (size_t h_idx = 0; h_idx < leg_data.n_hedgers(); h_idx++){
SingleFillHedger& hedger = leg_data.get_hedger(h_idx);
if (!hedger.is_terminal_state())
return false;
@@ -242,30 +215,23 @@ bool SpreaderOrder::_all_hedger_orders_in_terminal_state()
}
return true;
}
void SpreaderOrder::_terminate_quoters ()
{
for (size_t q_idx = 0; q_idx < _bookkeeping_data().n_legs(); q_idx++)
{
void SpreaderOrder::_terminate_quoters (){
for (size_t q_idx = 0; q_idx < _bookkeeping_data().n_legs(); q_idx++){
SpreaderLegData& leg_data = _bookkeeping_data().get_leg_data(q_idx);
leg_data.quoter().terminate();
leg_data.quoter().terminate();
}
}
void SpreaderOrder::_terminate_hedgers ()
{
for (size_t q_idx = 0; q_idx < _bookkeeping_data().n_legs(); q_idx++)
{
void SpreaderOrder::_terminate_hedgers (){
for (size_t q_idx = 0; q_idx < _bookkeeping_data().n_legs(); q_idx++){
SpreaderLegData& leg_data = _bookkeeping_data().get_leg_data(q_idx);
for (size_t h_idx = 0; h_idx < leg_data.n_hedgers(); h_idx++)
{
for (size_t h_idx = 0; h_idx < leg_data.n_hedgers(); h_idx++){
SingleFillHedger& hedger = leg_data.get_hedger(h_idx);
hedger.terminate ();
}
}
}
bool SpreaderOrder::_quoters_can_accept_modification ()
{
for (size_t q_idx = 0; q_idx < _bookkeeping_data().n_legs(); q_idx++)
{
bool SpreaderOrder::_quoters_can_accept_modification (){
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())
if (!leg_data.quoter().accept_modifications())
@@ -273,46 +239,48 @@ bool SpreaderOrder::_quoters_can_accept_modification ()
}
return true;
}
void SpreaderOrder::_send_hedge(size_t filled_leg_index, Price filled_price, Quantity filled_quantity,
size_t hedge_leg_index)
{
void SpreaderOrder::_send_hedge(size_t filled_leg_index,
Price filled_price,
Quantity filled_quantity,
size_t hedge_leg_index){
LOG(OrderMan, Info, "SpreaderOrder::_send_hedge key: {}, filled_leg_index: {} filled_price: {} filled_quantity: {} hedge_leg_index: {}",
key().value(), filled_leg_index, filled_price.value(), filled_quantity.value(), hedge_leg_index);
Price hedge_price;
Quantity hedge_quantity;
_compute_hedge_price_quantity (filled_leg_index, filled_price, filled_quantity, hedge_leg_index,
_compute_hedge_price_quantity (filled_leg_index, filled_price,
filled_quantity, hedge_leg_index,
hedge_price, hedge_quantity);
SingleFillHedger& hedger = _bookkeeping_data().add_new_hedger (*this, hedge_leg_index, hedge_price, hedge_quantity);
SingleFillHedger& hedger = _bookkeeping_data().add_new_hedger (*this,
hedge_leg_index,
hedge_price,
hedge_quantity);
hedger.init ();
}
void SpreaderOrder::_compute_hedge_price_quantity(size_t filled_leg_index,
Price fill_price, Quantity fill_quantity,
size_t hedge_leg_index,
Price& hedge_price, Quantity& hedge_quantity)
{
Price& hedge_price, Quantity& hedge_quantity){
Price computed_hedge_price;
SpreaderLegData& filled_leg_data = _bookkeeping_data().get_leg_data(filled_leg_index);
SpreaderLegData& hedged_leg_data = _bookkeeping_data().get_leg_data(hedge_leg_index);
if (_bookkeeping_data().n_legs() == 2)
{
SpreaderUtils::compute_hedge_price_size_2_legs (_bookkeeping_data().price(),
filled_leg_data.leg_property_data(), fill_price, fill_quantity,
hedged_leg_data.leg_property_data(), computed_hedge_price, hedge_quantity);
}
else
{
else{
const QuoterLegDriverData& driver_data = filled_leg_data.quoter().driver_data(hedge_leg_index);
SpreaderUtils::compute_hedge_price_size_n_legs (filled_leg_data.leg_property_data(), fill_price, fill_quantity,
hedged_leg_data.leg_property_data(), driver_data,
computed_hedge_price, hedge_quantity);
}
hedge_price = SpreaderUtils::apply_pre_hedge_parameters (hedged_leg_data.leg_property_data().leg_params().pre_hedge_params(), hedged_leg_data.book(), hedged_leg_data.leg_property_data().trading_side(), hedged_leg_data.leg_property_data().tick_size(),
hedge_price = SpreaderUtils::apply_pre_hedge_parameters (hedged_leg_data.leg_property_data().leg_params().pre_hedge_params(),
hedged_leg_data.book(),
hedged_leg_data.leg_property_data().trading_side(),
hedged_leg_data.leg_property_data().tick_size(),
computed_hedge_price, hedge_quantity);
}
void SpreaderOrder::_report_state ()
{
if (_activated())
{
void SpreaderOrder::_report_state (){
if (_activated()){
SpreaderBookkeepingData& bookkeeping_data = _bookkeeping_data();
api::orderman::OrderStatus status = _get_order_status ();
Quantity filled_qty; Price avg_filled_price;
@@ -322,17 +290,15 @@ void SpreaderOrder::_report_state ()
_reported_state.set_quantity(bookkeeping_data.quantity().value());
_reported_state.set_filled_quantity(filled_qty.value());
_reported_state.set_avg_price(avg_filled_price.value());
// TODO fill in filled_qty etc
// TODO fill in filled_qty etc
LOG(OrderMan, Info, "SpreaderOrder::_report_state key: {} status: {} price: {} qty: {} filled: {} abg_price: {}",
key(), status, _reported_state.price(), _reported_state.quantity(), _reported_state.filled_quantity(), _reported_state.avg_price());
_order_services->report_state(_reported_state);
}
}
bts::api::orderman::OrderStatus SpreaderOrder::_get_order_status () const
{
SpreaderState curr_state = _state_worker == nullptr ? SpreaderState::START : _state_worker->state();
switch (curr_state)
{
bts::api::orderman::OrderStatus SpreaderOrder::_get_order_status () const{
SpreaderState curr_state = _state_worker == nullptr ? SpreaderState::START : _state_worker->state();
switch (curr_state){
case SpreaderState::WORKING:
return bts::api::orderman::OrderStatus::ORDER_STATUS_WORKING;
case SpreaderState::WAITING_FOR_CANCEL_ALL:
@@ -340,7 +306,7 @@ bts::api::orderman::OrderStatus SpreaderOrder::_get_order_status () const
case SpreaderState::END:
return _cancelled ?
bts::api::orderman::OrderStatus::ORDER_STATUS_CANCELED :
bts::api::orderman::OrderStatus::ORDER_STATUS_COMPLETED;
bts::api::orderman::OrderStatus::ORDER_STATUS_COMPLETED;
case SpreaderState::START:
return bts::api::orderman::OrderStatus::ORDER_STATUS_INACTIVE;
default:
@@ -18,7 +18,7 @@
#include "server/sparkd/orderman/events/BookUpdateEvent.h"
#include "server/sparkd/mercury/events/TimerEvent.h"
using namespace bts::orderman;
//using namespace bts::orderman;
namespace bts {
namespace api {namespace orderman {class OrderState; class CreateSpreaderOrder;}}
@@ -52,10 +52,11 @@ 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)
// state thats sent out in report (its kept here amd modified
// vs creating a new one everytime)
bts::api::orderman::OrderState _reported_state;
// timestamp of last the on_time call
bts::TimeStamp _curr_timestamp;
@@ -65,56 +66,86 @@ private:
SpreaderWorkingStateWorker _working_state_worker;
SpreaderWaitingForCancelAllStateWorker _waiting_for_cancel_all_state_worker;
SpreaderEndStateWorker _end_state_worker;
/**************************Cons/Dest*******************************/
// const/dest ////////////////////
// (can not be copy/mv cons'd or
// copied either way)
//////////////////////////////////
public:
SpreaderOrder();
SpreaderOrder(const SpreaderOrder&)=delete;
SpreaderOrder(SpreaderOrder&&)=delete;
SpreaderOrder& operator=(const SpreaderOrder&)=delete;
SpreaderOrder& operator=(SpreaderOrder&&)=delete;
virtual ~SpreaderOrder();
/**************************Order Derived***************************/
// Order Derived ////////////////////
/////////////////////////////////////
public:
// inherited function to return the OrderDefinition. asserts
// if on_init_new has not been called yet
const api::orderman::OrderDefinition& definition() const override;
// this is the construction, can be slow
void on_init_new(const bts::api::orderman::OrderDefinition& order_def, const bts::orderman::PreActivationOrderServices&) override;
void on_init_new(const bts::api::orderman::OrderDefinition& order_def,
const PreActivationOrderServices&) override;
// called when the order is recovered from disk (not dealing with it now)
void on_init_recovered(bts::api::orderman::OrderRecord const&, const bts::orderman::PreActivationOrderServices&) override;
void on_init_recovered(bts::api::orderman::OrderRecord const&,
const PreActivationOrderServices&) override;
// this is in the critical path, orders can be submitted now
// (this is only called once and after the on_init_new)
void on_activate(Price, Quantity, OrderServices&) override;
void on_activate(Price, Quantity, OrderServices&,TickToTradeCorID) override;
// called when the user (or a higher level order)
// updates the order's price or quantity
void on_update(Price, Quantity) override;
void on_update(Price, Quantity,TickToTradeCorID) override;
// called when the user (or higher level order) cancels an order
void on_cancel() override;
void on_cancel(TickToTradeCorID) override;
// crack means to let the lower level orders lose from the higher
// level order and cancel the higher level order
void on_crack() override;
/*****************************ISpreaderManager*******************************/
void on_crack(TickToTradeCorID) override;
// ISpreaderManager ////////////////////
////////////////////////////////////////
public:
bts::orderman::OrderKey spreader_order_key() const override {return key();}
bts::TimeStamp current_time() const override {return _curr_timestamp;}
const bts::orderman::Book& get_book(size_t leg_index) const override {return _bookkeeping_data().get_leg_data(leg_index).book();}
const LegPropertyData& get_leg_data(size_t leg_index) const override {return _bookkeeping_data().get_leg_data(leg_index).leg_property_data();}
bts::orderman::OrderKey spreader_order_key() const override {
return key();
}
bts::TimeStamp current_time() const override {
return _curr_timestamp;
}
const bts::orderman::Book& get_book(size_t leg_index) const override {
return _bookkeeping_data().get_leg_data(leg_index).book();
}
const LegPropertyData& get_leg_data(size_t leg_index) const override {
return _bookkeeping_data().get_leg_data(leg_index).leg_property_data();
}
void setup_new_quoter_order (size_t leg_index) override;
private:
/**************************Event Handlers***********************************/
// events
// events from orders system ////////////////////
/////////////////////////////////////////////////
void _on_time (const bts::mercury::TimerEvent&);
void _on_book_update(const bts::orderman::BookUpdateEvent&);
void _on_child_order_update(const bts::orderman::OrderStateUpdateEvent&);
/***************************State Transformations***************************/
void _on_book_update(const BookUpdateEvent&);
void _on_child_order_update(const OrderStateUpdateEvent&);
// State Transformations ////////////////////
/////////////////////////////////////////////
void _transform_to_START();
void _transform_to_WORKING();
void _transform_to_WAITING_FOR_CANCEL_ALL();
// optionally parameter to state the end reason
void _transform_to_END(std::string* m = nullptr);
// swtches to the state passed in the arg
void _switch_state (ISpreaderStateWorker* new_state);
/***************************Misc***************************/
bool _inited () const {return _bookkeeping_data_ptr != nullptr;}
bool _activated () {return _order_services != nullptr;}
void _on_init_new(const bts::api::orderman::OrderDefinition&, const SpreaderPreActivationOrderServices&);
// this means on_init_new has been called and we have the
// the structure built
// misc ////////////////////
////////////////////////////
bool _inited () const {
return _bookkeeping_data_ptr != nullptr;
}
// this means on_activate has been called
bool _activated () {
return _order_services != nullptr;
}
void _on_init_new(const bts::api::orderman::OrderDefinition&,
const SpreaderPreActivationOrderServices&);
void _on_activate(Price, Quantity, SpreaderOrderServices&);
// this calls the cancel on quoter and hedger orders
// this calls the cancel on quoters and hedgers
void _cancel_orders(bool cancel_quotes, bool cancel_hedges);
// iterates through all quoters and returns true if they
// are all in terminal state
@@ -126,12 +157,15 @@ private:
void _terminate_quoters ();
// same as the above for hedgers
void _terminate_hedgers();
// this returns true is all quoters can accept modification
bool _quoters_can_accept_modification();
// this sends a hedge (creates a hedger and inits it)
// on leg 'leg_to_hedge_index' in response to a fill
// in leg 'filled_leg_index' with price 'filled_price'
// on leg 'filled_leg_index' with price 'filled_price'
// quantity 'filled_quantity'
void _send_hedge(size_t filled_leg_index, Price filled_price, Quantity filled_quantity, size_t leg_to_hedge_index);
void _send_hedge(size_t filled_leg_index, Price filled_price,
Quantity filled_quantity,
size_t leg_to_hedge_index);
// purely a computating function that computes the
// hedge price and quantity (last 2 args) for leg
// 'hedged_leg_index' for a fill on leg 'filled_leg_index'
@@ -142,32 +176,38 @@ private:
Price& hedge_price, Quantity& hedge_quantity);
// sends the report out
void _report_state ();
SpreaderBookkeepingData& _bookkeeping_data()
{
SpreaderBookkeepingData& _bookkeeping_data(){
bts_assert (_bookkeeping_data_ptr != nullptr);
return *_bookkeeping_data_ptr;
}
const SpreaderBookkeepingData& _bookkeeping_data() const
{
const SpreaderBookkeepingData& _bookkeeping_data() const {
bts_assert (_bookkeeping_data_ptr != nullptr);
return *_bookkeeping_data_ptr;
}
// just sets the price and qty in the bookkeeping data
void _set_price_quantity (Price price, Quantity quantity) {_bookkeeping_data().update_order (price, quantity);}
void _set_price_quantity (Price price, Quantity quantity) {
_bookkeeping_data().update_order (price, quantity);
}
// returns the equivelant order status of the spreader order
bts::api::orderman::OrderStatus _get_order_status () const;
static ChildOrderError _get_order_error (const bts::orderman::OrderStateUpdateEvent& ord_ev, bool expecting_cancel)
{
static ChildOrderError _get_order_error (const OrderStateUpdateEvent& ord_ev,
bool expecting_cancel){
bts::api::orderman::OrderStatus order_status = ord_ev.state.status();
if (SpreaderUtils::order_in_error_state(order_status))
return ChildOrderError::ORDER_STATE_ERROR;
if (SpreaderUtils::SpreaderUtils::order_in_cancelled_state(order_status) && !expecting_cancel)
return ChildOrderError::UNEXPECTED_CANCEL_ERROR;
// this is an inelegant way to detect modify errors
if (!SpreaderUtils::order_in_terminal_state(order_status) && ord_ev.state.last_msg().length() > 0)
return ChildOrderError::MODIFY_ERROR;
return ChildOrderError::NONE;
}
/**************************Testing only***********************************/
// testing only //////////////////
// this provides direct way to inject
// data into the spreader, as well as
// access to the internal state
//////////////////////////////////
public:
SpreaderState test_only_func_spreader_state () {return _state_worker->state();}
SpreaderLegData& test_only_func_spreader_leg_data (size_t index) {return _bookkeeping_data().get_leg_data (index);}
@@ -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();
@@ -142,8 +144,12 @@ private:
bool _target_reached(){
return _price_quantity_state.target_reached();
}
void _place_order (Price price, Quantity quantity) {_child_order_state.place_order(price, quantity);}
void _modify_order (Price price, Quantity quantity) {_child_order_state.modify_order(price, quantity);}
void _place_order (Price price, Quantity quantity, TickToTradeCorID cid = 0) {
_child_order_state.place_order(price, quantity, cid);
}
void _modify_order (Price price, Quantity quantity, TickToTradeCorID cid = 0) {
_child_order_state.modify_order(price, quantity, cid);
}
void _cancel_order () {_child_order_state.cancel_order();}
// this updates the hedge reduction in _price_quantity_state
void _incorporate_other_leg_fill (size_t leg_index, Quantity filled_quantity);
@@ -114,7 +114,8 @@ public:
}
// order actions ////////////////////
/////////////////////////////////////
void place_order(Price price, Quantity quantity){
void place_order(Price price, Quantity quantity,
TickToTradeCorID cid = 0){
_out_price = price;
_out_quantity = quantity;
bts_assert(_n_place_orders == 0);
@@ -122,16 +123,17 @@ public:
LOG(OrderMan, Info, "SpreaderChildOrderState::place_order order_key: {} is_quoting: {} sec_key: {} live: {} price: {} quantity: {}",
order_key().value(), _is_quoting, _sec_key, _is_live, price.value(), quantity.value());
if (_is_live)
_child_order.activate(price, quantity);
_child_order.activate(price, quantity, cid);
}
void modify_order(Price price, Quantity quantity){
void modify_order(Price price, Quantity quantity,
TickToTradeCorID cid = 0){
_out_price = price;
_out_quantity = quantity;
_n_modify_orders++;
LOG(OrderMan, Info, "SpreaderChildOrderState::modify_order order_key: {} is_quoting: {} sec_key: {} live: {} price: {} quantity: {} mod_no: {}",
order_key().value(), _is_quoting, _sec_key, _is_live, price.value(), quantity.value(), _n_modify_orders);
if (_is_live)
_child_order.update(price, quantity);
_child_order.update(price, quantity, cid);
}
void cancel_order(){
bts_assert(_n_cancel_orders == 0);
@@ -175,7 +175,7 @@ public:
if (price_change_ticks != 0){
bool price_increasing = price_change_ticks > 0;
uint32_t abs_price_change_ticks = static_cast<uint32_t>(std::abs(price_change_ticks));
LOG(OrderMan, Info, "SpreaderUtils::reprice_quote_mute_zone current_price: {} new_price: {} price_change_ticks: {} abs_price_change_ticks: {} inside_limit_ticks: {} outside_limit_ticks: {}",
LOG(OrderMan, Diag, "SpreaderUtils::reprice_quote_mute_zone current_price: {} new_price: {} price_change_ticks: {} abs_price_change_ticks: {} inside_limit_ticks: {} outside_limit_ticks: {}",
current_price, new_price, price_change_ticks, abs_price_change_ticks, quote_mute_zone.inside_ticks(), quote_mute_zone.outside_ticks());
if (quote_side == api::SIDE_BUY){
if (price_increasing) // moving toward inside market
@@ -7,8 +7,10 @@
namespace bts{
namespace orderman{
QuoterState QuoterEndStateWorker::state () const {return QuoterState::END;}
void QuoterEndStateWorker::on_transition (){
QuoterState QuoterEndStateWorker::state () const {
return QuoterState::END;
}
void QuoterEndStateWorker::on_transition (){
bool is_final = _quoter->_is_permenantly_cancelled || _quoter->_price_quantity_state.target_reached ();
if (!is_final){
// getting new order
@@ -23,7 +23,8 @@ void QuoterWaitingForFirstAckWorker::on_order_update (const SpreaderChildOrderSt
}
void QuoterWaitingForFirstAckWorker::on_book (bts::orderman::TickToTradeCorID,
size_t,
const bts::orderman::Book&){
const bts::orderman::Book&){
_analyze_and_act();
}
void QuoterWaitingForFirstAckWorker::cancel(){
_quoter->_transform_to_WAITING_FOR_FIRST_ACK_CANCELLED ();
@@ -46,5 +47,11 @@ void QuoterWaitingForFirstAckWorker::update_spreader_order (Price price,
void QuoterWaitingForFirstAckWorker::_analyze_and_act (){
if (_quoter->_price_quantity_state.target_reached())
_quoter->_transform_to_WAITING_FOR_FIRST_ACK_CANCELLED();
else{
Price computed_price; Quantity computed_quantity;
_quoter->_compute_price_quantity (computed_price, computed_quantity);
if (computed_quantity.value() == 0)
_quoter->_transform_to_WAITING_FOR_FIRST_ACK_CANCELLED();
}
}
}}
@@ -42,7 +42,7 @@ void QuoterWaitingForSizeStateWorker::update_spreader_order (Price price, Quanti
_quoter->_price_quantity_state.update_target_price_quantity (price, quantity);
_longpath_analyze_and_act (orderman::NullTickToTradeCorID);
}
void QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act (orderman::TickToTradeCorID){
void QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act (orderman::TickToTradeCorID cid){
if (_quoter->_price_quantity_state.target_reached()){
LOG(OrderMan, Info, "QuoterWaitingForSizeStateWorker key: {}, target reached, transforming to end", _quoter->_key().value());
_quoter->_transform_to_END ();
@@ -54,7 +54,7 @@ void QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act (orderman::TickT
_quoter->_commit_drivers ();
LOG(OrderMan, Info, "QuoterWaitingForSizeStateWorker sending order key: {} price: {} quantity: {}", _quoter->_key(), out_price, out_quantity);
// this is the only place where a quoter order is entered
_quoter->_place_order(out_price, out_quantity);
_quoter->_place_order(out_price, out_quantity, cid);
_quoter->_transform_to_WAITING_FOR_FIRST_ACK ();
}
}
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff. Show more