Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
806ed427ab | ||
|
|
fe6dabb5fe | ||
|
|
0546f0d9fb | ||
|
|
f70e8de497 | ||
|
|
ed06924df1 | ||
|
|
b78179ccee | ||
|
|
5a00c5eb2a | ||
|
|
74fa41f151 | ||
|
|
32d5fc2c37 | ||
|
|
1049046d0b | ||
|
|
1f8e0a1e2b | ||
|
|
5d3d5251c7 | ||
|
|
f7cee30511 | ||
|
|
98690ee747 | ||
|
|
3e14d83dde | ||
|
|
746622161c | ||
|
|
c064cdd215 | ||
|
|
acce1e3f7d | ||
|
|
9bdf084838 | ||
|
|
5132f81bc1 | ||
|
|
ab797a311e | ||
|
|
d032b23841 | ||
|
|
8c68a66ef1 | ||
|
|
d617d1597c | ||
|
|
625f76e1f4 | ||
|
|
ccff3ee160 | ||
|
|
1b1c8a7c1d | ||
|
|
5206a378ff | ||
|
|
7d2047d7ce | ||
|
|
8a43a4d157 | ||
|
|
230e9e525f | ||
|
|
034318db5e | ||
|
|
0758dd6f46 | ||
|
|
83f9f3375c | ||
|
|
98a6616804 | ||
|
|
b46f811cb5 | ||
|
|
7cf37bb080 | ||
|
|
6d918c0962 | ||
|
|
7cd052979e | ||
|
|
3b3d44d6f6 | ||
|
|
2b2eaac979 | ||
|
|
5b5274ab5f | ||
|
|
484ad656f9 | ||
|
|
0e92ee15f4 | ||
|
|
2d5211ac9b | ||
|
|
e62d116a44 | ||
|
|
0deb4fbcf4 | ||
|
|
b8b375eca5 | ||
|
|
4a42238cfb | ||
|
|
d4081420ae | ||
|
|
27d38cf443 | ||
|
|
4dad98a8b2 | ||
|
|
a2b8d53ebb |
No files matched your search
@@ -47,7 +47,7 @@ namespace mbts.ui
|
||||
//public static ImageSource Ticket { get { return load_image("Ticket.png"); } } // replaced with AddOrders
|
||||
public static ImageSource Date { get { return load_image("Date.png"); } }
|
||||
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; /*return load_image("Cube-Molecule.png");*/ } }
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; } }
|
||||
public static ImageSource Algo { get { return Cube3d; } }
|
||||
public static ImageSource Quote { get { return Math; } }
|
||||
public static ImageSource Math { get { return (DrawingImage)_resources["Surface"]; } }
|
||||
@@ -62,9 +62,7 @@ namespace mbts.ui
|
||||
#pragma warning restore CS0618
|
||||
|
||||
// Vector icons
|
||||
public static ImageSource Favorites { get { return Favorites_Gold; } }
|
||||
public static ImageSource Favorites_Gold { get { return (DrawingImage)_resources["FavoritesGold"]; } }
|
||||
public static ImageSource Favorites_White { get { return (DrawingImage)_resources["FavoritesWhite"]; } }
|
||||
public static ImageSource Bookmarks { get { return (DrawingImage)_resources["Bookmarks"]; } }
|
||||
public static ImageSource Window { get { return (DrawingImage)_resources["Window"]; } }
|
||||
public static ImageSource ActiveWindow { get { return (DrawingImage)_resources["ActiveWindow"]; } }
|
||||
public static ImageSource Trash { get { return (DrawingImage)_resources["Trash"]; } }
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -3,33 +3,17 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mbts="clr-namespace:mbts.ui">
|
||||
<DrawingImage x:Key="FavoritesGold">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#ffd932" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingImage x:Key="FavoritesWhite">
|
||||
<DrawingImage x:Key="Bookmarks">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
<Pen Thickness="3" Brush="Black" />
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#ffd932" />
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using bts.spark;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.ui {
|
||||
[Export(typeof(IShellModule))]
|
||||
public class BookmarkShellModule : ILazyMenuModule {
|
||||
readonly WindowManager _wm;
|
||||
readonly IBookmarks _bookmarks;
|
||||
readonly ISavedWindowFactory _window_factory;
|
||||
readonly WorkspacesModel _workspaces;
|
||||
|
||||
[ImportingConstructor]
|
||||
public BookmarkShellModule(
|
||||
WorkspacesModel workspaces,
|
||||
WindowManager wm,
|
||||
IBookmarks bm,
|
||||
ISavedWindowFactory swf) {
|
||||
_workspaces = workspaces;
|
||||
_wm = wm;
|
||||
_bookmarks = bm;
|
||||
_window_factory = swf;
|
||||
}
|
||||
|
||||
public ImageSource icon => Icons.Bookmarks;
|
||||
|
||||
public string name => "My Windows";
|
||||
|
||||
public int priority => 35;
|
||||
|
||||
public IEnumerable<SparkCommand> actions {
|
||||
get {
|
||||
// The windows that are open
|
||||
var open =
|
||||
_wm.list_documents()
|
||||
.Select(td => td.ViewModel)
|
||||
.Where(vm => vm.HasCustomTitle)
|
||||
.Select(vm =>
|
||||
SparkCommand.create(vm.Title, vm.Icon, SparkTags.ActivateWindow, () => vm.Model))
|
||||
.Cast<SparkCommand>();
|
||||
|
||||
var open_set = new HashSet<string>(open.Select(c => c.title));
|
||||
|
||||
// The windows that are not open
|
||||
var closed =
|
||||
_bookmarks.bookmarks
|
||||
.Where(state => !open_set.Contains(state.title))
|
||||
.Select(state =>
|
||||
SparkCommand.create(state.title, Icons.Bookmarks, SparkTags.ShowWindowAsync,
|
||||
() => {
|
||||
_bookmarks.remove(state);
|
||||
return _window_factory.restore(state);
|
||||
}))
|
||||
.Cast<SparkCommand>();
|
||||
|
||||
// todo: The windows that are closed
|
||||
foreach (var w in open.Concat(closed).OrderBy(w => w.title)) {
|
||||
yield return w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) {
|
||||
// This module shouldn't have any windows
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.ui.shell {
|
||||
|
||||
public interface IBookmarks {
|
||||
void save_bookmark(IShellWindow window);
|
||||
void save_bookmark(WindowState window);
|
||||
void remove(WindowState window);
|
||||
void clear();
|
||||
IEnumerable<WindowState> bookmarks { get; }
|
||||
}
|
||||
|
||||
[Export(typeof(IBookmarks))]
|
||||
public class BookmarksModel : IBookmarks {
|
||||
readonly ISettingsCollector _settings_collector;
|
||||
readonly Dictionary<string, WindowState> _bookmarks = new Dictionary<string, WindowState>();
|
||||
|
||||
[ImportingConstructor]
|
||||
public BookmarksModel(ISettingsCollector settings) {
|
||||
_settings_collector = settings;
|
||||
}
|
||||
public void save_bookmark(IShellWindow window) {
|
||||
var state = _settings_collector.get_window_state(window);
|
||||
if (state != null) {
|
||||
_bookmarks[state.title] = state;
|
||||
}
|
||||
}
|
||||
public void save_bookmark(WindowState window) {
|
||||
_bookmarks[window.title] = window;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
_bookmarks.Clear();
|
||||
}
|
||||
|
||||
public void remove(WindowState window) {
|
||||
_bookmarks.Remove(window.title);
|
||||
}
|
||||
|
||||
public IEnumerable<WindowState> bookmarks => _bookmarks.Values;
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace bts.ui {
|
||||
|
||||
public int priority => 40;
|
||||
|
||||
public IEnumerable<SparkCommand> actions => new []{
|
||||
public IEnumerable<SparkCommand> actions => new SparkCommand[]{
|
||||
SparkCommands.ShowWorkspaces
|
||||
//SparkCommands.ShowSettings
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<ResourceDictionary Source="/view/Resources/DataTemplates.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Spreader/ParameterSectionView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/TickFactorsPicker/TickFactorsPicker.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="RowHeaderWidth">285</sys:Double>
|
||||
<sys:Double x:Key="RowHeight">16</sys:Double>
|
||||
@@ -365,20 +366,11 @@
|
||||
Grid.Column="2" VerticalAlignment="Center" Margin="6,0,0,0" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<ComboBox
|
||||
x:Name="PART_Tick_ComboBox" Grid.Column="3" Margin="6,0,0,0" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
ItemsSource="{TemplateBinding AvailableTickFactors}" SelectedItem="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
<c:TickFactorsPicker
|
||||
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="6,0,0,0" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource TickFactorsTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
/>
|
||||
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="6,0,0,0">Pricing</TextBlock>
|
||||
<ComboBox
|
||||
Grid.Column="5" Margin="6,0,0,0" IsEnabled="False"
|
||||
|
||||
@@ -50,13 +50,6 @@ namespace mbts.ui.spark {
|
||||
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region AvailableTickFactors
|
||||
public static readonly DependencyProperty AvailableTickFactorsProperty = DependencyProperty.Register(
|
||||
nameof(AvailableTickFactors), typeof(IEnumerable), typeof(ExecutionStrategyEditor), new PropertyMetadata(new TickFactors[0]));
|
||||
|
||||
public IEnumerable AvailableTickFactors { get => GetValue(AvailableTickFactorsProperty) as IEnumerable; set => SetValue(AvailableTickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region IsNew
|
||||
public static readonly DependencyProperty IsNewProperty =
|
||||
DependencyProperty.Register(
|
||||
@@ -253,11 +246,11 @@ namespace mbts.ui.spark {
|
||||
|
||||
class ExecutionStrategyErrorAdorner : NotifyDataErrorAdorner<ExecutionStrategyEditor, SyntheticEditorVM>, IDisposable {
|
||||
readonly ChildProperty<TextBox> _name_text_box;
|
||||
readonly ChildProperty<ComboBox> _tick_combo_box;
|
||||
readonly ChildProperty<TickFactorsPicker> _tick_factors_picker;
|
||||
|
||||
public ExecutionStrategyErrorAdorner(ExecutionStrategyEditor editor) : base(editor) {
|
||||
_name_text_box = child_property<TextBox>("PART_Name_TextBox");
|
||||
_tick_combo_box = child_property<ComboBox>("PART_Tick_ComboBox");
|
||||
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactorsPicker");
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawing_context) {
|
||||
@@ -269,7 +262,7 @@ namespace mbts.ui.spark {
|
||||
if (_vm != null && _vm.GetErrors(nameof(SyntheticEditorVM.name)).Cast<string>().Any())
|
||||
this.draw_border(drawing_context, _name_text_box.value, null, new Pen(Brushes.Red, 1));
|
||||
if (_vm != null && _vm.GetErrors(nameof(SyntheticEditorVM.tick_factors)).Cast<string>().Any())
|
||||
this.draw_border(drawing_context, _tick_combo_box.value, null, new Pen(Brushes.Red, 1));
|
||||
this.draw_border(drawing_context, _tick_factors_picker.value, null, new Pen(Brushes.Red, 1));
|
||||
string error_text = string.Join(", ", _vm.AllErrors);
|
||||
this.draw_relative_text(RelativePosition.Below, error_text, drawing_context, _name_text_box.value, BTSColors.header_error_text_brush, 0, y_offset);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
HorizontalAlignment="Stretch" Margin="6"
|
||||
SyntheticName="{Binding name}"
|
||||
TickFactors="{Binding tick_factors}"
|
||||
AvailableTickFactors="{Binding available_tick_factors_view}"
|
||||
IsNew="{Binding is_new}"
|
||||
CanUserAddNewLeg="{Binding can_add_leg}"
|
||||
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -10,11 +11,9 @@ using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
@@ -23,7 +22,6 @@ namespace mbts.spark {
|
||||
/// The view model designed to serve as the DataContext for the SyntheticEditorView
|
||||
/// </summary>
|
||||
partial class SyntheticEditorVM : DisposableViewModelBase, IRequestViewClose, IRequestBindingSourceUpdate, IDisposable {
|
||||
readonly ObservableCollection<TickFactors> _available_tick_factors = new ObservableCollection<TickFactors>(TickUtils.all_present_ticks());
|
||||
readonly QuantityModel _quantity_model;
|
||||
readonly PriceMultiplierModel _price_mult_model;
|
||||
readonly List<IDisposable> _resources = new List<IDisposable>();
|
||||
@@ -121,7 +119,7 @@ namespace mbts.spark {
|
||||
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
if (_tick_factors == null || !_tick_factors.is_valid() || !_available_tick_factors.Contains(_tick_factors)) {
|
||||
if (_tick_factors == null || !_tick_factors.is_valid()) {
|
||||
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
@@ -133,6 +131,8 @@ namespace mbts.spark {
|
||||
_synthetic.ContractSpec.LowTick = tick_frac;
|
||||
_synthetic.ContractSpec.HighTick = tick_frac;
|
||||
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
|
||||
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
|
||||
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
|
||||
uint? id = await model.create_synthetic(_synthetic);
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
@@ -152,8 +152,6 @@ namespace mbts.spark {
|
||||
_set_ticks_automatically = is_new;
|
||||
_set_quantities_automatically = is_new;
|
||||
_set_price_mults_automatically = is_new;
|
||||
available_tick_factors_view = CollectionViewSource.GetDefaultView(_available_tick_factors);
|
||||
available_tick_factors_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TickFactors.tick_base)));
|
||||
|
||||
_quantity_model = new QuantityModel(!is_new);
|
||||
_price_mult_model = new PriceMultiplierModel(!is_new);
|
||||
@@ -173,7 +171,8 @@ namespace mbts.spark {
|
||||
}
|
||||
_is_setting_price_mults = false;
|
||||
};
|
||||
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
|
||||
//_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
|
||||
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => true), true);
|
||||
} else {
|
||||
_can_add_leg = backing(nameof(can_add_leg), false);
|
||||
}
|
||||
@@ -244,9 +243,11 @@ namespace mbts.spark {
|
||||
public TickFactors tick_factors {
|
||||
get => _tick_factors;
|
||||
set {
|
||||
_tick_factors = value;
|
||||
_set_ticks_automatically = false;
|
||||
RaisePropertyChanged();
|
||||
if (_tick_factors != value) {
|
||||
_tick_factors = value;
|
||||
_set_ticks_automatically = false;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool has_working_orders => _has_working_orders.Value;
|
||||
@@ -260,7 +261,6 @@ namespace mbts.spark {
|
||||
public IRaiseCanExecuteChangedCommand commit { get; }
|
||||
public ICommand remove_leg { get; }
|
||||
public ContractPickerModel contract_picker_model { get; }
|
||||
public ICollectionView available_tick_factors_view { get; }
|
||||
|
||||
public ExecutionStrategyImmutable get_strategy() {
|
||||
var mutable_strategy = _strategy.CloneToMutable();
|
||||
@@ -327,8 +327,6 @@ namespace mbts.spark {
|
||||
|
||||
void _deduce_ticks() {
|
||||
_tick_factors = TickUtils.deduce_ticks(leg_tuples.Select(l => l.leg.Security.ContractSpec));
|
||||
if (_tick_factors != null && !_available_tick_factors.Contains(tick_factors))
|
||||
_available_tick_factors.Add(_tick_factors);
|
||||
RaisePropertyChanged(nameof(tick_factors));
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace bts.spark {
|
||||
(var security, var spec) = get_security(neworder.Def.SecurityKey);
|
||||
//
|
||||
// description
|
||||
var mod_desc = modified_description(oldorder, neworder, spec.Value);
|
||||
var mod_desc = modified_description(oldorder, neworder, spec);
|
||||
if (!string.IsNullOrEmpty(mod_desc)) {
|
||||
log_status(ActivityLogCategory.modify, security, mod_desc);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace mbts.ui.tests
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
Assert.IsNotNull(Application.Current);
|
||||
Assert.IsNotNull(Application.Current.Windows);
|
||||
Assert.AreEqual(Application.Current.Windows.Count, 0);
|
||||
@@ -100,7 +100,7 @@ namespace mbts.ui.tests
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_slow_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
Assert.AreEqual(Application.Current.Windows.Count, 0);
|
||||
|
||||
// an ISlowWindow gets its own thread
|
||||
|
||||
@@ -5,6 +5,7 @@ using bts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.spark.SparkBusData;
|
||||
|
||||
namespace mbts.spark
|
||||
@@ -34,6 +35,8 @@ namespace mbts.spark
|
||||
public static ITag<(PartialCommand<SecurityMD>, SecurityCriteria)> FilterPickOne { get; } = new SparkTag<(PartialCommand<SecurityMD>, SecurityCriteria)>("FilterPick1");
|
||||
|
||||
public static ITag<IShellWindow> ShowWindow { get; } = new SparkTag<IShellWindow>("Show Window");
|
||||
public static ITag<Task<IShellWindow>> ShowWindowAsync { get; } = new SparkTag<Task<IShellWindow>>("ShowWindowAsync");
|
||||
public static ITag<IShellWindow> ActivateWindow { get; } = new SparkTag<IShellWindow>("Activate Window");
|
||||
public static ITag<Nothing> ShowWorkspaces { get; } = new SparkTag<Nothing>(nameof(ShowWorkspaces));
|
||||
public static ITag<Nothing> ShowSettings { get; } = new SparkTag<Nothing>(nameof(ShowSettings));
|
||||
public static ITag<Uri> ShowUri { get; } = new SparkTag<Uri>("ShowUri");
|
||||
|
||||
@@ -48,10 +48,10 @@ namespace System.Collections.Generic {
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<T> ToIEnumerable<T>(this T item) {
|
||||
yield return item;
|
||||
}
|
||||
// use "new [] { item }" - avoid putting this on the intellisense for every item everywhere
|
||||
//public static IEnumerable<T> ToIEnumerable<T>(this T item) {
|
||||
// yield return item;
|
||||
//}
|
||||
|
||||
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
|
||||
TAccumulate a = seed;
|
||||
|
||||
@@ -17,8 +17,10 @@ namespace mbts {
|
||||
public static double tick_unit_frac(this TickFactors tick) =>
|
||||
tick.tick_unit / tick.tick_base;
|
||||
|
||||
public static double reduced_base(this TickFactors tick) => factor_out(tick.tick_base, 10);
|
||||
|
||||
public static string format(this TickFactors tick, double value) =>
|
||||
format(tick, value, double.NaN, true, _nan_text);
|
||||
format(tick, value, double.NaN, false, _nan_text);
|
||||
|
||||
public static string format(this TickFactors tick, double value, bool show_commas) =>
|
||||
format(tick, value, double.NaN, show_commas, _nan_text);
|
||||
@@ -176,9 +178,10 @@ namespace mbts {
|
||||
if (DoubleHelper.IsNaNOrInfinity(input)) return input;
|
||||
return input <= regional_tick.low_tick_inclusive_end ? regional_tick.low_tick.round(input) : regional_tick.high_tick.round(input);
|
||||
}
|
||||
public static int display_digits(this TickFactors tick) => display_digits(tick.tick_base);
|
||||
#endregion
|
||||
|
||||
public static int n_display_digits(double tick_base) {
|
||||
public static int display_digits(double tick_base) {
|
||||
double log_tick_base = Math.Log10(tick_base);
|
||||
int floor = (int)Math.Floor(log_tick_base);
|
||||
double dec = log_tick_base - floor;
|
||||
@@ -216,7 +219,7 @@ namespace mbts {
|
||||
if (numerator_part >= tick_base) numerator_part /= Math.Pow(10, Math.Ceiling(Math.Log10(numerator_part) - Math.Log10(tick_base)));
|
||||
else {
|
||||
//e.g [tick_base 640] 1989-8 == 1989-80, or 1989-32 == 1989-320
|
||||
int ndd_diff = n_display_digits(tick_base) - numerator_text.Length;
|
||||
int ndd_diff = display_digits(tick_base) - numerator_text.Length;
|
||||
if (ndd_diff > 0) numerator_part *= Math.Pow(10, ndd_diff);
|
||||
if (numerator_part >= tick_base) numerator_part /= 10;
|
||||
}
|
||||
@@ -255,16 +258,21 @@ namespace mbts {
|
||||
double numerator_dec = value - integer_part;
|
||||
double unit_frac = tick.tick_unit / tick.tick_base;
|
||||
int n_units = (int)Math.Round(numerator_dec / unit_frac);
|
||||
if (n_units == tick.tick_base) return (integer_part + 1).ToString() + tick.separator() + new string('0', n_display_digits(tick.tick_base));
|
||||
if (n_units == tick.tick_base) return (integer_part + 1).ToString() + tick.separator() + new string('0', display_digits(tick.tick_base));
|
||||
double decimal_part = n_units * tick.tick_unit;
|
||||
if (decimal_part == tick.tick_base) {
|
||||
++integer_part;
|
||||
decimal_part = 0;
|
||||
} else {
|
||||
// some ticks are not formatted with 100% precision, using less than the required display_digits
|
||||
// for such cases, we need to truncate the decimal part rather than round it.
|
||||
decimal_part = Math.Floor(decimal_part);
|
||||
}
|
||||
string integer_part_text = show_commas ? integer_part.ToString("#,0") : integer_part.ToString();
|
||||
return integer_part_text + tick.separator() + decimal_part.ToString(new string('0', n_display_digits(tick.tick_base)));
|
||||
return integer_part_text + tick.separator() + decimal_part.ToString(new string('0', display_digits(tick.tick_base)));
|
||||
}
|
||||
} catch (OverflowException ex) {
|
||||
}
|
||||
catch (OverflowException ex) {
|
||||
Logger.warn(bts.log.Subsystem.Ui, $"Arithmetic overflow exception in format_tick (tick:{tick}, value:{value}):{ex}");
|
||||
return "Overflow";
|
||||
}
|
||||
@@ -272,7 +280,7 @@ namespace mbts {
|
||||
|
||||
private static double _round_tick(double tick_unit_frac, double input) {
|
||||
return input - Math.IEEERemainder(input, tick_unit_frac);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -69,16 +69,18 @@ namespace mbts.ui.settings
|
||||
if (window == null || window.Owner == null) return null;
|
||||
MemoryStream out_stream = new MemoryStream();
|
||||
window.GetState(out_stream);
|
||||
|
||||
var pos = _interop.GetPosition(window);
|
||||
var state = new WindowState() {
|
||||
owner = window.Owner.key(),
|
||||
app_data = out_stream.ToArray(),
|
||||
position = _interop.GetPosition(window),
|
||||
title = window.Title
|
||||
position = pos,
|
||||
title = string.IsNullOrEmpty(pos.custom_title) ? window.Title : pos.custom_title
|
||||
};
|
||||
return state;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
Logger.log(Level.Error, Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ namespace mbts.ui.shell {
|
||||
[ProtoMember(102), DefaultValue(true)]
|
||||
public bool always_on_top = true;
|
||||
//[ProtoMember(103)] //used
|
||||
|
||||
[ProtoMember(104)]
|
||||
public string custom_title;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -58,7 +61,7 @@ namespace mbts.ui.shell {
|
||||
public WindowPosition position;
|
||||
|
||||
[ProtoMember(9)]
|
||||
public string title;
|
||||
public string title; // for debugging
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace mbts.ui
|
||||
Task<IShellWindow> create(Stream load_stream);
|
||||
}
|
||||
|
||||
// ILazyModule may also impement IShellMenu to get opening/closing notification
|
||||
public interface ILazyMenuModule : IShellModule { }
|
||||
|
||||
public interface IPersistShellModule : IShellModule {
|
||||
// Called at startup before the UI is shown
|
||||
// This is an opportunity for you to kick off background services
|
||||
|
||||
@@ -17,5 +17,5 @@ namespace mbts.ui
|
||||
WindowPosition GetPosition(IShellWindow window);
|
||||
bool IsVisible(IShellWindow window);
|
||||
void PinWindow(IShellWindow window);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.utils {
|
||||
@@ -25,6 +26,9 @@ namespace mbts.utils {
|
||||
public static IEqualityComparer<T?> CreateNullable<T>(Func<T, T, bool> equals) where T : struct =>
|
||||
new AnonymousEqualityComparer<T?>((a, b) => a.HasValue ? b.HasValue && equals(a.Value, b.Value) : !b.HasValue);
|
||||
|
||||
public static IEqualityComparer to_non_generic<T>(this IEqualityComparer<T> comparer) =>
|
||||
new AnonymousEqualityComparer<T>(comparer.Equals, comparer.GetHashCode);
|
||||
|
||||
public static bool NullCheckEquals<T>(T x, T y, Func<T, T, bool> non_null_equals) where T : class, IEquatable<T> =>
|
||||
x == null ? y == null :
|
||||
y != null &&
|
||||
@@ -55,7 +59,7 @@ namespace mbts.utils {
|
||||
static void _accumulate_hash(ref int aggregate_hash, ref int item_hash) =>
|
||||
aggregate_hash = aggregate_hash * _hash_multiplier + item_hash;
|
||||
|
||||
private class AnonymousEqualityComparer<T> : IEqualityComparer<T> {
|
||||
private class AnonymousEqualityComparer<T> : IEqualityComparer<T>, IEqualityComparer {
|
||||
readonly Func<T, T, bool> _equals;
|
||||
readonly Func<T, int> _get_hash_code;
|
||||
|
||||
@@ -67,6 +71,9 @@ namespace mbts.utils {
|
||||
|
||||
public bool Equals(T x, T y) => _equals(x, y);
|
||||
public int GetHashCode(T obj) => _get_hash_code(obj);
|
||||
|
||||
bool IEqualityComparer.Equals(object x, object y) => x is T xt && y is T yt && _equals(xt, yt);
|
||||
int IEqualityComparer.GetHashCode(object obj) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.utils {
|
||||
public static class CompareUtils {
|
||||
@@ -19,5 +21,22 @@ namespace mbts.utils {
|
||||
b.HasValue ? compare(a.Value, b.Value) : 1 :
|
||||
b.HasValue ? -1 : 0;
|
||||
#endregion
|
||||
|
||||
#region Comparer
|
||||
public static IComparer to_non_generic<T>(this IComparer<T> comparer) =>
|
||||
new NonGenericComparerWrapper<T>(comparer);
|
||||
|
||||
public static IComparer<TOutter> wrap<TInner, TOutter>(this IComparer<TInner> inner_comparer, Func<TOutter, TInner> selector) =>
|
||||
Comparer<TOutter>.Create((a, b) => inner_comparer.Compare(a == null ? default : selector(a), b == null ? default : selector(b)));
|
||||
|
||||
class NonGenericComparerWrapper<T> : IComparer {
|
||||
readonly IComparer<T> _comparer;
|
||||
|
||||
public NonGenericComparerWrapper(IComparer<T> comparer) => _comparer = comparer;
|
||||
|
||||
public int Compare(object x, object y) =>
|
||||
_comparer.Compare(ObjectHelper.try_convert<T>(x), ObjectHelper.try_convert<T>(y));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mbts.spark {
|
||||
interface IValueFactoryVM<T> {
|
||||
T create();
|
||||
void load_from(T value);
|
||||
}
|
||||
}
|
||||
@@ -375,7 +375,6 @@
|
||||
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}"
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace mbts.ui.spark {
|
||||
|
||||
#region ContractSpec
|
||||
public static readonly DependencyProperty ContractSpecProperty = DependencyProperty.Register("ContractSpec", typeof(ContractSpecificationImmutable), typeof(PriceEntryControl),
|
||||
new PropertyMetadata(SecurityDataHelper.Defaults.ContractSpecificationImmutable, OnContractSpecChanged));
|
||||
new PropertyMetadata(SecurityDataHelper.Defaults.ContractSpecificationImmutablePennies, OnContractSpecChanged));
|
||||
static void OnContractSpecChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((PriceEntryControl)d)._update_text_binding();
|
||||
public ContractSpecificationImmutable? ContractSpec { get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?; set => SetValue(ContractSpecProperty, value); }
|
||||
#endregion
|
||||
|
||||
@@ -10,9 +10,11 @@ namespace mbts.ui.controls {
|
||||
static SparkMenu() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(SparkMenu), new FrameworkPropertyMetadata(typeof(SparkMenu)));
|
||||
|
||||
public SparkMenu() =>
|
||||
public SparkMenu() {
|
||||
SetCurrentValue(FocusableProperty, false);
|
||||
}
|
||||
|
||||
|
||||
public SparkMenu(IEnumerable<SparkCommand> commands, IBus bus) : this() =>
|
||||
ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:txt="clr-namespace:mbts.ui.utility.text"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style TargetType="c:TickFactorsPicker">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="c:TickFactorsPicker">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="4"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ComboBox
|
||||
MinWidth="48"
|
||||
IsEditable="True"
|
||||
DisplayMemberPath="value"
|
||||
SelectedValuePath="value"
|
||||
ItemsSource="{Binding TickUnitPresetsView, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
SelectedValue="{Binding Controller.tick_unit, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Text="{Binding Controller.tick_unit, Converter={StaticResource DoubleTextConverter}, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{x:Static c:TickFactorsPicker.TickUnitVisibleTextConverter}">
|
||||
<Binding Path="Controller.tick_base" RelativeSource="{RelativeSource AncestorType=c:TickFactorsPicker}"/>
|
||||
<Binding Path="value"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="1" Foreground="{u:BTSColor UI.input_disabled_text}" Text="{Binding value, Converter={x:Static c:TickFactorsPicker.TickUnitHiddenTextConverter}}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<ComboBox
|
||||
Grid.Column="2"
|
||||
MinWidth="60"
|
||||
IsEditable="True"
|
||||
DisplayMemberPath="value"
|
||||
SelectedValuePath="value"
|
||||
ItemsSource="{Binding TickBasePresetsView, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
SelectedValue="{Binding Controller.tick_base, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Text="{Binding Controller.tick_base, Converter={StaticResource DoubleTextConverter}, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,157 @@
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.user.settings;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for TickPicker.xaml
|
||||
/// </summary>
|
||||
class TickFactorsPicker : Control {
|
||||
readonly ObservableCollection<double> _tick_unit_presets = new ObservableCollection<double>();
|
||||
|
||||
static TickFactorsPicker() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TickFactorsPicker), new FrameworkPropertyMetadata(typeof(TickFactorsPicker)));
|
||||
|
||||
public TickFactorsPicker() {
|
||||
Controller = new TickFactorsController(this);
|
||||
TickBasePresetsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TickBaseVM.reduced_base_group)));
|
||||
((ListCollectionView)TickBasePresetsView).CustomSort = TickUtils.CompareUtils.base_comparer.wrap<double, TickBaseVM>(vm => vm.value).to_non_generic();
|
||||
TickUnitPresetsView = CollectionViewSource.GetDefaultView(new ObservableAutoWrapper<TickUnitVM, double>(_tick_unit_presets, v => new TickUnitVM(v)));
|
||||
TickUnitPresetsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TickUnitVM.reduced_unit_group)));
|
||||
((ListCollectionView)TickUnitPresetsView).CustomSort = Comparer<double>.Default.wrap<double, TickUnitVM>(vm => vm.value).to_non_generic();
|
||||
}
|
||||
|
||||
public static IEnumerable<double> TickBasePresets =>
|
||||
new [] {
|
||||
8, 16, 32, 64, 100
|
||||
}.SelectMany(i => Enumerable.Range(0, 4).Select(log10 => i * Math.Pow(10, log10)));
|
||||
|
||||
public static IMultiValueConverter TickUnitVisibleTextConverter =>
|
||||
ValueConverter.create((double tick_base, double tick_unit) => {
|
||||
int display_digits = TickExtensions.display_digits(tick_base);
|
||||
return ((int)(tick_unit)).ToString(new string('0', display_digits));
|
||||
});
|
||||
|
||||
public static IValueConverter TickUnitHiddenTextConverter =>
|
||||
ValueConverter.create((double tick_unit) => new string(tick_unit.ToString().SkipWhile(c => c != '.').ToArray()));
|
||||
|
||||
public static readonly DependencyProperty SelectedTickFactorsProperty =
|
||||
WPFHelper.create_dp<TickFactors, TickFactorsPicker>(
|
||||
nameof(SelectedTickFactors),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => o.Controller.load_from(v),
|
||||
new TickFactors());
|
||||
|
||||
public TickFactors SelectedTickFactors {
|
||||
get => GetValue(SelectedTickFactorsProperty) as TickFactors;
|
||||
set => SetValue(SelectedTickFactorsProperty, value);
|
||||
}
|
||||
|
||||
public TickFactorsController Controller { get; }
|
||||
|
||||
public ICollectionView TickBasePresetsView { get; } = CollectionViewSource.GetDefaultView(TickBasePresets.Select(v => new TickBaseVM(v)).ToList());
|
||||
public ICollectionView TickUnitPresetsView { get; }
|
||||
|
||||
static IEnumerable<double> _get_tick_unit_presets(double tick_base) {
|
||||
double reduced_base = MathUtils.factor_out(tick_base, 10, out int base_log10);
|
||||
return reduced_base == 1 ?
|
||||
_get_dbased_tick_unit_presets(base_log10) :
|
||||
_get_2based_tick_unit_presets((int)Math.Log(reduced_base, 2), base_log10);
|
||||
}
|
||||
|
||||
static IEnumerable<double> _get_2based_tick_unit_presets(int log2, int log10) =>
|
||||
(log10 == 0 ? new[] { 1.0 } : _get_dbased_tick_unit_presets(log10)).Concat(
|
||||
Enumerable.Range(1, log2 - 1).SelectMany(pow2 => {
|
||||
double root = Math.Pow(2, pow2);
|
||||
return Enumerable.Range(0, log10 + 1).Select(pow10 => root * Math.Pow(10, pow10));
|
||||
}));
|
||||
|
||||
static IEnumerable<double> _get_dbased_tick_unit_presets(int log10) =>
|
||||
Enumerable.Range(0, log10).SelectMany(_coins);
|
||||
|
||||
static IEnumerable<double> _coins(int pow10) {
|
||||
double d = Math.Pow(10, pow10);
|
||||
yield return d;
|
||||
yield return 1.25 * d;
|
||||
yield return 2.5 * d;
|
||||
yield return 5.0 * d;
|
||||
}
|
||||
|
||||
public class TickFactorsController : ViewModelBase, IValueFactoryVM<TickFactors> {
|
||||
readonly TickFactorsPicker _view;
|
||||
readonly Property<double> _tick_base, _tick_unit;
|
||||
|
||||
public TickFactorsController(TickFactorsPicker view) {
|
||||
var init = (_view = view).SelectedTickFactors;
|
||||
_tick_base = backing(nameof(tick_base), init.tick_base);
|
||||
_tick_unit = backing(nameof(tick_unit), init.tick_unit);
|
||||
}
|
||||
|
||||
public double tick_base {
|
||||
get => _tick_base.Value;
|
||||
set {
|
||||
_tick_base.set(value);
|
||||
WPFHelper.set_current_if_changed(_view, SelectedTickFactorsProperty, new TickFactors(value, tick_unit));
|
||||
_view._tick_unit_presets.sync_with(_get_tick_unit_presets(value));
|
||||
}
|
||||
}
|
||||
public double tick_unit {
|
||||
get => _tick_unit.Value;
|
||||
set {
|
||||
_tick_unit.set(value);
|
||||
WPFHelper.set_current_if_changed(_view, SelectedTickFactorsProperty, new TickFactors(tick_base, value));
|
||||
}
|
||||
}
|
||||
|
||||
public TickFactors create() => new TickFactors(tick_base, tick_unit);
|
||||
public void load_from(TickFactors value) {
|
||||
if (value != null) {
|
||||
_tick_base.set(value.tick_base);
|
||||
_tick_unit.set(value.tick_unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TickBaseVM : IEquatable<TickBaseVM> {
|
||||
public TickBaseVM(double value) =>
|
||||
this.value = value;
|
||||
|
||||
public double value { get; }
|
||||
public double reduced_base_group => MathUtils.factor_out(value, 10);
|
||||
|
||||
public bool Equals(TickBaseVM other) => other != null && value == other.value;
|
||||
public override bool Equals(object obj) => Equals(obj as TickBaseVM);
|
||||
public override int GetHashCode() => value.GetHashCode();
|
||||
}
|
||||
|
||||
public class TickUnitVM : IEquatable<TickUnitVM> {
|
||||
public TickUnitVM(double value) =>
|
||||
this.value = value;
|
||||
|
||||
public double value { get; }
|
||||
public double reduced_unit_group {
|
||||
get {
|
||||
if (!MathUtils.is_int(value))
|
||||
return 1;
|
||||
else return MathUtils.factor_out(value, 10, 5);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(TickUnitVM other) => other != null && value == other.value;
|
||||
public override bool Equals(object obj) => Equals(obj as TickUnitVM);
|
||||
public override int GetHashCode() => value.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,15 @@
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters
|
||||
{
|
||||
namespace mbts.ui.converters {
|
||||
static class ValueConverter {
|
||||
public static IValueConverter create<TIn, TOut>(Func<TIn, TOut> convert) =>
|
||||
new AnonymousValueConverter<TIn, TOut>(convert);
|
||||
|
||||
public static IMultiValueConverter create<TIn1, TIn2, TOut>(Func<TIn1, TIn2, TOut> convert) =>
|
||||
new AnonymousMultiValueConverter<TIn1, TIn2, TOut>(convert);
|
||||
}
|
||||
|
||||
public class AnonymousValueConverter : IValueConverter {
|
||||
public AnonymousValueConverter(Func<object, object> convert) : this(convert, null) { }
|
||||
public AnonymousValueConverter(Func<object, object> convert, Func<object, object> convert_back) {
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace mbts.ui.converters
|
||||
Minimum = Double.NaN;
|
||||
Maximum = Double.NaN;
|
||||
Multiplier = Double.NaN;
|
||||
Format = FORMAT_WITH_COMMAS;
|
||||
HideNaN = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace mbts.ui.converters {
|
||||
class TickFactorsTextConverter : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is TickFactors tf) {
|
||||
int n_digits = TickExtensions.n_display_digits(tf.tick_base);
|
||||
int n_digits = tf.display_digits();
|
||||
if (n_digits > 1) {
|
||||
string n_digits_format = new string('0', n_digits);
|
||||
string format = "{0:" + n_digits_format + "}/{1}";
|
||||
|
||||
@@ -61,16 +61,16 @@ namespace mbts.ui
|
||||
_source_window = window as FloatingWindow;
|
||||
if (_source != null) {
|
||||
_mouse_start = e.GetPosition(window);
|
||||
if (!is_button_descendant((DependencyObject)e.OriginalSource)) {
|
||||
if (!is_clickable_descendant((DependencyObject)e.OriginalSource)) {
|
||||
Mouse.Capture(_source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_button_descendant(DependencyObject target) {
|
||||
static bool is_clickable_descendant(DependencyObject target) {
|
||||
while (target != null && !(target is TabDocument)) {
|
||||
target = VisualTreeHelper.GetParent(target);
|
||||
if (target is Button) return true;
|
||||
if (target is Button || target is TextBox) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -24,38 +24,7 @@ namespace mbts.ui
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task FromEvent(this object target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
Action result = () => { tcs.TrySetResult(null); };
|
||||
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
|
||||
try {
|
||||
evt.AddEventHandler(target, d);
|
||||
await tcs.Task;
|
||||
}
|
||||
finally {
|
||||
evt.RemoveEventHandler(target, d);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<T> FromEvent<T>(this object target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
Action<T> result = (arg) => { tcs.TrySetResult(arg); };
|
||||
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
|
||||
try {
|
||||
evt.AddEventHandler(target, d);
|
||||
await tcs.Task;
|
||||
return tcs.Task.Result;
|
||||
}
|
||||
finally {
|
||||
evt.RemoveEventHandler(target, d);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this object target, string event_name) {
|
||||
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this DependencyObject target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<Tuple<T1,T2>>();
|
||||
|
||||
@@ -14,18 +14,19 @@ namespace mbts.ui.shell
|
||||
public partial class FloatingWindow : Window
|
||||
{
|
||||
bool _closed = false;
|
||||
private IUndoActions _undo;
|
||||
readonly IBookmarks _bookmark;
|
||||
private IWindowManager _wm;
|
||||
private ISavedWindowFactory _window_factory;
|
||||
private ISettingsCollector _window_settings;
|
||||
private Stack<Action> _recently_closed_windows = new Stack<Action>();
|
||||
|
||||
// if is_loaded is true, will NOT set width/height
|
||||
internal FloatingWindow(TabDocument document, IWindowManager wm, IUndoActions undo, ISettingsCollector settings, ISavedWindowFactory factory, bool is_loaded=false) {
|
||||
internal FloatingWindow(TabDocument document,
|
||||
IWindowManager wm, IBookmarks bookmarks, ISettingsCollector settings, ISavedWindowFactory factory, bool is_loaded=false) {
|
||||
_wm = wm;
|
||||
_undo = undo;
|
||||
_window_settings = settings;
|
||||
_window_factory = factory;
|
||||
_bookmark = bookmarks;
|
||||
|
||||
InitializeComponent();
|
||||
if (string.IsNullOrEmpty(document.ViewModel.host_id)) {
|
||||
@@ -67,6 +68,7 @@ namespace mbts.ui.shell
|
||||
|
||||
|
||||
private ItemCollection Items { get { return _tabs.Items; } }
|
||||
public IEnumerable<TabDocument> Documents => _tabs.Items.OfType<TabDocument>();
|
||||
public bool IsEmpty { get { return _tabs.Items.Count == 0; } }
|
||||
public bool SingleChild { get { return _tabs.Items.Count == 1; } }
|
||||
public string host_id { get; private set; }
|
||||
@@ -88,10 +90,10 @@ namespace mbts.ui.shell
|
||||
if (sender != this) {
|
||||
var undoaction = create_undo_action(document);
|
||||
if (undoaction != null) {
|
||||
IDisposable undoitem = _undo.push("Restore Tab '{0}'".fmt(document.Title), undoaction);
|
||||
_recently_closed_windows.Push(() => { undoaction(); undoitem.Dispose(); });
|
||||
_recently_closed_windows.Push(() => { undoaction(); });
|
||||
}
|
||||
}
|
||||
_bookmark.save_bookmark(document.Model);
|
||||
}
|
||||
|
||||
public void remove_document(TabDocument document) {
|
||||
@@ -144,22 +146,9 @@ namespace mbts.ui.shell
|
||||
|
||||
protected override void OnClosed(EventArgs e) {
|
||||
_closed = true;
|
||||
TabDocument[] items = _tabs.Items.OfType<TabDocument>().ToArray();
|
||||
if (items.Length > 0) {
|
||||
// If we're closing and still have Tabs open we save the whole "window" worth of state in the undo stack
|
||||
// If the application is shutting down this might be unnecessary work but it's not too costly
|
||||
var undo_actions = items.Select(t => create_undo_action(t.ViewModel)).Where(item => item != null).ToArray();
|
||||
if (undo_actions.Length > 0) {
|
||||
string name = "Restore Window: '{0}'".fmt(string.Join("', '", items.Select(t => t.ViewModel.Title)));
|
||||
_undo.push(name, () => {
|
||||
foreach (var u in undo_actions) { u(); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
base.OnClosed(e);
|
||||
|
||||
foreach (TabDocument document in items) {
|
||||
foreach (TabDocument document in _tabs.Items.OfType<TabDocument>().ToList()) {
|
||||
if (document != null && document.ViewModel.CloseCommand.CanExecute(this)) {
|
||||
document.ViewModel.CloseCommand.Execute(this);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d" Style="{StaticResource {x:Type TabItem}}" d:DesignWidth="125"
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
xmlns:convert="clr-namespace:mbts.ui.converters"
|
||||
mc:Ignorable="d" d:DesignWidth="125"
|
||||
Header="{Binding}" Content="{Binding AppContent}"
|
||||
Background="#e6e6e6">
|
||||
|
||||
@@ -18,6 +20,18 @@
|
||||
<Storyboard x:Key="_slide_back">
|
||||
<DoubleAnimation Storyboard.TargetName="translate_transform" Storyboard.TargetProperty="X" To="100" Duration="0:0:0.25" FillBehavior="Stop" />
|
||||
</Storyboard>
|
||||
<DataTemplate x:Key="_edit_template">
|
||||
<TextBox Text="{Binding TitleOverride}"
|
||||
ui:TextBoxBehavior.CommitOnEnter="True"
|
||||
ui:TextBoxBehavior.FocusOnLoaded="True"
|
||||
ui:TextBoxBehavior.SelectAllOnFocus="True"
|
||||
ui:TextBoxBehavior.ReleaseOnEscape="True"
|
||||
Width="150" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="_template">
|
||||
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0"></TextBlock>
|
||||
</DataTemplate>
|
||||
<convert:BooleanToObjectConverter x:Key="MenuHeaderConverter" ValueForTrue="Rename Window" ValueForFalse="Add to My Windows" />
|
||||
</ResourceDictionary>
|
||||
</TabItem.Resources>
|
||||
<TabItem.LayoutTransform>
|
||||
@@ -28,25 +42,32 @@
|
||||
<TabItem.RenderTransform>
|
||||
<TranslateTransform x:Name="translate_transform" X="0" />
|
||||
</TabItem.RenderTransform>
|
||||
<TabItem.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0"> </TextBlock>
|
||||
</DataTemplate>
|
||||
</TabItem.HeaderTemplate>
|
||||
|
||||
<TabItem.Template>
|
||||
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||
<Border x:Name="bd" BorderBrush="Black" BorderThickness="0,0,0.5,0" Background="#d6d6d6" Width="{DynamicResource TabWidth}" Height="{DynamicResource TabHeight}" MinWidth="40" Margin="0" SnapsToDevicePixels="True" DockPanel.Dock="Left">
|
||||
<Border.ToolTip>
|
||||
<TextBlock Text="{Binding Title}" />
|
||||
</Border.ToolTip>
|
||||
<Border.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{Binding HasCustomTitle, Converter={StaticResource MenuHeaderConverter}}" Command="{Binding rename_command}">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="{StaticResource Bookmarks}" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Remove from My Windows" Command="{Binding unrename_command}" IsEnabled="{Binding HasCustomTitle}" />
|
||||
</ContextMenu>
|
||||
</Border.ContextMenu>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image SnapsToDevicePixels="True" MaxHeight="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Margin="5,0,5,0" Source="{Binding Icon, Mode=OneTime}" RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
<Image SnapsToDevicePixels="True" MaxHeight="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Margin="5,0,5,0"
|
||||
Source="{Binding Icon, Mode=OneTime}" RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
<!--<Image MaxHeight="10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Stretch="Uniform" Margin="5"
|
||||
Source="{StaticResource Bookmarks}" Visibility="{Binding HasCustomTitle, Converter={StaticResource BoolToVisibility}}" />-->
|
||||
<ContentPresenter Grid.Column="1" x:Name="content" ContentSource="Header" TextBlock.Foreground="#666"
|
||||
RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -62,12 +83,18 @@
|
||||
<Setter Property="Panel.ZIndex" Value="1" />
|
||||
<Setter Property="Background" TargetName="bd" Value="#F9F9F9" />
|
||||
<Setter Property="TextBlock.Foreground" TargetName="content" Value="black" />
|
||||
<!--<Setter Property="BorderThickness" TargetName="bd" Value="0,4,0.5,0" />-->
|
||||
<!--<Setter Property="Margin" Value="-2,-2,-2,-1" />
|
||||
<Setter Property="Margin" TargetName="content" Value="0,0,0,1" />
|
||||
<Setter Property="Margin" TargetName="bd" Value="0,0,0,0" />-->
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</TabItem.Template>
|
||||
<TabItem.Style>
|
||||
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource _template}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding EditMode}" Value="True">
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource _edit_template}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TabItem.Style>
|
||||
</TabItem>
|
||||
@@ -18,8 +18,15 @@ namespace mbts.ui.shell {
|
||||
public ImageSource IconSource { get { return _module.icon; } }
|
||||
public UIElement MenuContent => new SparkMenu(_module.actions, _bus);
|
||||
|
||||
public void opening() { }
|
||||
public void opening() {
|
||||
(_module as IShellMenu)?.opening();
|
||||
if (_module is ILazyMenuModule) {
|
||||
RaisePropertyChanged(nameof(MenuContent));
|
||||
}
|
||||
}
|
||||
|
||||
public void closing() { }
|
||||
public void closing() {
|
||||
(_module as IShellMenu)?.closing();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,11 @@ namespace mbts.ui.shell
|
||||
Dispatcher _dispatcher;
|
||||
HwndContentHost _hwnd_host;
|
||||
|
||||
|
||||
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = "") {
|
||||
Logger.assert(model != null);
|
||||
_model = model;
|
||||
_model.TitleChanged += () => RaisePropertyChanged("Title");
|
||||
_model.TitleChanged += () => { if (!HasCustomTitle) RaisePropertyChanged("Title"); };
|
||||
_model.IconChanged += () => { _icon = null; RaisePropertyChanged("Icon"); };
|
||||
_wm = wm;
|
||||
host_id = host;
|
||||
@@ -40,13 +41,55 @@ namespace mbts.ui.shell
|
||||
if (HasTabAffinity) {
|
||||
this.host_id = ((ITabAffineWindow)model).tab_group;
|
||||
}
|
||||
|
||||
_edit_mode = backing(nameof(EditMode), false);
|
||||
}
|
||||
|
||||
public string host_id { get; set; }
|
||||
public Dispatcher Dispatcher { get { return _dispatcher; } }
|
||||
public string Title { get { return _model.Title; } }
|
||||
|
||||
public string Title { get { return HasCustomTitle? TitleOverride : _model.Title; } }
|
||||
|
||||
string _title_override;
|
||||
public string TitleOverride {
|
||||
get => _title_override;
|
||||
set {
|
||||
if (_title_override != value) {
|
||||
if (value == _model.Title) {
|
||||
_title_override = null;
|
||||
}
|
||||
else {
|
||||
_title_override = value;
|
||||
}
|
||||
RaisePropertyChanged(nameof(TitleOverride), nameof(Title), nameof(HasCustomTitle));
|
||||
}
|
||||
_edit_mode.set(false);
|
||||
}
|
||||
}
|
||||
public bool HasCustomTitle => !string.IsNullOrWhiteSpace(TitleOverride);
|
||||
|
||||
Property<bool> _edit_mode;
|
||||
public bool EditMode => _edit_mode.Value;
|
||||
|
||||
|
||||
public ICommand rename_command => new DelegateCommand(begin_rename);
|
||||
public ICommand unrename_command => new DelegateCommand(remove_custom);
|
||||
|
||||
private void begin_rename() {
|
||||
if (string.IsNullOrEmpty(TitleOverride)) {
|
||||
_title_override = Title;
|
||||
}
|
||||
_edit_mode.set(true);
|
||||
}
|
||||
|
||||
private void remove_custom() {
|
||||
TitleOverride = string.Empty;
|
||||
//begin_close(this);
|
||||
}
|
||||
|
||||
public string OwnerName { get { return _model.Owner == null ? "" : _model.Owner.name; } }
|
||||
public ICommand CloseCommand { get { return new DelegateCommand(begin_close); } }
|
||||
|
||||
public double InitialWidth { get; private set; }
|
||||
public double InitialHeight { get; private set; }
|
||||
|
||||
@@ -155,18 +198,14 @@ namespace mbts.ui.shell
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetSaveState() {
|
||||
MemoryStream stream = new MemoryStream();
|
||||
_model.GetState(stream);
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
public IShellWindow Model { get { return _model; } }
|
||||
|
||||
public bool IsSlow { get { return _model is ISlowWindow; } }
|
||||
public bool IsShared { get { return _model is ISharedWindow; } }
|
||||
public bool HasTabAffinity { get { return _model is ITabAffineWindow; } }
|
||||
|
||||
|
||||
|
||||
public void Dispose() {
|
||||
if (IsShared) {
|
||||
// Shared threads continue running
|
||||
|
||||
@@ -20,25 +20,31 @@ namespace mbts.ui.shell {
|
||||
|
||||
[Export(typeof(IWindowManager))]
|
||||
[Export(typeof(IWindowsInterop))]
|
||||
[Export]
|
||||
public class WindowManager : IWindowManager, IWindowsInterop, IDisposable {
|
||||
Dictionary<IShellWindow, TabDocument> _documents = new Dictionary<IShellWindow, TabDocument>();
|
||||
Dictionary<string, FloatingWindow> _windows = new Dictionary<string, FloatingWindow>();
|
||||
IShellThreadFactory _threads;
|
||||
readonly IShellThreadFactory _threads;
|
||||
Lazy<ISettingsCollector> _window_settings;
|
||||
ISavedWindowFactory _window_factory;
|
||||
IUndoActions _undo;
|
||||
readonly ISavedWindowFactory _window_factory;
|
||||
readonly Lazy<IBookmarks> _bookmarks;
|
||||
IDisposable _dispose;
|
||||
object _lock = new object();
|
||||
|
||||
[ImportingConstructor]
|
||||
public WindowManager(
|
||||
IShellThreadFactory threads, Lazy<ISettingsCollector> settings,
|
||||
ISavedWindowFactory factory, IUndoActions undo, IBus bus) {
|
||||
ISavedWindowFactory factory, Lazy<IBookmarks> bookmarks, IBus bus) {
|
||||
_threads = threads;
|
||||
_window_settings = settings;
|
||||
_window_factory = factory;
|
||||
_undo = undo;
|
||||
_dispose = bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore());
|
||||
_bookmarks = bookmarks;
|
||||
_dispose =
|
||||
Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore()),
|
||||
bus.subscribe(SparkTags.ShowWindowAsync).OnDispatcher().Subscribe(w => add(w).ignore()),
|
||||
bus.subscribe(SparkTags.ActivateWindow).OnDispatcher().Subscribe(MakeVisible)
|
||||
);
|
||||
}
|
||||
|
||||
#region Window creation
|
||||
@@ -48,6 +54,7 @@ namespace mbts.ui.shell {
|
||||
var state = model.start_position();
|
||||
if (state != null) {
|
||||
viewmodel.host_id = state.host_id ?? "";
|
||||
viewmodel.TitleOverride = state.custom_title;
|
||||
}
|
||||
var window = CreateWindow(content, state);
|
||||
|
||||
@@ -66,10 +73,10 @@ namespace mbts.ui.shell {
|
||||
}
|
||||
} else {
|
||||
if (position != null) {
|
||||
window = new FloatingWindow(document, this, _undo, _window_settings.Value, _window_factory, true);
|
||||
window = new FloatingWindow(document, this, _bookmarks.Value, _window_settings.Value, _window_factory, true);
|
||||
window.set_position(position);
|
||||
} else
|
||||
window = new FloatingWindow(document, this, _undo, _window_settings.Value, _window_factory);
|
||||
window = new FloatingWindow(document, this, _bookmarks.Value, _window_settings.Value, _window_factory);
|
||||
_windows.Add(window.host_id, window);
|
||||
window.Closed += (o, e) => _windows.Remove(window.host_id);
|
||||
}
|
||||
@@ -81,12 +88,19 @@ namespace mbts.ui.shell {
|
||||
public IReadOnlyList<IShellWindow> list_windows() =>
|
||||
_documents.Keys.ToList();
|
||||
|
||||
public IReadOnlyList<TabDocument> list_documents() => _documents.Values.ToList();
|
||||
|
||||
public async Task add(IShellWindow window) {
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
await Application.Current.Dispatcher.InvokeAsync(() => add(window));
|
||||
Logger.info(bts.log.Subsystem.Ui, $"Opening window {window.diagnostic_name()}.");
|
||||
lock (_lock)
|
||||
lock (_lock) {
|
||||
_documents[window] = _create_document(window);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task add(Task<IShellWindow> window) {
|
||||
await add(await window);
|
||||
}
|
||||
|
||||
public void remove(IShellWindow window) {
|
||||
@@ -139,14 +153,13 @@ namespace mbts.ui.shell {
|
||||
|
||||
#region IWindowsInterop
|
||||
public WindowPosition GetPosition(IShellWindow shell_window) {
|
||||
return GetPosition(shell_window, null);
|
||||
}
|
||||
public WindowPosition GetPosition(IShellWindow shell_window, FloatingWindow owning_window) {
|
||||
FloatingWindow window = owning_window == null ? window_for(shell_window) : owning_window;
|
||||
FloatingWindow window = window_for(shell_window);
|
||||
var doc = window.Documents.FirstOrDefault(td => td.ViewModel.Model == shell_window);
|
||||
if (window != null) {
|
||||
WindowPosition pos = window.get_position();
|
||||
pos.tab_index = window.document_index(shell_window);
|
||||
pos.host_id = window.host_id;
|
||||
pos.custom_title = doc?.ViewModel?.TitleOverride;
|
||||
return pos;
|
||||
}
|
||||
Logger.log(bts.log.Level.Warn, bts.log.Subsystem.Ui, "IShellWindow '{0}' does not have a valid position. It may not be displayed".fmt(shell_window.Title));
|
||||
|
||||
@@ -19,6 +19,7 @@ using mbts.persist;
|
||||
|
||||
namespace mbts.ui.shell
|
||||
{
|
||||
|
||||
[Export]
|
||||
public class WorkspacesModel : ViewModelBaseWithValidation
|
||||
{
|
||||
@@ -35,10 +36,11 @@ namespace mbts.ui.shell
|
||||
private bool _closing = false;
|
||||
private ILauncher _launcher;
|
||||
private IUndoActions _undo;
|
||||
readonly IBookmarks _bookmarks;
|
||||
|
||||
[ImportingConstructor]
|
||||
public WorkspacesModel(ISettingsCollector settings, ISavedWindowFactory window_restore,
|
||||
IWindowManager windows, ObservableCollection<IShellModule> modules, IPersist persist,
|
||||
IWindowManager windows, ObservableCollection<IShellModule> modules, IPersist persist, IBookmarks bm,
|
||||
IUserPersist user_persist, ILauncher launcher, IUndoActions undo, [Import("workspace")]string initial_workspace) {
|
||||
_settings_collector = settings;
|
||||
_window_restore = window_restore;
|
||||
@@ -48,6 +50,7 @@ namespace mbts.ui.shell
|
||||
_modules = modules;
|
||||
_launcher = launcher;
|
||||
_undo = undo;
|
||||
_bookmarks = bm;
|
||||
_launcher.closing += _launcher_closing;
|
||||
Workspaces = new ObservableCollection<WorkspaceViewModel>();
|
||||
init(initial_workspace).ignore();
|
||||
@@ -150,6 +153,7 @@ namespace mbts.ui.shell
|
||||
workspace.windows = _settings_collector.get_window_state().ToList();
|
||||
workspace.modules = _settings_collector.get_module_state().ToList();
|
||||
workspace.launcher_state = _settings_collector.get_launcher_state();
|
||||
workspace.named_windows = _bookmarks.bookmarks.ToList();
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@@ -166,6 +170,7 @@ namespace mbts.ui.shell
|
||||
await Task.Yield();
|
||||
}
|
||||
workspace.launcher_state = _settings_collector.get_launcher_state();
|
||||
workspace.named_windows = _bookmarks.bookmarks.ToList();
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@@ -226,10 +231,15 @@ namespace mbts.ui.shell
|
||||
}
|
||||
}
|
||||
|
||||
// Notify workspace changed
|
||||
if (WorkspaceLoaded != null) {
|
||||
WorkspaceLoaded(name);
|
||||
_bookmarks.clear();
|
||||
if (def.named_windows != null) {
|
||||
foreach (var window in def.named_windows) {
|
||||
_bookmarks.save_bookmark(window);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify workspace changed
|
||||
WorkspaceLoaded?.Invoke(name);
|
||||
}
|
||||
|
||||
public Task delete_workspace(string name, WorkspaceDefinition def) {
|
||||
@@ -244,18 +254,20 @@ namespace mbts.ui.shell
|
||||
}
|
||||
return (def.is_shared ? _public : _private).clear(WORKSPACE_NAMESPACE, name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The serialized data for the Workspace
|
||||
[ProtoContract(Name = "WorkspaceDefinition")]
|
||||
public class WorkspaceDefinition
|
||||
{
|
||||
// Converted the protobuf type ShellSettings to c#
|
||||
// Avoid re-using id: 1,3,4,5,6,7
|
||||
|
||||
[ProtoMember(2, IsRequired = false)]
|
||||
public List<WindowState> windows { get; set; }
|
||||
|
||||
// named windows which are not currently open
|
||||
[ProtoMember(3)]
|
||||
public List<WindowState> named_windows { get; set; }
|
||||
|
||||
[ProtoMember(9, IsRequired = false)]
|
||||
public List<ModuleState> modules { get; set; }
|
||||
|
||||
|
||||
@@ -197,10 +197,6 @@ namespace mbts.spark
|
||||
selected_securities = secs.Select(s => s.Id).ToList();
|
||||
ok_button_command.TryRaiseCanExecuteChanged();
|
||||
_compose_criteria_clause_name();
|
||||
|
||||
// _security_in_criteria is our fun to be returned to our callers
|
||||
// test on click -- verify our func works
|
||||
//var results = all_securities.Select(all => all.Value.security).Where(x => _security_in_criteria(x)).ToList();
|
||||
}
|
||||
|
||||
private void on_securities_changed(IReadOnlyDictionary<uint, SecurityMD> changed_securities) {
|
||||
@@ -212,10 +208,6 @@ namespace mbts.spark
|
||||
all_securities = changed_securities;
|
||||
}
|
||||
|
||||
securities.sync_with(all_securities
|
||||
.Select(entry => entry.Value.security)
|
||||
);
|
||||
|
||||
_update_exchanges();
|
||||
_update_security_types();
|
||||
_update_symbols();
|
||||
@@ -327,7 +319,6 @@ namespace mbts.spark
|
||||
.Select(s => s.Value.security.Symbol)
|
||||
.Distinct()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private void _update_securities() {
|
||||
|
||||
@@ -6,105 +6,40 @@ using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
static class TickUtils {
|
||||
#region TickFactors Cache
|
||||
static readonly TickFactors _default_tick = new TickFactors(100, 1);
|
||||
internal static ContractSpecificationImmutable spec_or_default(this ContractSpecificationImmutable? spec) =>
|
||||
spec.HasValue ? spec.Value : SecurityDataHelper.Defaults.ContractSpecificationImmutable;
|
||||
|
||||
static readonly ConcurrentDictionary<double, bool> _resolved_unknown_ticks = new ConcurrentDictionary<double, bool>();
|
||||
internal static ContractSpecificationImmutable spec_or_default(this SecurityImmutable security) =>
|
||||
security.HasContractSpec ? security.ContractSpec : SecurityDataHelper.Defaults.ContractSpecificationImmutable;
|
||||
|
||||
static readonly ConcurrentDictionary<(double low_tick, double high_tick, double price_break), RegionalTickFactors> _regional_ticks_cache =
|
||||
new ConcurrentDictionary<(double low_tick, double high_tick, double price_break), RegionalTickFactors>();
|
||||
|
||||
static readonly double[] _descending_tick_fractional_units;
|
||||
|
||||
static readonly IReadOnlyDictionary<double, TickFactors> _ticks_cache = new[] {
|
||||
// 100 based
|
||||
new { tick_unit = 0.01, tick_factors = new TickFactors(100, 1) }, // 1/100
|
||||
new { tick_unit = 0.05, tick_factors = new TickFactors(100, 5) }, // 1/20
|
||||
new { tick_unit = 0.10, tick_factors = new TickFactors(100, 10) }, // 1/10
|
||||
new { tick_unit = 0.25, tick_factors = new TickFactors(100, 25) }, // 1/4
|
||||
new { tick_unit = 0.50, tick_factors = new TickFactors(100, 50) }, // 1/2
|
||||
// 1,000 based
|
||||
new { tick_unit = 0.001, tick_factors = new TickFactors(1000, 1) }, // 1/1,000
|
||||
new { tick_unit = 0.005, tick_factors = new TickFactors(1000, 5) }, // 1/200
|
||||
new { tick_unit = 0.025, tick_factors = new TickFactors(1000, 25) }, // 1/40
|
||||
// 10,000 based
|
||||
new { tick_unit = 0.0001, tick_factors = new TickFactors(10000, 1) }, // 1/10,000
|
||||
new { tick_unit = 0.0005, tick_factors = new TickFactors(10000, 5) }, // 1/2,000
|
||||
new { tick_unit = 0.0025, tick_factors = new TickFactors(10000, 25) }, // 1/400
|
||||
// 64 based
|
||||
new { tick_unit = 0.015625, tick_factors = new TickFactors(64, 1) }, // 1/64
|
||||
new { tick_unit = 0.03125, tick_factors = new TickFactors(64, 2) }, // 1/32
|
||||
new { tick_unit = 0.0625, tick_factors = new TickFactors(64, 4) }, // 1/16
|
||||
new { tick_unit = 0.125, tick_factors = new TickFactors(64, 8) }, // 1/8
|
||||
// 640 based
|
||||
new { tick_unit = 0.0015625, tick_factors = new TickFactors(640, 1) }, // 1/640
|
||||
new { tick_unit = 0.003125, tick_factors = new TickFactors(640, 2) }, // 1/320
|
||||
new { tick_unit = 0.00625, tick_factors = new TickFactors(640, 4) }, // 1/160
|
||||
new { tick_unit = 0.0078125, tick_factors = new TickFactors(640, 5) }, // 1/128
|
||||
new { tick_unit = 0.0125, tick_factors = new TickFactors(640, 8) }, // 1/80
|
||||
new { tick_unit = 0.0390625, tick_factors = new TickFactors(640, 25) }, // 5/128
|
||||
new { tick_unit = 0.078125, tick_factors = new TickFactors(640, 50) }, // 5/64
|
||||
// 6,400 based
|
||||
new { tick_unit = 0.00015625, tick_factors = new TickFactors(6400, 1) }, // 1/6,400
|
||||
new { tick_unit = 0.0003125, tick_factors = new TickFactors(6400, 2) }, // 1/3,200
|
||||
new { tick_unit = 0.000625, tick_factors = new TickFactors(6400, 4) }, // 1/1,600
|
||||
new { tick_unit = 0.00078125, tick_factors = new TickFactors(6400, 5) }, // 1/1,280
|
||||
new { tick_unit = 0.00125, tick_factors = new TickFactors(6400, 8) }, // 1/800
|
||||
new { tick_unit = 0.00390625, tick_factors = new TickFactors(6400, 25) }, // 1/256
|
||||
// 64,000 based
|
||||
new { tick_unit = 0.000015625, tick_factors = new TickFactors(64000, 1) }, // 1/6,400
|
||||
new { tick_unit = 0.00003125, tick_factors = new TickFactors(64000, 2) }, // 1/3,200
|
||||
new { tick_unit = 0.0000625, tick_factors = new TickFactors(64000, 4) }, // 1/1,600
|
||||
new { tick_unit = 0.000078125, tick_factors = new TickFactors(64000, 5) }, // 1/12,800
|
||||
new { tick_unit = 0.000125, tick_factors = new TickFactors(64000, 8) }, // 1/8,000
|
||||
new { tick_unit = 0.000390625, tick_factors = new TickFactors(64000, 25) }, // 1/2,560
|
||||
new { tick_unit = 0.001953125, tick_factors = new TickFactors(64000, 125) },// 1/512
|
||||
}.ToReadOnlyDictionary(obj => obj.tick_unit, obj => obj.tick_factors, EqualityComparer.Create<double>(MathUtils.epsilon_equals));
|
||||
#endregion
|
||||
|
||||
static TickUtils() {
|
||||
// overkill? Maybe, but ticks are fundamental to the entire application.
|
||||
// Any changes to the tick defaults must be deliberate, and scrutinized.
|
||||
Logger.assert(_ticks_cache.Count == 35);
|
||||
foreach(var kv in _ticks_cache) {
|
||||
double result = kv.Value.tick_unit / kv.Value.tick_base;
|
||||
Logger.assert(result == kv.Key, $"{kv.Value.tick_unit}/{kv.Value.tick_base} ({result} != {kv.Key})");
|
||||
}
|
||||
_descending_tick_fractional_units = _ticks_cache.Keys.Ordered(new ReverseComparer<double>(Comparer<double>.Default)).ToLArray();
|
||||
}
|
||||
internal static ContractSpecificationImmutable spec_or_default(this SecurityImmutable? security) =>
|
||||
security.HasValue && security.Value.HasContractSpec ? security.Value.ContractSpec : SecurityDataHelper.Defaults.ContractSpecificationImmutable;
|
||||
|
||||
public static double ticksize(this ContractSpecificationImmutable spec, double price) =>
|
||||
Math.Abs(price) > spec.PriceBreak ? spec.HighTick : spec.LowTick;
|
||||
|
||||
public static string format(this ContractSpecificationImmutable spec, double price) {
|
||||
var tick_factors = _get_tick_factors(spec.ticksize(price));
|
||||
return tick_factors.format(price, false);
|
||||
}
|
||||
public static string format(this ContractSpecificationImmutable? spec, double price) {
|
||||
if (spec.HasValue) {
|
||||
return spec.Value.format(price);
|
||||
}
|
||||
return price.ToString("0.00000");
|
||||
}
|
||||
public static string format(this ContractSpecificationImmutable spec, double price) =>
|
||||
TickStore.get(spec).format(price);
|
||||
|
||||
public static string format(this ContractSpecificationImmutable spec, double price, double display_multiplier, bool show_commas, string nan_text) {
|
||||
var tick_factors = _get_tick_factors(spec.ticksize(price));
|
||||
return tick_factors.format(price, display_multiplier, show_commas, nan_text);
|
||||
}
|
||||
public static string format(this ContractSpecificationImmutable? spec, double price) =>
|
||||
spec.spec_or_default().format(price);
|
||||
|
||||
public static double parse(this ContractSpecificationImmutable spec, string text) {
|
||||
var regional_tfs = get_regional_ticks(spec);
|
||||
return regional_tfs.parse(text);
|
||||
}
|
||||
public static string format(this ContractSpecificationImmutable spec, double price, double display_multiplier, bool show_commas, string nan_text) =>
|
||||
TickStore.get(spec).format(price, display_multiplier, show_commas, nan_text);
|
||||
|
||||
public static double parse(this ContractSpecificationImmutable spec, string text, double display_multiplier, string nan_text) {
|
||||
var regional_tfs = get_regional_ticks(spec);
|
||||
return regional_tfs.parse(text, display_multiplier, nan_text);
|
||||
}
|
||||
public static string format(this ContractSpecificationImmutable? spec, double price, double display_multiplier, bool show_commas, string nan_text) =>
|
||||
spec.spec_or_default().format(price, display_multiplier, show_commas, nan_text);
|
||||
|
||||
public static double parse(this ContractSpecificationImmutable spec, string text, double display_multiplier, string nan_text) =>
|
||||
TickStore.get(spec).parse(text, display_multiplier, nan_text);
|
||||
|
||||
public static double parse(this ContractSpecificationImmutable? spec, string text, double display_multiplier, string nan_text) =>
|
||||
spec.spec_or_default().parse(text, display_multiplier, nan_text);
|
||||
|
||||
public static double low_tick(NativePriceSize[] bids, NativePriceSize[] asks) {
|
||||
double low = bids[bids.Length - 1].price;
|
||||
@@ -132,6 +67,9 @@ namespace mbts.spark {
|
||||
return n_low + n_high;
|
||||
}
|
||||
|
||||
public static int normalized_tick_diff(SecurityImmutable security, double x, double y) =>
|
||||
normalized_tick_diff(security.spec_or_default(), x, y);
|
||||
|
||||
public static double mid_tick(ContractSpecificationImmutable spec, double bid, double ask) {
|
||||
double raw_mid;
|
||||
if (double.IsNaN(bid)) {
|
||||
@@ -146,36 +84,35 @@ namespace mbts.spark {
|
||||
return raw_mid - (raw_mid % tick_size);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<TickFactors> all_present_ticks() =>
|
||||
_ticks_cache.Values.ToReadOnlyList();
|
||||
|
||||
public static TickFactors deduce_ticks(IEnumerable<ContractSpecification> contract_specs) {
|
||||
var gcd = MathUtils.GCD(contract_specs.Select(s => s.LowTick));
|
||||
if (gcd == 0) return null;
|
||||
return _try_get_tick_factors(gcd);
|
||||
var ticks = new HashSet<TickFactors>(contract_specs.Select(TickStore.get).SelectMany(rtf => new[] { rtf.low_tick, rtf.high_tick }));
|
||||
switch (ticks.Count) {
|
||||
case 0: return TickStore.default_rtf.low_tick;
|
||||
case 1: return ticks.First();
|
||||
default:
|
||||
double tick_base_lcm = MathUtils.LCM(ticks.Select(t => (int)t.tick_base));
|
||||
double tick_unit_gcd = MathUtils.GCD(ticks.Select(t => t.tick_unit * (tick_base_lcm / t.tick_base)));
|
||||
return new TickFactors(tick_base_lcm, tick_unit_gcd);
|
||||
}
|
||||
}
|
||||
|
||||
public static double tickup(this ContractSpecificationImmutable contract, double value) =>
|
||||
get_regional_ticks(contract).tickup(value);
|
||||
public static double tickup(this ContractSpecificationImmutable spec, double value) =>
|
||||
TickStore.get(spec).tickup(value);
|
||||
|
||||
public static double tickup(this ContractSpecificationImmutable contract, double value, int count) =>
|
||||
get_regional_ticks(contract).tickup(value, (uint)count);
|
||||
public static double tickup(this ContractSpecificationImmutable spec, double value, int count) =>
|
||||
TickStore.get(spec).tickup(value, (uint)count);
|
||||
|
||||
public static double tickdown(this ContractSpecificationImmutable contract, double value) =>
|
||||
get_regional_ticks(contract).tickdown(value);
|
||||
public static double tickdown(this ContractSpecificationImmutable spec, double value) =>
|
||||
TickStore.get(spec).tickdown(value);
|
||||
|
||||
public static double tickdown(this ContractSpecificationImmutable contract, double value, int count) =>
|
||||
get_regional_ticks(contract).tickdown(value, (uint)count);
|
||||
public static double tickdown(this ContractSpecificationImmutable spec, double value, int count) =>
|
||||
TickStore.get(spec).tickdown(value, (uint)count);
|
||||
|
||||
public static bool tick_equals(this ContractSpecificationImmutable contract, double x, double y) =>
|
||||
MathUtils.RoundFractionalUnits(x, contract.LowTick) == MathUtils.RoundFractionalUnits(y, contract.LowTick);
|
||||
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this ContractSpecificationImmutable contract)
|
||||
{
|
||||
var eq = EqualityComparer.Create((double a, double b) => TickUtils.tick_equals(contract, a, b),
|
||||
x => MathUtils.RoundFractionalUnits(x, contract.LowTick));
|
||||
return eq;
|
||||
}
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this ContractSpecificationImmutable contract) =>
|
||||
EqualityComparer.Create((double a, double b) => tick_equals(contract, a, b), x => MathUtils.RoundFractionalUnits(x, contract.LowTick));
|
||||
|
||||
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) {
|
||||
double intpart = (long)value;
|
||||
@@ -186,46 +123,196 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public static RegionalTickFactors get_regional_ticks(ContractSpecificationImmutable spec) =>
|
||||
_regional_ticks_cache.GetOrAdd((spec.LowTick, spec.HighTick, spec.PriceBreak), _create_regional_ticks);
|
||||
TickStore.get(spec);
|
||||
|
||||
public static RegionalTickFactors get_regional_ticks(ContractSpecification spec) =>
|
||||
_regional_ticks_cache.GetOrAdd((spec.LowTick, spec.HighTick, spec.PriceBreak), _create_regional_ticks);
|
||||
public static RegionalTickFactors get_regional_ticks(SecurityImmutable security) =>
|
||||
get_regional_ticks(security.spec_or_default());
|
||||
|
||||
static RegionalTickFactors _create_regional_ticks((double low_tick, double high_tick, double price_break) def) =>
|
||||
new RegionalTickFactors(_get_tick_factors(def.low_tick), _get_tick_factors(def.high_tick), def.price_break);
|
||||
static TickFactors _inflate_to_base(TickFactors tick_factors, double new_base) =>
|
||||
new TickFactors(new_base, tick_factors.tick_unit * new_base / tick_factors.tick_base);
|
||||
|
||||
static TickFactors _get_tick_factors(double tick) {
|
||||
if (_ticks_cache.TryGetValue(tick, out TickFactors result))
|
||||
return result;
|
||||
// try to return the largest known tick that is divisible by this tick
|
||||
foreach (var tfu in _descending_tick_fractional_units) {
|
||||
double mod = tick % tfu;
|
||||
if (MathUtils.epsilon_equals(mod, 0)) {
|
||||
if (_resolved_unknown_ticks.TryAdd(tick, true))
|
||||
Logger.warn(bts.log.Subsystem.Ui, $"Resolved unknown tick {tick} to {tfu}, if this tick is expected, talk to John about adding it to the hard-coded defaults ASAP.");
|
||||
return _ticks_cache[tfu];
|
||||
}
|
||||
public static class CompareUtils {
|
||||
public static IComparer<double> base_comparer =>
|
||||
Comparer<double>.Create(_compare_base);
|
||||
|
||||
public static IComparer<double> unit_comparer =>
|
||||
Comparer<double>.Create(_compare_unit);
|
||||
|
||||
public static IComparer<TickFactors> base_unit_comparer =>
|
||||
Comparer<TickFactors>.Create(utils.CompareUtils.safe_comparison<TickFactors>(compare_base_unit));
|
||||
|
||||
public static int compare_base_unit(TickFactors a, TickFactors b) {
|
||||
int comp = _compare_base(a.tick_base, b.tick_base);
|
||||
return comp == 0 ? a.tick_unit.CompareTo(b.tick_unit) : comp;
|
||||
}
|
||||
|
||||
static int _compare_base(double a, double b) {
|
||||
double reduced_a = MathUtils.factor_out(a, 10);
|
||||
double reduced_b = MathUtils.factor_out(b, 10);
|
||||
int comp = reduced_a.CompareTo(reduced_b);
|
||||
return comp == 0 ? a.CompareTo(b) : comp;
|
||||
}
|
||||
|
||||
static int _compare_unit(double a, double b) =>
|
||||
_compare_unit(a, b, 0, 0);
|
||||
|
||||
static int _compare_unit(double a, double b, int a_mult10, int b_mult10) {
|
||||
// fractional-coins are a special case, as they may not be an integer to begin with
|
||||
if (!MathUtils.is_int(a))
|
||||
return _compare_unit(a * 10, b, ++a_mult10, b_mult10);
|
||||
if (!MathUtils.is_int(b))
|
||||
return _compare_unit(a, b * 10, a_mult10, ++b_mult10);
|
||||
double reduced_a = a_mult10 > 0 ? a : MathUtils.factor_out(a, 10, 5);
|
||||
double reduced_b = b_mult10 > 0 ? b : MathUtils.factor_out(b, 10, 5);
|
||||
int comp = reduced_a.CompareTo(reduced_b);
|
||||
return comp == 0 ? (a * Math.Pow(10, -a_mult10)).CompareTo(b * Math.Pow(10, -b_mult10)) : comp;
|
||||
}
|
||||
Logger.assert(false, $"Unable to resolve tick ({tick}): going down because the application cannot run properly with unresolved ticks. " +
|
||||
$"FOR DEVS: If this tick was not caused by a bug, please talk to John about adding it to the hard-coded defaults. " +
|
||||
$"If you believe this tick belongs to a synthetic security, and should not be in the defaults, please delete said synthetic from the db " +
|
||||
$"(note: the synthetic creation dialog has changed to only allow known ticks from now on, so if you get the latest code and delete old synthetics from the db, this will not happen again). " +
|
||||
$"Direct any further questions to John.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Almost the same function as _get_tick_factors, only difference being no assert/log.
|
||||
// This seemed more practical than trying to factor this behavior out, it's only a few lines of code.
|
||||
static TickFactors _try_get_tick_factors(double tick) {
|
||||
if (_ticks_cache.TryGetValue(tick, out TickFactors result))
|
||||
return result;
|
||||
// try to return the largest known tick that is divisible by this tick
|
||||
foreach (var tfu in _descending_tick_fractional_units) {
|
||||
double mod = tick % tfu;
|
||||
if (MathUtils.epsilon_equals(mod, 0))
|
||||
return _ticks_cache[tfu];
|
||||
static class TickStore {
|
||||
public static readonly RegionalTickFactors default_rtf = new RegionalTickFactors(new TickFactors(1000, 1), new TickFactors(1000, 1), double.MaxValue);
|
||||
static readonly ConcurrentDictionary<SpecKey, RegionalTickFactors> _exchange_ticks = new ConcurrentDictionary<SpecKey, RegionalTickFactors>();
|
||||
|
||||
public static RegionalTickFactors get(ContractSpecification spec) =>
|
||||
_exchange_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
|
||||
|
||||
public static RegionalTickFactors get(ContractSpecificationImmutable spec) =>
|
||||
_exchange_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
|
||||
|
||||
public static RegionalTickFactors get(SecurityImmutable security) =>
|
||||
security.HasContractSpec ? get(security.ContractSpec) : default_rtf;
|
||||
|
||||
public static RegionalTickFactors get(SecurityImmutable? security) =>
|
||||
security.HasValue ? get(security.Value) : default_rtf;
|
||||
|
||||
static RegionalTickFactors _create_rtf(SpecKey key) {
|
||||
double tick_base = key.denominator;
|
||||
if (tick_base == 0) {
|
||||
// indicates tick is a decimal
|
||||
tick_base = 100;
|
||||
}
|
||||
return _create_rtf(key.low_tick, key.high_tick, key.price_break, tick_base, key.display_digits);
|
||||
}
|
||||
|
||||
static RegionalTickFactors _create_rtf(double low_tick, double high_tick, double price_break, double reduced_target_base, uint display_digits_override) {
|
||||
var low_tf = _try_create_tf(low_tick, reduced_target_base, display_digits_override);
|
||||
var high_tf = _try_create_tf(high_tick, reduced_target_base, display_digits_override);
|
||||
if (low_tf == null) {
|
||||
low_tf = high_tf ?? (high_tf = default_rtf.low_tick);
|
||||
} else if (high_tf == null)
|
||||
high_tf = low_tf;
|
||||
else {
|
||||
// if low/high tick-bases do not match, inflate the smaller base so they do
|
||||
// Note: this assumes the tick-bases are related by factors of 10 (i.e. they have all the same prime factors except for a different number of pairs of 5 and 2, e.g. 64 and 640)
|
||||
if (low_tf.tick_base > high_tf.tick_base) {
|
||||
high_tf = _inflate_to_base(high_tf, low_tf.tick_base);
|
||||
} else if (low_tf.tick_base < high_tf.tick_base) {
|
||||
low_tf = _inflate_to_base(low_tf, high_tf.tick_base);
|
||||
}
|
||||
}
|
||||
return new RegionalTickFactors(low_tf, high_tf, price_break);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates TickFactors with a base equal to "<paramref name="tick_base"/> * 10^n," where 'n' is a natural number (including 0).
|
||||
/// <para>Note: If <paramref name="display_digits_override"/> is non-zero, 'n' will be "<paramref name="display_digits_override"/> - Math.Ceiling(Log10(<paramref name="tick_base"/>))"</para>
|
||||
/// </summary>
|
||||
/// <param name="tick_size">The raw tick-size.</param>
|
||||
/// <param name="tick_base">The raw tick-base.</param>
|
||||
/// <returns>Well-defined TickFactors</returns>
|
||||
static TickFactors _try_create_tf(double tick_size, double tick_base, uint display_digits_override) {
|
||||
var tf = _try_create_tf(tick_size, tick_base);
|
||||
if (tf != null && display_digits_override > 0) {
|
||||
int display_digit_diff = (int)display_digits_override - tf.display_digits();
|
||||
if (display_digit_diff != 0)
|
||||
tf = _inflate_to_base(tf, tf.tick_base * Math.Pow(10, display_digit_diff));
|
||||
}
|
||||
return tf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates TickFactors with a base equal to "reduced_target_base * 10^n," where 'n' is a natural number (including 0).
|
||||
/// </summary>
|
||||
/// <param name="tick_size">The raw tick-size.</param>
|
||||
/// <param name="reduced_target_base">The smallest acceptable tick-base.</param>
|
||||
/// <returns>Well-defined TickFactors</returns>
|
||||
static TickFactors _try_create_tf(double tick_size, double reduced_target_base) {
|
||||
if (!_check_tick_size(tick_size))
|
||||
return null;
|
||||
double raw_base = 1 / tick_size;
|
||||
double raw_base_log10 = Math.Log10(raw_base);
|
||||
double reduced_target_base_log10 = Math.Log10(reduced_target_base);
|
||||
double log10_diff = raw_base_log10 - reduced_target_base_log10;
|
||||
if (MathUtils.is_int(log10_diff))
|
||||
return raw_base > reduced_target_base ? new TickFactors(raw_base, 1) : new TickFactors(reduced_target_base, Math.Pow(10, -log10_diff));
|
||||
int count = 0;
|
||||
double factored_base = log10_diff > 0 ? reduced_target_base * Math.Pow(10, Math.Ceiling(log10_diff)) : reduced_target_base;
|
||||
do {
|
||||
if (factored_base % raw_base == 0)
|
||||
return new TickFactors(factored_base, factored_base / raw_base);
|
||||
factored_base *= 10;
|
||||
} while (++count < 10);
|
||||
return null;
|
||||
}
|
||||
|
||||
static TickFactors _create_tf(double tick_size) {
|
||||
if (!_check_tick_size(tick_size))
|
||||
return default_rtf.low_tick;
|
||||
double matched_unit = double.MinValue;
|
||||
TickFactors matched_tf = null;
|
||||
foreach (var kv in _exchange_ticks) {
|
||||
if (kv.Key.high_tick == tick_size) {
|
||||
return kv.Value.high_tick;
|
||||
} else if (kv.Key.low_tick == tick_size) {
|
||||
return kv.Value.low_tick;
|
||||
} else if (is_better_match(kv.Key.high_tick)) {
|
||||
matched_unit = kv.Key.high_tick;
|
||||
matched_tf = kv.Value.high_tick;
|
||||
} else if (is_better_match(kv.Key.low_tick)) {
|
||||
matched_unit = kv.Key.low_tick;
|
||||
matched_tf = kv.Value.low_tick;
|
||||
}
|
||||
}
|
||||
return matched_tf ?? new TickFactors(1.0 / tick_size, 1);
|
||||
|
||||
bool is_better_match(double known_unit) =>
|
||||
known_unit < tick_size && known_unit > matched_unit && tick_size % known_unit == 0;
|
||||
}
|
||||
|
||||
static bool _check_tick_size(double tick_size) =>
|
||||
tick_size > 0 && tick_size <= 0.5;
|
||||
|
||||
struct SpecKey : IEquatable<SpecKey> {
|
||||
public readonly uint denominator, display_digits;
|
||||
public readonly double low_tick, high_tick, price_break;
|
||||
|
||||
public SpecKey(ContractSpecification spec)
|
||||
: this(spec.MainFraction, spec.PriceDisplayFormat, spec.LowTick, spec.HighTick, spec.PriceBreak) { }
|
||||
|
||||
public SpecKey(ContractSpecificationImmutable spec)
|
||||
: this(spec.MainFraction, spec.PriceDisplayFormat, spec.LowTick, spec.HighTick, spec.PriceBreak) { }
|
||||
|
||||
public SpecKey(uint denominator, uint display_digits, double low_tick, double high_tick, double price_break) {
|
||||
this.denominator = denominator;
|
||||
this.display_digits = display_digits;
|
||||
this.low_tick = low_tick;
|
||||
this.high_tick = high_tick;
|
||||
this.price_break = price_break;
|
||||
}
|
||||
|
||||
public bool Equals(SpecKey other) =>
|
||||
denominator == other.denominator &&
|
||||
display_digits == other.display_digits &&
|
||||
low_tick == other.low_tick &&
|
||||
high_tick == other.high_tick &&
|
||||
price_break == other.price_break;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is SpecKey sk && Equals(sk);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash((int)denominator, (int)display_digits, low_tick.GetHashCode(), high_tick.GetHashCode(), price_break.GetHashCode());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,13 +37,13 @@ namespace bts.spark
|
||||
if (this.IsRoot) {
|
||||
return Enumerable.Empty<OrderNode>();
|
||||
}
|
||||
return this.Parent.ToIEnumerable().Concat(this.Parent.Ancestors);
|
||||
return new[] { this.Parent }.Concat(this.Parent.Ancestors);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<OrderNode> MeAndDescedants {
|
||||
get {
|
||||
return this.ToIEnumerable().Concat(this.children.SelectMany(c => c.MeAndDescedants));
|
||||
return new[] { this }.Concat(this.children.SelectMany(c => c.MeAndDescedants));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,5 +63,32 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
public static int RoundFractionalUnits(double value, double fractional_unit) =>
|
||||
(int)Math.Round(value / fractional_unit);
|
||||
|
||||
public static bool is_int(double d) =>
|
||||
epsilon_equals(d, Math.Round(d));
|
||||
|
||||
public static bool is_int(double d, out double with_int_value) {
|
||||
with_int_value = Math.Round(d);
|
||||
return epsilon_equals(with_int_value, d);
|
||||
}
|
||||
|
||||
public static double factor_out(double value, params double[] factors) {
|
||||
foreach (double f in factors)
|
||||
value = factor_out(value, f, out int _);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static double factor_out(double value, double factor, out int n_factors_removed) {
|
||||
n_factors_removed = 0;
|
||||
if (value == 0)
|
||||
return value;
|
||||
double quotient = value / factor;
|
||||
while (is_int(quotient, out quotient)) {
|
||||
value = quotient;
|
||||
++n_factors_removed;
|
||||
quotient /= factor;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using bts.api;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static class SecurityDataHelper
|
||||
{
|
||||
namespace mbts.spark {
|
||||
static class SecurityDataHelper {
|
||||
public static class EqualityComparer {
|
||||
public static readonly IEqualityComparer<SecurityMD> MDComparer = utils.EqualityComparer.Create<SecurityMD>(Equals, GetHashCode);
|
||||
public static bool Equals(SecurityMD x, SecurityMD y) => x.security.Id == y.security.Id;
|
||||
@@ -14,13 +12,34 @@ namespace mbts.spark
|
||||
}
|
||||
|
||||
public static class Defaults {
|
||||
public static ContractSpecification ContractSpecification => new ContractSpecification { CashPerPoint = 1.0, LowTick = 0.01, HighTick = 0.01, PriceBreak = double.MaxValue, PriceMultiplier = 1.0 };
|
||||
public static ContractSpecification ContractSpecification =>
|
||||
new ContractSpecification {
|
||||
CashPerPoint = 1.0,
|
||||
LowTick = 0.0001,
|
||||
HighTick = 0.0001,
|
||||
PriceBreak = double.MaxValue,
|
||||
PriceMultiplier = 1.0,
|
||||
MainFraction = 10000
|
||||
};
|
||||
public static readonly ContractSpecificationImmutable ContractSpecificationImmutable = new ContractSpecificationImmutable(ContractSpecification);
|
||||
public static Security Security => new Security {
|
||||
Symbol = string.Empty,
|
||||
Exchange = Venue.Bts,
|
||||
ContractSpec = ContractSpecification,
|
||||
};
|
||||
|
||||
public static ContractSpecification ContractSpecificationPennies =>
|
||||
new ContractSpecification {
|
||||
CashPerPoint = 1.0,
|
||||
LowTick = 0.01,
|
||||
HighTick = 0.01,
|
||||
PriceBreak = double.MaxValue,
|
||||
PriceMultiplier = 1.0,
|
||||
MainFraction = 100
|
||||
};
|
||||
public static ContractSpecificationImmutable ContractSpecificationImmutablePennies = new ContractSpecificationImmutable(ContractSpecificationPennies);
|
||||
|
||||
public static Security Security =>
|
||||
new Security {
|
||||
Symbol = string.Empty,
|
||||
Exchange = Venue.Bts,
|
||||
ContractSpec = ContractSpecification
|
||||
};
|
||||
public static readonly SecurityImmutable SecurityImmutable = new SecurityImmutable(Security);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,15 @@ namespace mbts.ui {
|
||||
d.SetValue(property, new_value);
|
||||
}
|
||||
|
||||
public static void set_current_if_changed<T>(DependencyObject d, DependencyProperty property, T new_value) =>
|
||||
set_current_if_changed(d, property, new_value, EqualityComparer<T>.Default);
|
||||
|
||||
public static void set_current_if_changed<T>(DependencyObject d, DependencyProperty property, T new_value, IEqualityComparer<T> comparer) {
|
||||
object curr = d.GetValue(property);
|
||||
if (!comparer.to_non_generic().Equals(d.GetValue(property), new_value))
|
||||
d.SetCurrentValue(property, new_value);
|
||||
}
|
||||
|
||||
public static PropertyChangedCallback prop_changed_callback<TValue, TOwner>(PropertyChangedCallback<TValue, TOwner> callback) =>
|
||||
(d, e) => callback(ObjectHelper.try_convert<TOwner>(d), ObjectHelper.try_convert<TValue>(e.OldValue), ObjectHelper.try_convert<TValue>(e.NewValue));
|
||||
|
||||
|
||||
@@ -820,19 +820,49 @@ void AdminServiceUtils::delete_user (bts::api::admin::AdminData& admin_data,
|
||||
////////////////////////////////////////////
|
||||
bool AdminServiceUtils::_sort_security_scopes (const bts::api::admin::SecurityScope& a,
|
||||
const bts::api::admin::SecurityScope& b){
|
||||
if (has_id(a.security_id()))
|
||||
return true;
|
||||
// for both the sec and product scope sorting, the more
|
||||
// qualified arg is sent to the top (vs wildcard). the
|
||||
// order of qualification is sec_id, symbol, type, exchange.
|
||||
// if both a and b are equally qualified, it is a
|
||||
// a dont-care case (in terms of sec/prod res) and
|
||||
// ascending orderring is used
|
||||
if (has_id(a.security_id())){
|
||||
if (has_id(b.security_id()))
|
||||
return a.security_id() < b.security_id();
|
||||
else
|
||||
return true;
|
||||
}
|
||||
if (has_id(b.security_id()))
|
||||
return false;
|
||||
if (a.security_symbol() != "*")
|
||||
return true;
|
||||
return false;
|
||||
// symbol
|
||||
if (is_symbol_set(a.security_symbol())){
|
||||
if (is_symbol_set(b.security_symbol()))
|
||||
return a.security_symbol() < b.security_symbol();
|
||||
else
|
||||
return true;
|
||||
}
|
||||
if (is_symbol_set(b.security_symbol()))
|
||||
return false;
|
||||
// sec type
|
||||
if (a.security_type() != b.security_type())
|
||||
return a.security_type() < b.security_type();
|
||||
return a.exchange() < b.exchange();
|
||||
}
|
||||
bool AdminServiceUtils::_sort_product_scopes (const bts::api::admin::ProductScope& a,
|
||||
const bts::api::admin::ProductScope&){
|
||||
if (a.security_symbol() != "*")
|
||||
return true;
|
||||
return false;
|
||||
const bts::api::admin::ProductScope& b){
|
||||
// symbol
|
||||
if (is_symbol_set(a.security_symbol())){
|
||||
if (is_symbol_set(b.security_symbol()))
|
||||
return a.security_symbol() < b.security_symbol();
|
||||
else
|
||||
return true;
|
||||
}
|
||||
if (is_symbol_set(b.security_symbol()))
|
||||
return false;
|
||||
// sec type
|
||||
if (a.security_type() != b.security_type())
|
||||
return a.security_type() < b.security_type();
|
||||
return a.exchange() < b.exchange();
|
||||
}
|
||||
void AdminServiceUtils::sort_limits (bts::api::admin::BTSAccount& account){
|
||||
std::sort (account.mutable_security_position_limits()->begin(),
|
||||
@@ -1025,4 +1055,7 @@ void AdminServiceUtils::bootstrap_data (bts::api::admin::AdminData& data){
|
||||
acct_a1_clip_size->set_clip_size(100);
|
||||
}
|
||||
bool AdminServiceUtils::has_id (uint32_t id){return id>0;}
|
||||
bool AdminServiceUtils::is_symbol_set (const std::string& symbol){
|
||||
return symbol !="*";
|
||||
}
|
||||
}}
|
||||
@@ -240,6 +240,7 @@ public:
|
||||
// misc//////// ////////////////////////////
|
||||
////////////////////////////////////////////
|
||||
static bool has_id (uint32_t id);
|
||||
static bool is_symbol_set (const std::string& symbol);
|
||||
static void bootstrap_data (bts::api::admin::AdminData& data);
|
||||
};
|
||||
}}
|
||||
@@ -76,6 +76,10 @@ message CMEOptions
|
||||
repeated uint32 affinity = 10;
|
||||
optional uint32 udp_socket_buffer_size = 11 [default = 33554432]; //32mb
|
||||
}
|
||||
enum BookType {
|
||||
BOOKTYPE_CONSOLIDATED = 0;
|
||||
BOOKTYPE_DIRECT = 1;
|
||||
}
|
||||
message CMEChannel {
|
||||
repeated string id = 2;
|
||||
optional string feed_engine = 3 [default = "default"];
|
||||
@@ -98,6 +102,7 @@ message CMEOptions
|
||||
optional bool join_snapshot_on_gap = 32 [default = false];
|
||||
|
||||
optional bool load_security_db = 33 [default = true];
|
||||
optional BookType book_type = 34 [default = BOOKTYPE_CONSOLIDATED];
|
||||
|
||||
}
|
||||
optional string onixs_license = 1 [default = "{default}"];
|
||||
|
||||
@@ -55,9 +55,9 @@ message GLBXExchangeData {
|
||||
string security_desc = 20;
|
||||
string cfi_code = 21;
|
||||
string cme_symbol = 22;
|
||||
uint32 main_fraction = 30;
|
||||
uint32 sub_fraction = 31;
|
||||
uint32 price_display_format = 32;
|
||||
uint32 do_not_use_main_fraction = 30;
|
||||
uint32 do_not_use_sub_fraction = 31;
|
||||
uint32 do_not_use_price_display_format = 32;
|
||||
uint32 market_segment_id = 33;
|
||||
}
|
||||
message CBOEExchangeData {
|
||||
@@ -117,6 +117,10 @@ message ContractSpecification {
|
||||
double high_tick = 30;
|
||||
double cash_per_point = 40;
|
||||
double price_multiplier = 50;
|
||||
|
||||
uint32 main_fraction = 60;
|
||||
uint32 sub_fraction = 61;
|
||||
uint32 price_display_format = 62;
|
||||
}
|
||||
|
||||
message Security {
|
||||
|
||||
@@ -71,9 +71,9 @@ string dump_security(const FixedSecurity& security) {
|
||||
res << "'display_factor': " << (int)security.display_factor << ", ";
|
||||
res << "'numeric_id': " << security.numeric_id << ", ";
|
||||
res << "'cboe_class_key': " << security.cboe_class_key << ", ";
|
||||
res << "'cme_main_frac': " << (int)security.cme_main_fraction << ", ";
|
||||
res << "'cme_sub_fract': " << (int)security.cme_sub_fraction << ", ";
|
||||
res << "'cme_price_dformat': " << (int)security.cme_price_display_format << ", ";
|
||||
res << "'main_frac': " << (int)security.cme_main_fraction << ", ";
|
||||
res << "'sub_fract': " << (int)security.cme_sub_fraction << ", ";
|
||||
res << "'price_format': " << (int)security.cme_price_display_format << ", ";
|
||||
|
||||
res << "'market_segment': " << security.cme_market_segment_id << ", ";
|
||||
|
||||
|
||||
@@ -72,7 +72,19 @@ size_t load_securities(
|
||||
if (symbols.find(us) != symbols.end()) {
|
||||
auto data = sqlite3_column_blob(select, 4);
|
||||
auto len = sqlite3_column_bytes(select, 4);
|
||||
// Migrate from old tick stuff
|
||||
if (security.ParseFromArray(data, len)) {
|
||||
if (security.has_glbx()) {
|
||||
if (security.contract_spec().main_fraction() == 0) {
|
||||
security.mutable_contract_spec()->set_main_fraction(security.glbx().do_not_use_main_fraction());
|
||||
}
|
||||
if (security.contract_spec().sub_fraction() == 0) {
|
||||
security.mutable_contract_spec()->set_sub_fraction(security.glbx().do_not_use_sub_fraction());
|
||||
}
|
||||
if (security.contract_spec().price_display_format() == 0) {
|
||||
security.mutable_contract_spec()->set_price_display_format(security.glbx().do_not_use_price_display_format());
|
||||
}
|
||||
}
|
||||
bts_assert(security_key == security.id());
|
||||
count++;
|
||||
callback(security);
|
||||
@@ -89,7 +101,7 @@ size_t load_securities(
|
||||
|
||||
void store_if_new(const std::vector<Security*>& securities) {
|
||||
auto dbptr = open_db();
|
||||
auto insert = utils::prepare_query(dbptr, "insert into securities (key, symbol, venue, type, security_blob, expiration) values (?,?,?,?,?,?)");
|
||||
auto insert = utils::prepare_query(dbptr, "insert or ignore into securities (key, symbol, venue, type, security_blob, expiration) values (?,?,?,?,?,?)");
|
||||
sqlite3_exec_nofail(dbptr, "BEGIN TRANSACTION");
|
||||
std::string buffer;
|
||||
for (const api::Security* psecurity : securities) {
|
||||
@@ -100,10 +112,7 @@ void store_if_new(const std::vector<Security*>& securities) {
|
||||
SQLITE_CHK(sqlite3_bind_int(insert.get(), 4, securitytype_of(*psecurity)), dbptr);
|
||||
SQLITE_CHK(sqlite3_bind_blob(insert.get(), 5, buffer.c_str(), buffer.size(), SQLITE_STATIC), dbptr);
|
||||
SQLITE_CHK(sqlite3_bind_int(insert.get(), 6, expiration(*psecurity).to_short()), dbptr);
|
||||
auto result = sqlite3_step(insert.get());
|
||||
if (result != SQLITE_DONE) {
|
||||
LOG(md, Warn, "Failed to insert security into security db. key={}", psecurity->id());
|
||||
}
|
||||
sqlite3_step(insert.get());
|
||||
sqlite3_reset(insert.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,8 @@ void SecurityIDRecorder::work_db() {
|
||||
// Once a security has a key it is public so record it in the security database
|
||||
std::shared_ptr<SecurityIDEntry> temp;
|
||||
vector<std::shared_ptr<SecurityIDEntry>> new_entries;
|
||||
// SPARK-1446 Need to try to add existing securities to the db in case we deleted them by hand
|
||||
vector<std::shared_ptr<SecurityIDEntry>> existing_securities;
|
||||
vector<std::shared_ptr<SecurityIDEntry>> defer;
|
||||
vector<string> new_descriptors;
|
||||
size_t count = 0;
|
||||
@@ -150,6 +152,7 @@ void SecurityIDRecorder::work_db() {
|
||||
if (key != invalid_security_key) {
|
||||
temp->security.set_id(key);
|
||||
temp->callback(temp->security);
|
||||
existing_securities.push_back(move(temp));
|
||||
}
|
||||
else {
|
||||
// always push in parallel
|
||||
@@ -168,7 +171,7 @@ void SecurityIDRecorder::work_db() {
|
||||
}
|
||||
|
||||
|
||||
if (new_entries.size() > 0) {
|
||||
if (!new_entries.empty()) {
|
||||
sqlite3_exec_nofail(_self->db.get(), "BEGIN TRANSACTION");
|
||||
|
||||
for (size_t x = 0; x < new_entries.size(); ++x) {
|
||||
@@ -179,15 +182,22 @@ void SecurityIDRecorder::work_db() {
|
||||
}
|
||||
|
||||
sqlite3_exec_nofail(_self->db.get(), "COMMIT TRANSACTION");
|
||||
}
|
||||
|
||||
// record the securities
|
||||
// record the securities
|
||||
if (!new_entries.empty() || !existing_securities.empty()) {
|
||||
std::vector<Security*> save_these;
|
||||
save_these.reserve(new_entries.size());
|
||||
save_these.reserve(new_entries.size() + existing_securities.size());
|
||||
for (const auto& e : new_entries) {
|
||||
save_these.push_back(&e->security);
|
||||
}
|
||||
for (const auto& e : existing_securities) {
|
||||
save_these.push_back(&e->security);
|
||||
}
|
||||
db::store_if_new(save_these);
|
||||
}
|
||||
|
||||
if (!new_entries.empty()) {
|
||||
for (size_t x = 0; x < new_entries.size(); ++x) {
|
||||
Security& security = new_entries[x]->security;
|
||||
new_entries[x]->callback(security);
|
||||
@@ -199,9 +209,18 @@ void SecurityIDRecorder::bulk_insert(const std::vector<const api::Security*>& da
|
||||
if (data.size() == 0) return;
|
||||
sqlite3_exec_nofail(_self->db.get(), "BEGIN TRANSACTION");
|
||||
std::string descriptor;
|
||||
for (const api::Security* sec : data) {
|
||||
descriptor = security_descriptor(*sec);
|
||||
insert(prefix + descriptor, securitytype_of(*sec));
|
||||
{
|
||||
auto insert_stmt = prepare_query(_self->db, "INSERT INTO security_keys (rowid, descriptor, type) values (?, ?, ?)");
|
||||
for (const api::Security* sec : data) {
|
||||
descriptor = prefix + security_descriptor(*sec);
|
||||
auto type = securitytype_of(*sec);
|
||||
|
||||
sqlite3_bind_int(insert_stmt.get(), 1, sec->id());
|
||||
sqlite3_bind_text(insert_stmt.get(), 2, descriptor.c_str(), descriptor.size(), SQLITE_STATIC);
|
||||
sqlite3_bind_int(insert_stmt.get(), 3, (int)type);
|
||||
sqlite3_step(insert_stmt.get());
|
||||
sqlite3_reset(insert_stmt.get());
|
||||
}
|
||||
}
|
||||
sqlite3_exec_nofail(_self->db.get(), "COMMIT TRANSACTION");
|
||||
}
|
||||
|
||||
@@ -140,6 +140,11 @@ void get_contract_spec(const FixedSecurity& source, api::ContractSpecification&
|
||||
spec.set_price_break(spec.price_break() * spec.price_multiplier());
|
||||
spec.set_low_tick(spec.low_tick() * spec.price_multiplier());
|
||||
spec.set_high_tick(spec.high_tick() * spec.price_multiplier());
|
||||
|
||||
// Tick info
|
||||
spec.set_main_fraction(source.cme_main_fraction);
|
||||
spec.set_sub_fraction(source.cme_sub_fraction);
|
||||
spec.set_price_display_format(source.cme_price_display_format);
|
||||
}
|
||||
|
||||
Security convert_security(const FixedSecurity& security) {
|
||||
@@ -153,7 +158,7 @@ void convert_security(const FixedSecurity& source, Security* presult) {
|
||||
dest.set_id(source.key);
|
||||
dest.set_exchange(source.exchange);
|
||||
dest.set_symbol(source.symbol());
|
||||
|
||||
|
||||
switch(source.exchange) {
|
||||
case Venue::VENUE_GLBX:
|
||||
dest.mutable_glbx()->set_exchange_id(source.numeric_id);
|
||||
@@ -161,9 +166,6 @@ void convert_security(const FixedSecurity& source, Security* presult) {
|
||||
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);
|
||||
break;
|
||||
case Venue::VENUE_BTS:
|
||||
break;
|
||||
@@ -262,9 +264,6 @@ void convert_security(const Security& src, FixedSecurity& dest, bool set_securit
|
||||
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();
|
||||
break;
|
||||
case Security::ExchangeDataCase::kCboe:
|
||||
dest.cboe_class_key = src.cboe().class_key();
|
||||
@@ -279,6 +278,9 @@ void convert_security(const Security& src, FixedSecurity& dest, bool set_securit
|
||||
dest.low_tick = src.contract_spec().low_tick() / src.contract_spec().price_multiplier();
|
||||
dest.high_tick = src.contract_spec().high_tick() / src.contract_spec().price_multiplier();
|
||||
dest.cash_per_point = src.contract_spec().cash_per_point();
|
||||
dest.cme_main_fraction = src.contract_spec().main_fraction();
|
||||
dest.cme_sub_fraction = src.contract_spec().sub_fraction();
|
||||
dest.cme_price_display_format = src.contract_spec().price_display_format();
|
||||
|
||||
switch(securitytype_of(src)) {
|
||||
case SECURITYTYPE_FUTURE: {
|
||||
|
||||
@@ -121,9 +121,6 @@ void record_security_keys(md::SecurityIDRecorder& idrec, const co2::MDReply& rep
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sort by id only actually works if the recording was done on a fresh machine
|
||||
std::sort(data.begin(), data.end(), [](const Security* a, const Security* b) { return a->id() < b->id(); });
|
||||
// We could insert garbage until data[0]->id()
|
||||
idrec.bulk_insert(data, "REPLAY: ");
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -193,10 +193,18 @@ void QBConnection::handle_msg(msgs::QBMsg *msgs)
|
||||
me->intro=op.intro();
|
||||
me->my_id = NodeID(me->intro.node_id());
|
||||
|
||||
if (!me->router.add_neighbour(me->my_id,shared_from_this())) {
|
||||
close_connection("Node is already connected");
|
||||
if (me->router.is_neighbour(me->my_id)) {
|
||||
LOG(QBus,Warn, "Refusing connection from {} to {} reason: {} is already connected" ,
|
||||
me->my_desc , me->remote_desc , me->my_id.value());
|
||||
if (me->socket.is_open())
|
||||
me->socket.close();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
LOG(QBus,Warn, "Accepted connection from {} to {}. {} is now connected." ,
|
||||
me->my_desc , me->remote_desc , me->my_id.value());
|
||||
me->router.add_neighbour(me->my_id,shared_from_this());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -184,6 +184,11 @@ void Router::remove_neighbour(NodeID const&removed_neighbour_id)
|
||||
me->neighbours.erase(removed_neighbour_id);
|
||||
}
|
||||
|
||||
bool Router::is_neighbour(NodeID const&neighbour_id) const
|
||||
{
|
||||
return me->neighbours.find(neighbour_id) != me->neighbours.end();
|
||||
}
|
||||
|
||||
void Router::advertise(NodeID const& sender,TableID const&tid,Advertisement const&ad)
|
||||
{
|
||||
LOG(QBus,Diag, "{} received advertisement from nid: {} tid: {} ad topic: {}",
|
||||
|
||||
@@ -15,7 +15,8 @@ public:
|
||||
NodeID id() const ;
|
||||
bool add_neighbour (NodeID const& neighbour_id, INodePtr const& neighbour);
|
||||
void remove_neighbour (NodeID const& neighbour_id);
|
||||
|
||||
bool is_neighbour(NodeID const&neighbour_id) const;
|
||||
|
||||
// from INode
|
||||
void advertise(NodeID const& sender,TableID const&id,Advertisement const&adv) override;
|
||||
void unadvertise(NodeID const& sender,TableID const&tid) override;
|
||||
|
||||
@@ -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/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
|
||||
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;cmds/Web.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;metrics/metrics.cpp;metrics/system_metrics.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/spreaderLine truncated
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/quickbus/quickbus.h"
|
||||
#include "lib/utils/Dispatcher.h"
|
||||
|
||||
#include "lib/md/SyntheticSecurityService.h"
|
||||
#include "server/sparkd/exchange_sim/ExchangeSim.h"
|
||||
@@ -53,6 +54,11 @@ int Run::execute(std::vector<std::string> const &) {
|
||||
orderman::OrderServiceServer order_service_server(sc, synthetic_service);
|
||||
support::ErrorReportServer error_report_service(sc);
|
||||
admin::AdminServiceServer admin_service(sc);
|
||||
|
||||
auto bg_health = std::make_shared<metrics::IntCounterMetric>("spark_background_thread");
|
||||
utils::DispatcherTimer timer(utils::Dispatcher::background(), 5000, [=]() { bg_health->increment(); });
|
||||
timer.start();
|
||||
stats.publish(bg_health);
|
||||
|
||||
std::thread([]() {
|
||||
os::set_thread_name("ExchangeSim");
|
||||
|
||||
@@ -217,11 +217,22 @@ namespace http {
|
||||
con->set_body(read_file(path.string()));
|
||||
con->set_status(websocketpp::http::status_code::ok);
|
||||
}
|
||||
|
||||
void return_bts_installer(connection_ptr& con) {
|
||||
std::string installer_bits = read_file(_www+"/bts_installer.exe");
|
||||
const std::string installer_config = _www+"install_config.json";
|
||||
if (boost::filesystem::is_regular_file(installer_config)) {
|
||||
installer_bits += read_file(installer_config);
|
||||
auto conf = read_file(installer_config);
|
||||
std::string needle = "$HOST";
|
||||
std::size_t pos;
|
||||
do {
|
||||
pos = conf.find(needle,0);
|
||||
if (pos != std::string::npos) {
|
||||
auto host = con->get_uri()->get_host();
|
||||
conf.replace(pos, needle.size(), host);
|
||||
}
|
||||
} while(pos != std::string::npos);
|
||||
installer_bits += conf;
|
||||
}
|
||||
con->set_body(installer_bits);
|
||||
con->set_status(websocketpp::http::status_code::ok);
|
||||
|
||||
@@ -127,17 +127,6 @@ std::string store_path (std::string base_path,
|
||||
return result.string ();
|
||||
}
|
||||
|
||||
void create_current_simlink (std::string const &path)
|
||||
{
|
||||
auto sym_link_path = boost::filesystem::path (path);
|
||||
sym_link_path /= "..";
|
||||
sym_link_path /= "current";
|
||||
if (boost::filesystem::exists (sym_link_path))
|
||||
boost::filesystem::remove (sym_link_path);
|
||||
|
||||
boost::filesystem::create_symlink (boost::filesystem::path (path),sym_link_path);
|
||||
}
|
||||
|
||||
std::map<uint32_t,InMsgBuffer>
|
||||
load_replay (config::JetFIX::Connection const &cfg,std::string const&path, uint32_t max_sn)
|
||||
{
|
||||
@@ -227,8 +216,6 @@ FIXFileStore::FIXFileStore (config::JetFIX::Connection const&cfg,std::string bas
|
||||
}
|
||||
}
|
||||
me->fix_store.open (fix_log_p,std::ofstream::app);
|
||||
|
||||
create_current_simlink (path);
|
||||
me->writer_thread = std::thread (std::bind (&FIXFileStore::write,this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +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"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
@@ -84,7 +84,8 @@ MDP3Listener::MDP3Listener(
|
||||
MDProducer& producer,
|
||||
SecurityIDRecorder& recorder,
|
||||
co2::SettlementRecorder& ,
|
||||
std::vector<MDProducerChannel> channels) :
|
||||
const std::vector<MDProducerChannel> channels,
|
||||
CMEOptions::BookType bt) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_packet_arrived(),
|
||||
@@ -94,6 +95,7 @@ MDP3Listener::MDP3Listener(
|
||||
_symbol_filter_storage(),
|
||||
_symbol_filter(),
|
||||
_channels(channels),
|
||||
_booktype(bt),
|
||||
_concurrency_monitor(0),
|
||||
_book_updates()
|
||||
{
|
||||
@@ -188,11 +190,6 @@ double double_of_price(const Price& price, int8_t display_factor) {
|
||||
return (price.mantissa() * pow10(price.exponent())) * pow10(display_factor);
|
||||
}
|
||||
|
||||
inline PriceSize pricesize_of_price_size(const onixs::PRICE price, int8_t display_factor, Size size) {
|
||||
PriceSize result(price.mantissa(), price.exponent() + display_factor, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline datetime::LLDateTime ll_of_timestamp(const onixs::Timestamp& ts) {
|
||||
return datetime::ll_from_parts(ts.year(), (int)ts.month(), ts.day(), ts.hour(), ts.minute(), ts.second(), ts.millisecond());
|
||||
}
|
||||
@@ -335,49 +332,18 @@ void MDP3Listener::onDefinition(onixs::Handler&, const onixs::Security&, const o
|
||||
_index.add_spread_legs(fsec.numeric_id, leg_exchange_ids);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Book
|
||||
void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, const onixs::ConsolidatedBook& book) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", 0,_latest_sequence);
|
||||
|
||||
auto data = _index.get_security(sec.id());
|
||||
if (data == nullptr) return;
|
||||
bts_assert(data->id == (uint32_t)sec.id());
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
|
||||
// Put the data into the cache. Flush at the end of the packet.
|
||||
auto key = data->handle.key();
|
||||
auto find_iter = std::find_if(_book_updates.begin(), _book_updates.end(), [key](const BookData& entry) {
|
||||
return key == entry.handle.key();
|
||||
});
|
||||
bool found = find_iter != _book_updates.end();
|
||||
if (!found) {
|
||||
_book_updates.emplace_back(data->handle);
|
||||
}
|
||||
BookData& cache = found ? *find_iter : _book_updates.back();
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = book.bids().size();
|
||||
size_t asks_size = book.offers().size();
|
||||
if (!data->handle.has_depth()) {
|
||||
bids_size = min(bids_size, size_t { 1 });
|
||||
asks_size = min(asks_size, size_t { 1 });
|
||||
}
|
||||
for (size_t x = 0; x < bids_size; ++x) {
|
||||
cache.bids[x] = pricesize_of_price_size(book.bids()[x].price(), display_factor, book.bids()[x].quantity());
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x) {
|
||||
cache.asks[x] = pricesize_of_price_size(book.offers()[x].price(), display_factor, book.offers()[x].quantity());
|
||||
}
|
||||
// NaN out the remaining depth
|
||||
for (size_t x = bids_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.bids[x] = PriceSize::NO_MARKET;
|
||||
}
|
||||
for (size_t x = asks_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.asks[x] = PriceSize::NO_MARKET;
|
||||
void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, const onixs::DirectBook& book) {
|
||||
if (_booktype == CMEOptions::BOOKTYPE_DIRECT) {
|
||||
onBookUpdate(sec, book);
|
||||
}
|
||||
}
|
||||
|
||||
void MDP3Listener::onBookUpdate(onixs::Handler&, const onixs::Security& sec, const onixs::ConsolidatedBook& book) {
|
||||
if (_booktype == CMEOptions::BOOKTYPE_CONSOLIDATED) {
|
||||
onBookUpdate(sec, book);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Trades
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "lib/utils/bts_no_copy.h"
|
||||
#include "lib/md/coconut2.h"
|
||||
#include "CMESecurityIndex.h"
|
||||
#include "server/sparkd/tracing/bts_trace.h"
|
||||
|
||||
#include "onixs.h"
|
||||
#include <tbb/concurrent_queue.h>
|
||||
@@ -13,13 +14,23 @@
|
||||
namespace bts {
|
||||
namespace md {
|
||||
|
||||
inline PriceSize pricesize_of_price_size(const onixs::PRICE price, int8_t display_factor, Size size) {
|
||||
PriceSize result(price.mantissa(), price.exponent() + display_factor, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
class MDP3Listener :
|
||||
public onixs::HandlerListener,
|
||||
public onixs::MarketDataListener,
|
||||
public onixs::SecurityListener
|
||||
{
|
||||
public:
|
||||
MDP3Listener(CMESecurityIndex& index, MDProducer&, SecurityIDRecorder&, co2::SettlementRecorder&, std::vector<MDProducerChannel>);
|
||||
MDP3Listener(CMESecurityIndex& index,
|
||||
MDProducer&,
|
||||
SecurityIDRecorder&,
|
||||
co2::SettlementRecorder&,
|
||||
const std::vector<MDProducerChannel>,
|
||||
CMEOptions::BookType);
|
||||
virtual ~MDP3Listener();
|
||||
|
||||
//
|
||||
@@ -51,10 +62,52 @@ public:
|
||||
//
|
||||
// Book
|
||||
// void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::MboBook&) override;
|
||||
// void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::DirectBook&) override;
|
||||
void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::DirectBook&) override;
|
||||
// void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::ImpliedBook&) override;
|
||||
void onBookUpdate(onixs::Handler&, const onixs::Security&, const onixs::ConsolidatedBook&) override;
|
||||
|
||||
template <class TBook>
|
||||
void onBookUpdate(const onixs::Security& sec, const TBook& book) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", 0,_latest_sequence);
|
||||
|
||||
auto data = _index.get_security(sec.id());
|
||||
if (data == nullptr) return;
|
||||
bts_assert(data->id == (uint32_t)sec.id());
|
||||
auto display_factor = data->handle.security().display_factor;
|
||||
|
||||
// Put the data into the cache. Flush at the end of the packet.
|
||||
auto key = data->handle.key();
|
||||
auto find_iter = std::find_if(_book_updates.begin(), _book_updates.end(), [key](const BookData& entry) {
|
||||
return key == entry.handle.key();
|
||||
});
|
||||
bool found = find_iter != _book_updates.end();
|
||||
if (!found) {
|
||||
_book_updates.emplace_back(data->handle);
|
||||
}
|
||||
BookData& cache = found ? *find_iter : _book_updates.back();
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = book.bids().size();
|
||||
size_t asks_size = book.offers().size();
|
||||
if (!data->handle.has_depth()) {
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
for (size_t x = 0; x < bids_size; ++x) {
|
||||
cache.bids[x] = pricesize_of_price_size(book.bids()[x].price(), display_factor, book.bids()[x].quantity());
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x) {
|
||||
cache.asks[x] = pricesize_of_price_size(book.offers()[x].price(), display_factor, book.offers()[x].quantity());
|
||||
}
|
||||
// NaN out the remaining depth
|
||||
for (size_t x = bids_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.bids[x] = PriceSize::NO_MARKET;
|
||||
}
|
||||
for (size_t x = asks_size; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
cache.asks[x] = PriceSize::NO_MARKET;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Trades
|
||||
void onTrade(onixs::Handler&, const onixs::Security&, const onixs::RecoveryEntryArgs&) override;
|
||||
@@ -125,6 +178,7 @@ private:
|
||||
std::vector<std::string> _symbol_filter_storage;
|
||||
std::set<onixs::StrRef> _symbol_filter;
|
||||
std::vector<MDProducerChannel> _channels;
|
||||
CMEOptions::BookType _booktype;
|
||||
std::atomic<int> _concurrency_monitor;
|
||||
|
||||
struct BookData {
|
||||
|
||||
@@ -213,7 +213,12 @@ unique_ptr<onixs::Handler> MDP3Main::prepare_handler(const string& channel_id) {
|
||||
settings.feeds().connectivityConfigurationFile(config_path());
|
||||
|
||||
// Market data options
|
||||
settings.bookManagement().consolidatedBooks().maintain(true);
|
||||
if (options.book_type() == CMEOptions::BOOKTYPE_CONSOLIDATED) {
|
||||
settings.bookManagement().consolidatedBooks().maintain(true);
|
||||
}
|
||||
else {
|
||||
settings.bookManagement().directBooks().maintain(true);
|
||||
}
|
||||
|
||||
// 10k default is *far* too low
|
||||
settings.recoveryQueueMaxSize(options.recovery_queue_size());
|
||||
@@ -334,12 +339,13 @@ unique_ptr<MDP3Listener> MDP3Main::prepare_listener(onixs::Handler& handler, con
|
||||
producer_channels.push_back(_producer.get_channel(symbol.symbol().c_str(), symbol.type(), symbol.exchange()));
|
||||
}
|
||||
|
||||
unique_ptr<MDP3Listener> listener(new MDP3Listener(_index, _producer, _security_recorder, _settlement_recorder, producer_channels));
|
||||
const auto& options = channel_settings_for(channel.id);
|
||||
|
||||
unique_ptr<MDP3Listener> listener(new MDP3Listener(_index, _producer, _security_recorder, _settlement_recorder, producer_channels, options.book_type()));
|
||||
for (auto& symbol : channel.symbols) {
|
||||
listener->add_filter_symbol(symbol.symbol());
|
||||
}
|
||||
|
||||
const auto& options = channel_settings_for(channel.id);
|
||||
if (options.load_security_db()) {
|
||||
// Load the list of known securities and add them to the index for fast startup
|
||||
db::load_securities(channel.symbols, [&](const api::Security& security) {
|
||||
|
||||
@@ -24,6 +24,7 @@ struct Metric {
|
||||
Metric(const std::string&, const std::vector<Label>&);
|
||||
virtual void print(std::ostream&) const = 0;
|
||||
std::string str() const;
|
||||
virtual ~Metric() {}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
@@ -41,6 +42,7 @@ struct CounterMetric : public Metric {
|
||||
void decrement(T val) { _val -= val; }
|
||||
void set(T val) { _val = val; }
|
||||
};
|
||||
typedef CounterMetric<int> IntCounterMetric;
|
||||
|
||||
template <class T>
|
||||
class AtomicCounterMetric : public Metric {
|
||||
@@ -144,4 +146,5 @@ class MetricsContainer {
|
||||
};
|
||||
|
||||
|
||||
}}//namespace
|
||||
}}//namespace
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "OrderStore.h"
|
||||
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "server/sparkd/persistence/SqliteStore.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "server/sparkd/orderman/events/StoredOrderEvent.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "server/sparkd/orderman/events/OrderStateUpdateEvent.h"
|
||||
#include "server/sparkd/orderman/events/StoredOrderEvent.h"
|
||||
#include "server/sparkd/persistence/SqliteStore.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -13,74 +13,74 @@ namespace orderman {
|
||||
|
||||
static auto const orders_ns = "orders";
|
||||
|
||||
struct OrderStore::PImpl
|
||||
{
|
||||
PImpl() :
|
||||
known_orders(),
|
||||
store(new persistence::SqliteStore("orders")),
|
||||
order_stream()
|
||||
{}
|
||||
struct OrderStore::PImpl {
|
||||
PImpl()
|
||||
: known_orders(), store(new persistence::SqliteStore("orders")),
|
||||
order_stream() {}
|
||||
|
||||
const GatewayName gw_name;
|
||||
std::unordered_map<OrderKey,std::unique_ptr<api::orderman::OrderRecord>> known_orders;
|
||||
std::unordered_map<OrderKey, google::protobuf::Timestamp> known_orders;
|
||||
std::unique_ptr<persistence::IStore> store;
|
||||
mercury::Stream<StoredOrderEvent> order_stream;
|
||||
};
|
||||
|
||||
OrderStore::OrderStore () : me (new PImpl()) {}
|
||||
OrderStore::~OrderStore () {}
|
||||
OrderStore::OrderStore() : me(new PImpl()) {}
|
||||
OrderStore::~OrderStore() {}
|
||||
|
||||
void OrderStore::init()
|
||||
{
|
||||
void OrderStore::init() {
|
||||
api::orderman::OrderRecord rec;
|
||||
std::vector<StoredOrderEvent> stored_batch;
|
||||
|
||||
auto all = me->store->retrieve_all()[orders_ns];
|
||||
for (auto const &kvp : all)
|
||||
if (utils::pb::from_string<api::orderman::OrderRecord> (kvp.second,&rec)) {
|
||||
for (auto const &kvp : all)
|
||||
if (utils::pb::from_string<api::orderman::OrderRecord>(kvp.second,
|
||||
&rec)) {
|
||||
auto ok = OrderKey(std::stoi(kvp.first));
|
||||
auto order_record = std::make_unique<api::orderman::OrderRecord> (rec);
|
||||
stored_batch.emplace_back(*order_record.get());
|
||||
me->known_orders[ok] = std::move(order_record);
|
||||
}
|
||||
else
|
||||
LOG(OrderMan,Error,
|
||||
stored_batch.emplace_back(rec);
|
||||
me->known_orders[ok] = rec.state().created();
|
||||
} else
|
||||
LOG(OrderMan, Error,
|
||||
"Could not recover order {} from storage. "
|
||||
"This should be investigated", kvp.first);
|
||||
"This should be investigated",
|
||||
kvp.first);
|
||||
|
||||
me->order_stream = make_stream<StoredOrderEvent> ();
|
||||
LOG(OrderMan,Info,
|
||||
"Order Store published {} orders recoved from disk", all.size());
|
||||
publish_batch(me->order_stream,stored_batch);
|
||||
subscribe<OrderStateUpdateEvent> ([this] (auto const&ev) {this->on_order_state_update(ev);});
|
||||
me->order_stream = make_stream<StoredOrderEvent>();
|
||||
LOG(OrderMan, Info, "Order Store published {} orders recoved from disk",
|
||||
all.size());
|
||||
publish_batch(me->order_stream, stored_batch);
|
||||
subscribe<OrderStateUpdateEvent>(
|
||||
[this](auto const &ev) { this->on_order_state_update(ev); });
|
||||
}
|
||||
|
||||
void OrderStore::on_order_state_update(mercury::Update<OrderStateUpdateEvent> const&u)
|
||||
{
|
||||
void OrderStore::on_order_state_update(
|
||||
mercury::Update<OrderStateUpdateEvent> const &u) {
|
||||
if (u.events)
|
||||
for (auto const &ev : *u.events) {
|
||||
auto &ptr = me->known_orders[ev.key];
|
||||
auto new_order = !ptr;
|
||||
if (new_order) {
|
||||
ptr.reset (new api::orderman::OrderRecord());
|
||||
*ptr->mutable_def() = *ev.definition;
|
||||
ptr->set_key(ev.key.value());
|
||||
ptr->set_parent_key(ev.parent_key.value());
|
||||
*ptr->mutable_state()->mutable_created() = utils::pb::pb_now();
|
||||
}
|
||||
|
||||
auto created = ptr->state().created();
|
||||
*ptr->mutable_state() = ev.state;
|
||||
*ptr->mutable_state()->mutable_created() = created;
|
||||
*ptr->mutable_state()->mutable_last_change() = utils::pb::pb_now();
|
||||
auto k = std::to_string(ptr->key());
|
||||
auto v = utils::pb::to_string(*ptr);
|
||||
if (new_order)
|
||||
me->store->insert(orders_ns,k,v);
|
||||
else
|
||||
me->store->update(orders_ns,k,v);
|
||||
publish(me->order_stream,*ptr.get());
|
||||
api::orderman::OrderRecord order;
|
||||
order.set_key(ev.key.value());
|
||||
order.set_parent_key(ev.parent_key.value());
|
||||
*order.mutable_def() = *ev.definition;
|
||||
*order.mutable_state() = ev.state;
|
||||
*order.mutable_state()->mutable_last_change() = utils::pb::pb_now();
|
||||
|
||||
auto it = me->known_orders.find(ev.key);
|
||||
if (it == me->known_orders.end()) {
|
||||
// new order
|
||||
auto now = utils::pb::pb_now();
|
||||
me->known_orders[ev.key] = now;
|
||||
*order.mutable_state()->mutable_created() = now;
|
||||
me->store->insert(orders_ns, std::to_string(order.key()),
|
||||
utils::pb::to_string(order));
|
||||
} else {
|
||||
*order.mutable_state()->mutable_created() = it->second;
|
||||
me->store->update(orders_ns, std::to_string(order.key()),
|
||||
utils::pb::to_string(order));
|
||||
}
|
||||
LOG(OrderMan, Diag, "Storing order {}", utils::pb::to_string(order));
|
||||
publish(me->order_stream, order);
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -92,10 +92,10 @@ void ServiceBridge::on(mercury::Update<StoredOrderEvent> const &update) {
|
||||
bool was_ready = is_ready();
|
||||
me->have_orders = true;
|
||||
|
||||
std::vector<api::orderman::OrderRecord const *> orders;
|
||||
std::vector<api::orderman::OrderRecord> orders;
|
||||
orders.reserve(update.events->size());
|
||||
for (auto ev : *(update.events)) {
|
||||
me->known_orders.insert(OrderKey(ev.order_record->key()));
|
||||
me->known_orders.insert(OrderKey(ev.order_record.key()));
|
||||
orders.push_back(ev.order_record);
|
||||
}
|
||||
me->on_orders(orders);
|
||||
|
||||
@@ -39,8 +39,8 @@ public:
|
||||
template <class EV> using OnEv = std::function<void(EV const&)>;
|
||||
|
||||
using OnReady = std::function<void()>;
|
||||
using OnOrders = std::function<void (std::vector<api::orderman::OrderRecord const*> )>;
|
||||
using OnFills = std::function<void (std::vector<api::orderman::Fill const*> )>;
|
||||
using OnOrders = std::function<void (std::vector<api::orderman::OrderRecord> const& )>;
|
||||
using OnFills = std::function<void (std::vector<api::orderman::Fill const*> const&)>;
|
||||
|
||||
ServiceBridge(OnReady const&, OnOrders const&, OnFills const&);
|
||||
void init() override;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderServicesData.h"
|
||||
#include "server/sparkd/orderman/events/NewAdminDataEvent.h"
|
||||
#include "server/sparkd/orderman/events/OrderManagerReadyEvent.h"
|
||||
#include "server/sparkd/orderman/events/StoredOrderEvent.h"
|
||||
#include "server/sparkd/orderman/order_factory/OrderFactory.h"
|
||||
#include "server/sparkd/orderman/orders/Order.h"
|
||||
#include "server/sparkd/securities/ISecurityDB.h"
|
||||
@@ -96,8 +97,8 @@ void OrderManager::on_order_recovery(
|
||||
// recover orders from disk
|
||||
for (auto const &ev : *(update.events)) {
|
||||
auto &order =
|
||||
me->services_data.order_factory.recover(*ev.order_record);
|
||||
auto const &ostate = ev.order_record->state();
|
||||
me->services_data.order_factory.recover(ev.order_record);
|
||||
auto const &ostate = ev.order_record.state();
|
||||
OrderInfo oi{order, me->services_data};
|
||||
auto serv_ptr = oi.order_services.get();
|
||||
me->orders[order.key()] = std::move(oi);
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "server/sparkd/orderman/events/NewSecuritiesEvent.h"
|
||||
#include "server/sparkd/orderman/events/OrderStateUpdateEvent.h"
|
||||
#include "server/sparkd/orderman/events/OutFIXMsgEvent.h"
|
||||
#include "server/sparkd/orderman/events/StoredOrderEvent.h"
|
||||
#include "server/sparkd/orderman/events/UpdateOrderEvent.h"
|
||||
#include "server/sparkd/orderman/limiter/Limiter.h"
|
||||
#include "server/sparkd/orderman/order_router/OrderRouter.h"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "server/sparkd/mercury/synopsis/MergeByKey.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "server/sparkd/mercury/events/Event.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
@@ -14,10 +15,10 @@ class StoredOrderEvent : public mercury::Event <StoredOrderEvent>
|
||||
public:
|
||||
using SynopsisPolicy = mercury::MergeByKey<StoredOrderEvent>;
|
||||
|
||||
StoredOrderEvent(api::orderman::OrderRecord &record) : order_record(&record) {}
|
||||
StoredOrderEvent(api::orderman::OrderRecord const &record) : order_record(record) {}
|
||||
|
||||
auto key() const {return order_record->key();}
|
||||
api::orderman::OrderRecord *order_record;
|
||||
auto key() const {return order_record.key();}
|
||||
api::orderman::OrderRecord order_record;
|
||||
};
|
||||
|
||||
}}
|
||||
@@ -88,7 +88,8 @@ void IcebergOrder::on_init_recovered(api::orderman::OrderRecord const &rec,
|
||||
}
|
||||
}
|
||||
|
||||
void IcebergOrder::on_activate(Price p, Quantity q, OrderServices &services, TickToTradeCorID) {
|
||||
void IcebergOrder::on_activate(Price p, Quantity q, OrderServices &services,
|
||||
TickToTradeCorID) {
|
||||
bts_assert(!me->services);
|
||||
me->services = &services;
|
||||
me->child_builder.activate(services);
|
||||
@@ -184,6 +185,7 @@ void IcebergOrder::transition(api::orderman::OrderStatus new_status,
|
||||
auto const &to = OrderStatus_Name(new_status);
|
||||
LOG(OrderMan, Info, "Transitioning iceberg order {} from {} to {}. Msg: {}",
|
||||
key().value(), from, to, msg);
|
||||
me->state.set_last_msg(msg);
|
||||
me->state.set_status(new_status);
|
||||
}
|
||||
|
||||
@@ -229,8 +231,9 @@ void IcebergOrder::send_next_child_order() {
|
||||
auto remaining = me->state.quantity() - me->state.filled_quantity();
|
||||
auto to_send = std::min(remaining, me->show_quantity.value());
|
||||
me->child.order = me->child_builder.build();
|
||||
LOG(OrderMan, Info, "Iceberg order {} creating new child order {} with size "
|
||||
"{} price {}. Filled {} so far out of {}",
|
||||
LOG(OrderMan, Info,
|
||||
"Iceberg order {} creating new child order {} with size "
|
||||
"{} price {}. Filled {} so far out of {}",
|
||||
key().value(), me->child.order.key().value(), to_send, me->state.price(),
|
||||
me->state.filled_quantity(), me->state.quantity());
|
||||
|
||||
@@ -251,14 +254,20 @@ void IcebergOrder::on_child_order_update(OrderStateUpdateEvent const &ev) {
|
||||
break;
|
||||
|
||||
case ORDER_STATUS_COMPLETED: {
|
||||
api::orderman::OrderState merged_state;
|
||||
merge_states(me->state, me->child.state, &merged_state);
|
||||
me->state = merged_state;
|
||||
me->child.clear();
|
||||
if (me->state.quantity() > me->state.filled_quantity())
|
||||
send_next_child_order();
|
||||
else
|
||||
transition(ORDER_STATUS_COMPLETED);
|
||||
if (me->child.state.filled_quantity() > 0) {
|
||||
api::orderman::OrderState merged_state;
|
||||
merge_states(me->state, me->child.state, &merged_state);
|
||||
me->state = merged_state;
|
||||
me->child.clear();
|
||||
if (me->state.quantity() > me->state.filled_quantity())
|
||||
send_next_child_order();
|
||||
else
|
||||
transition(ORDER_STATUS_COMPLETED);
|
||||
} else {
|
||||
me->child.clear();
|
||||
transition(ORDER_STATUS_ERROR,
|
||||
"Child order completed without fills");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -302,5 +311,5 @@ void IcebergOrder::on_child_order_update(OrderStateUpdateEvent const &ev) {
|
||||
}
|
||||
report_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -126,9 +126,10 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
|
||||
|
||||
// bail if we can't find the security for the order
|
||||
if (!me->sec) {
|
||||
LOG(OrderMan, Error, "Unknown security {} referenced by recovered "
|
||||
"order {}. This is a serious "
|
||||
"problem which has to be corrected manually. ",
|
||||
LOG(OrderMan, Error,
|
||||
"Unknown security {} referenced by recovered "
|
||||
"order {}. This is a serious "
|
||||
"problem which has to be corrected manually. ",
|
||||
def.security_key(), rec.key());
|
||||
transition_to_error(
|
||||
NativeOrderState::SUB_ERROR_UNKNOWN_SECURITY,
|
||||
@@ -290,8 +291,9 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
|
||||
// is our gateway connected?
|
||||
if (!me->session_services->is_gateway_connected()) {
|
||||
LOG(OrderMan, Warn, "Attempt to send order to a disconnected gateway. "
|
||||
"Dropping order {}",
|
||||
LOG(OrderMan, Warn,
|
||||
"Attempt to send order to a disconnected gateway. "
|
||||
"Dropping order {}",
|
||||
key().value());
|
||||
transition(ORDER_STATUS_REJECTED_BY_SPARK, "Exchange gateway is down");
|
||||
break;
|
||||
@@ -302,7 +304,8 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
UserID(me->definition.user_id()),
|
||||
AccountID(me->definition.account_id()));
|
||||
if (!lqr.ok) {
|
||||
LOG(OrderMan, Warn, "Unauthorized order attempted. User: {} on "
|
||||
LOG(OrderMan, Warn,
|
||||
"Unauthorized order attempted. User: {} on "
|
||||
"Account: {}. Msg: {} Order: {}. Dropping order",
|
||||
me->definition.user(), me->definition.account(), lqr.error_msg,
|
||||
key().value());
|
||||
@@ -318,8 +321,9 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
me->definition.side(), q);
|
||||
|
||||
if (!lqr.ok) {
|
||||
LOG(OrderMan, Warn, "New order would breach limit. User: {} Account: "
|
||||
"{} Msg: {} Order: {}. Dropping order",
|
||||
LOG(OrderMan, Warn,
|
||||
"New order would breach limit. User: {} Account: "
|
||||
"{} Msg: {} Order: {}. Dropping order",
|
||||
me->definition.user(), me->definition.account(), lqr.error_msg,
|
||||
key().value());
|
||||
|
||||
@@ -330,8 +334,9 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
|
||||
// are we under the messaging threshold?
|
||||
if (!me->session_services->governor().can_send()) {
|
||||
LOG(OrderMan, Warn, "New order would cause breach of messaging "
|
||||
"limits. Dropping order {}",
|
||||
LOG(OrderMan, Warn,
|
||||
"New order would cause breach of messaging "
|
||||
"limits. Dropping order {}",
|
||||
key().value());
|
||||
transition(ORDER_STATUS_REJECTED_BY_SPARK,
|
||||
"Messaging threshold breached");
|
||||
@@ -747,8 +752,9 @@ void NativeOrder::report_state() {
|
||||
me->definition.side(),
|
||||
Quantity(previous_position.value() - current_position.value()));
|
||||
} else
|
||||
LOG(OrderMan, Error, "Order {} ignored for purposes of limit enforcing "
|
||||
"because it references unknown security {} ",
|
||||
LOG(OrderMan, Error,
|
||||
"Order {} ignored for purposes of limit enforcing "
|
||||
"because it references unknown security {} ",
|
||||
key().value(), me->definition.security_key());
|
||||
|
||||
me->services->report_state(me->state);
|
||||
@@ -845,8 +851,10 @@ void NativeOrder::init_fix() {
|
||||
auto stop_px = def.custom_def().stop().stop_price();
|
||||
auto encoded_stop_px =
|
||||
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px,
|
||||
me->price_encoding_precision, -1);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px,
|
||||
me->price_encoding_precision, -1);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -860,8 +868,10 @@ void NativeOrder::init_fix() {
|
||||
auto stop_px = def.custom_def().stop_limit().stop_price();
|
||||
auto encoded_stop_px =
|
||||
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px,
|
||||
me->price_encoding_precision, -1);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px,
|
||||
me->price_encoding_precision, -1);
|
||||
|
||||
// price
|
||||
me->create_price_bm = create_encoding.add<fix::Price>(
|
||||
@@ -1021,5 +1031,5 @@ NativeOrder::get_native_state(api::orderman::OrderState &st) {
|
||||
bts_assert(false);
|
||||
return *st.mutable_stop_limit()->mutable_native_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -53,7 +53,7 @@ void SpreaderOrder::on_activate(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());
|
||||
LOG(OrderMan, Info, "SpreaderOrder.on_update key: {} price: {} quantity: {}", key().value(), price.value(), quantity.value());
|
||||
_state_worker->on_update(price, quantity);
|
||||
_report_state();
|
||||
}
|
||||
@@ -242,45 +242,50 @@ 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){
|
||||
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,
|
||||
hedge_price, hedge_quantity);
|
||||
SingleFillHedger& hedger = _bookkeeping_data().add_new_hedger (*this,
|
||||
void SpreaderOrder::_send_hedge(size_t hedge_leg_index,
|
||||
Price hedge_price,
|
||||
Quantity hedge_qty){
|
||||
LOG(OrderMan, Info, "SpreaderOrder::_send_hedge key: {}, hedge_leg_index: {} hedge_price: {} hedge_qty: {}",
|
||||
key().value(), hedge_leg_index, hedge_price, hedge_qty);
|
||||
SingleFillHedger& hedger = _bookkeeping_data().add_new_hedger (*this,
|
||||
hedge_leg_index,
|
||||
hedge_price,
|
||||
hedge_quantity);
|
||||
hedge_qty);
|
||||
hedger.init ();
|
||||
}
|
||||
void SpreaderOrder::_compute_hedge_price_quantity(size_t filled_leg_index,
|
||||
Price fill_price, Quantity fill_quantity,
|
||||
Price fill_price,
|
||||
double fill_quantity,
|
||||
size_t hedge_leg_index,
|
||||
Price& hedge_price, Quantity& hedge_quantity){
|
||||
Price& hedge_price,
|
||||
double& 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);
|
||||
filled_leg_data.leg_property_data(),
|
||||
fill_price,
|
||||
fill_quantity,
|
||||
hedged_leg_data.leg_property_data(),
|
||||
computed_hedge_price,
|
||||
hedge_quantity);
|
||||
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);
|
||||
const QuoteLegDriver& driver_data = filled_leg_data.quoter().driver_data(fill_price, 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(),
|
||||
computed_hedge_price, hedge_quantity);
|
||||
Price(computed_hedge_price),
|
||||
Quantity(hedge_quantity));
|
||||
}
|
||||
void SpreaderOrder::_report_state (){
|
||||
if (_activated()){
|
||||
|
||||
@@ -133,10 +133,11 @@ private:
|
||||
void _transform_to_END(std::string* m = nullptr);
|
||||
// swtches to the state passed in the arg
|
||||
void _switch_state (ISpreaderStateWorker* new_state);
|
||||
// this means on_init_new has been called and we have the
|
||||
// the structure built
|
||||
// misc ////////////////////
|
||||
////////////////////////////
|
||||
// this means on_init_new or on_init_recovered
|
||||
// has been called and we have the bookkeeping
|
||||
// data built
|
||||
bool _inited () const {
|
||||
return _bookkeeping_data_ptr != nullptr;
|
||||
}
|
||||
@@ -165,17 +166,15 @@ private:
|
||||
// on leg 'leg_to_hedge_index' in response to a fill
|
||||
// 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);
|
||||
// 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'
|
||||
// with price 'filled_price' and quantity 'filled_quantity'
|
||||
void _send_hedge(size_t hedge_leg_index,
|
||||
Price hedge_price,
|
||||
Quantity hedge_qty);
|
||||
void _compute_hedge_price_quantity(size_t filled_leg_index,
|
||||
Price filled_price, Quantity filled_quantity,
|
||||
Price filled_price,
|
||||
double filled_quantity,
|
||||
size_t hedged_leg_index,
|
||||
Price& hedge_price, Quantity& hedge_quantity);
|
||||
Price& hedge_price,
|
||||
double& hedge_quantity);
|
||||
// sends the report out
|
||||
void _report_state ();
|
||||
SpreaderBookkeepingData& _bookkeeping_data(){
|
||||
|
||||
@@ -20,6 +20,7 @@ _spreader(spreader),
|
||||
_this_leg_data (this_leg_property),
|
||||
_n_spreader_legs (n_spreader_legs),
|
||||
_child_order_state (child_order_state),
|
||||
_driver_cache(n_spreader_legs,n_spreader_legs>2?10:0,this_leg_property.tick_size()),
|
||||
_price_quantity_state(Price(0), Quantity(0), child_order_state),
|
||||
_is_permenantly_cancelled (false),
|
||||
_worker(nullptr),
|
||||
@@ -33,8 +34,7 @@ _end_state_worker(this){
|
||||
if (_active_quoting){
|
||||
if (!_two_legged){
|
||||
for (size_t q_idx = 0; q_idx < _n_spreader_legs; q_idx++){
|
||||
_drivers_commited.push_back (QuoterLegDriverData());
|
||||
_drivers_temp_usage.push_back (QuoterLegDriverData());
|
||||
_drivers_temp_usage.push_back (QuoteLegDriver());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ _this_leg_data (other._this_leg_data),
|
||||
_n_spreader_legs (other._n_spreader_legs),
|
||||
_child_order_state (other._child_order_state),
|
||||
_drivers_temp_usage(other._drivers_temp_usage),
|
||||
_drivers_commited (other._drivers_commited),
|
||||
_driver_cache(other._driver_cache),
|
||||
_price_quantity_state(other._price_quantity_state.target_spread_price(),
|
||||
other._price_quantity_state.target_leg_quantity(),
|
||||
other._price_quantity_state.order_state()),
|
||||
@@ -131,13 +131,8 @@ bool SingleLegQuoter::is_terminal_state () const {
|
||||
bool SingleLegQuoter::accept_modifications() const {
|
||||
return _worker->state() == QuoterState::WAITING_FOR_SIZE || _worker->state() == QuoterState::WORKING;
|
||||
}
|
||||
Price SingleLegQuoter::driver_price (size_t leg_idx) const{
|
||||
bts_assert (leg_idx < _n_spreader_legs);
|
||||
return _drivers_commited[leg_idx].driver_price;
|
||||
}
|
||||
void SingleLegQuoter::_incorporate_other_leg_fill (size_t leg_index, Quantity filled_quantity){
|
||||
Quantity this_leg_reduction = SpreaderUtils::get_leg_quantity_from_another_leg_quantity (_spreader.get_leg_data(leg_index), filled_quantity, _this_leg_data);
|
||||
_price_quantity_state.add_hedged_reduction (this_leg_reduction);
|
||||
void SingleLegQuoter::_incorporate_other_leg_fill (size_t, Quantity hedge_reduction){
|
||||
_price_quantity_state.add_hedged_reduction (hedge_reduction);
|
||||
}
|
||||
void SingleLegQuoter::_compute_price_quantity(Price& price, Quantity& quantity){
|
||||
Quantity remaining_quantity = _price_quantity_state.remaining_to_fill();
|
||||
@@ -152,11 +147,9 @@ void SingleLegQuoter::_compute_price_quantity(Price& price, Quantity& quantity){
|
||||
SpreaderUtils::compute_quoting_price_size_n_legs (_price_quantity_state.target_spread_price(), remaining_quantity,
|
||||
_this_leg_data.leg_index(), _spreader, _drivers_temp_usage, price, quantity);
|
||||
}
|
||||
void SingleLegQuoter::_commit_drivers(){
|
||||
if (!_two_legged)
|
||||
// this could be done with a pointer swap too
|
||||
for (size_t idx = 0; idx < _n_spreader_legs; idx++)
|
||||
_drivers_commited[idx] = _drivers_temp_usage[idx];
|
||||
void SingleLegQuoter::_commit_drivers(Price quote_price){
|
||||
if (!_two_legged)
|
||||
_driver_cache.add(_drivers_temp_usage,quote_price);
|
||||
}
|
||||
/*********************************************************************************/
|
||||
/*******************************state transformations*****************************/
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForCancelAckStateWorker.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWorkingStateWorker.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterEndStateWorker.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoterLegDriverData.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoteLegDriver.h"
|
||||
#include "server/sparkd/orderman/actors/order_manager/ChildOrder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderChildOrderState.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoteDriverSetHistory.h"
|
||||
#include <sstream>
|
||||
|
||||
|
||||
@@ -24,7 +25,6 @@ namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class ISpreaderManager;
|
||||
//class SpreaderChildOrderState;
|
||||
class SingleLegQuoter
|
||||
{
|
||||
friend QuoterStartStateWorker;
|
||||
@@ -45,8 +45,8 @@ private:
|
||||
// legs requires auxiliary storage for drivers, which
|
||||
// _drivers_temp_usage provides. once we are ready
|
||||
// to send the orders, the drivers are committed to
|
||||
// _drivers_commited
|
||||
std::vector<QuoterLegDriverData> _drivers_temp_usage, _drivers_commited;
|
||||
std::vector<QuoteLegDriver> _drivers_temp_usage;
|
||||
QuoteDriverSetHistory _driver_cache;
|
||||
QuoteLegPriceQuantityState _price_quantity_state;
|
||||
bool _is_permenantly_cancelled;
|
||||
// state workers
|
||||
@@ -108,13 +108,10 @@ public:
|
||||
// the quoter returns true if it is in a state where orders
|
||||
// can be modified
|
||||
bool accept_modifications() const;
|
||||
// for a given leg, returns the driver price that was used
|
||||
// to price the current order
|
||||
Price driver_price (size_t leg_idx) const;
|
||||
const QuoterLegDriverData& driver_data (size_t leg_idx) const
|
||||
{
|
||||
bts_assert (leg_idx < _drivers_commited.size());
|
||||
return _drivers_commited[leg_idx];
|
||||
const QuoteLegDriver& driver_data (Price quote_price, size_t leg_idx) const{
|
||||
const QuoteLegDriver* leg_driver = _driver_cache.find_driver(quote_price,leg_idx);
|
||||
bts_assert(leg_driver!=nullptr);
|
||||
return *leg_driver;
|
||||
}
|
||||
// tells if it is expecting to get a cancel (this helps catch
|
||||
// unsolicited cancels). returns true is its expecting a cancel
|
||||
@@ -152,12 +149,12 @@ private:
|
||||
}
|
||||
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);
|
||||
void _incorporate_other_leg_fill (size_t leg_index, Quantity hedge_reduction);
|
||||
// this is the function that computes the quoting price and the
|
||||
// quantity for this leg.
|
||||
void _compute_price_quantity(Price& price, Quantity& quantity);
|
||||
// copies the computed driver to committed dri
|
||||
void _commit_drivers ();
|
||||
void _commit_drivers (Price quote_price);
|
||||
void _transform_to_START ();
|
||||
void _transform_to_WAITING_FOR_SIZE ();
|
||||
void _transform_to_WAITING_FOR_FIRST_ACK ();
|
||||
|
||||
+10
-3
@@ -23,6 +23,7 @@ _reported_state(){
|
||||
_quoter_child_order_states.reserve(_n_legs);
|
||||
_quoters.reserve (_n_legs);
|
||||
_hedgers.reserve (_n_legs);
|
||||
_unhedged_deltas.reserve(_n_legs);
|
||||
_leg_data.reserve (_n_legs);
|
||||
for (const auto& lpd_iter : legs){
|
||||
_leg_prop_data.push_back(lpd_iter);
|
||||
@@ -34,9 +35,15 @@ _reported_state(){
|
||||
_quoter_child_order_states.emplace_back (empty_order,leg_property_data.leg_security_key(),_is_live,true);
|
||||
_quoters.emplace_back(spreader_manager, leg_property_data, _n_legs, _quoter_child_order_states.back());
|
||||
_hedgers.emplace_back ();
|
||||
_leg_data.emplace_back(leg_property_data, _quoters.back(), _quoter_order_builders.back(),
|
||||
_hedger_order_builders.back(), _hedgers.back (), _quoter_child_order_states.back(),
|
||||
_books.back());
|
||||
_unhedged_deltas.emplace_back();
|
||||
_leg_data.emplace_back(leg_property_data,
|
||||
_quoters.back(),
|
||||
_quoter_order_builders.back(),
|
||||
_hedger_order_builders.back(),
|
||||
_hedgers.back (),
|
||||
_quoter_child_order_states.back(),
|
||||
_books.back(),
|
||||
_unhedged_deltas.back());
|
||||
bts_assert (_sec_key_to_index.find(leg_property_data.security_key()) == _sec_key_to_index.end());
|
||||
_sec_key_to_index.insert(std::pair<uint32_t, size_t>(leg_property_data.security_key(), leg_property_data.leg_index()));
|
||||
}
|
||||
|
||||
+3
-1
@@ -8,6 +8,7 @@
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/PreActivationOrderServices.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/hedger/UnhedgedDelta.h"
|
||||
|
||||
namespace bts{
|
||||
namespace orderman {
|
||||
@@ -32,7 +33,8 @@ private:
|
||||
std::vector<SpreaderChildOrderState> _quoter_child_order_states;
|
||||
std::vector<SingleLegQuoter> _quoters;
|
||||
std::vector<std::vector<SingleFillHedger>> _hedgers;
|
||||
std::vector<SpreaderLegData> _leg_data;
|
||||
std::vector<UnhedgedDelta> _unhedged_deltas;
|
||||
std::vector<SpreaderLegData> _leg_data;
|
||||
boost::container::flat_map<uint32_t, size_t> _sec_key_to_index;
|
||||
// state thats sent out in report (its kept here amd modified
|
||||
// vs creating a new one everytime)
|
||||
|
||||
@@ -9,7 +9,8 @@ SpreaderLegData::SpreaderLegData (const LegPropertyData& leg_property_data,
|
||||
SpreaderOrderBuilder& hedger_order_builder,
|
||||
std::vector<SingleFillHedger>& hedger_data_vector,
|
||||
SpreaderChildOrderState& quoter_child_order_state,
|
||||
Book& book)
|
||||
Book& book,
|
||||
UnhedgedDelta& unhedged_delta)
|
||||
:
|
||||
_leg_property_data (leg_property_data),
|
||||
_quoter(quoter),
|
||||
@@ -17,7 +18,8 @@ _quoter_order_builder (quoter_order_builder),
|
||||
_hedger_order_builder (hedger_order_builder),
|
||||
_quoter_child_order_state (quoter_child_order_state),
|
||||
_hedger_data_vector (hedger_data_vector),
|
||||
_book(book)
|
||||
_book(book),
|
||||
_unhedged_delta(unhedged_delta)
|
||||
{}
|
||||
|
||||
SpreaderLegData::SpreaderLegData(SpreaderLegData&& other)
|
||||
@@ -28,7 +30,8 @@ _quoter_order_builder(other._quoter_order_builder),
|
||||
_hedger_order_builder(other._hedger_order_builder),
|
||||
_quoter_child_order_state (other._quoter_child_order_state),
|
||||
_hedger_data_vector (other._hedger_data_vector),
|
||||
_book(other._book){
|
||||
_book(other._book),
|
||||
_unhedged_delta(other._unhedged_delta){
|
||||
bts_assert (false);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "SpreaderChildOrderState.h"
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/hedger/UnhedgedDelta.h"
|
||||
|
||||
namespace bts{
|
||||
namespace orderman{
|
||||
@@ -28,6 +29,7 @@ private:
|
||||
SpreaderChildOrderState& _quoter_child_order_state;
|
||||
std::vector<SingleFillHedger>& _hedger_data_vector;
|
||||
Book& _book;
|
||||
UnhedgedDelta& _unhedged_delta;
|
||||
public:
|
||||
SpreaderLegData (const LegPropertyData& leg_property_data,
|
||||
SingleLegQuoter& quoter,
|
||||
@@ -35,7 +37,8 @@ public:
|
||||
SpreaderOrderBuilder& hedger_order_builder,
|
||||
std::vector<SingleFillHedger>& hedger_data_vector,
|
||||
SpreaderChildOrderState& quoter_child_order_state,
|
||||
Book& book);
|
||||
Book& book,
|
||||
UnhedgedDelta& unhedged_delta);
|
||||
SpreaderLegData() = delete;
|
||||
SpreaderLegData(const SpreaderLegData&) = delete;
|
||||
// this cons is not deleted since for each leg,
|
||||
@@ -51,12 +54,13 @@ public:
|
||||
/////////////////////////////////
|
||||
const LegPropertyData& leg_property_data() const {return _leg_property_data;}
|
||||
SingleLegQuoter& quoter() {return _quoter;}
|
||||
const bts::orderman::Book& book () const {return _book;}
|
||||
const bts::orderman::Book& book () const {return _book;}
|
||||
SpreaderChildOrderState& order_update_message () {return _quoter_child_order_state;}
|
||||
bts::orderman::OrderKey key() {return _quoter_child_order_state.order_key();}
|
||||
bts::orderman::OrderKey order_key() {return _quoter_child_order_state.order_key();}
|
||||
SpreaderOrderBuilder& quoter_order_builder() {return _quoter_order_builder;}
|
||||
SpreaderOrderBuilder& hedger_order_builder() {return _hedger_order_builder;}
|
||||
UnhedgedDelta& unhedged_delta() {return _unhedged_delta;}
|
||||
// returns the number of active hedgers
|
||||
size_t n_hedgers() const { return _hedger_data_vector.size(); }
|
||||
// returns the hedger at a given index
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "server/sparkd/orderman/orderman_utils/Book.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/ISpreaderManager.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoterLegDriverData.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoteLegDriver.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
|
||||
|
||||
@@ -423,17 +423,28 @@ public:
|
||||
// so if we get an N lot fill in l2, we send a hedge on l1
|
||||
// sized s1 * N / s2
|
||||
////////////////////////general functions
|
||||
static double get_leg_quantity_from_another_leg_quantity (const LegPropertyData& source_leg,
|
||||
double source_leg_quantity,
|
||||
const LegPropertyData& target_leg){
|
||||
return std::abs(target_leg.size_multiplier () * source_leg_quantity / source_leg.size_multiplier ());
|
||||
}
|
||||
static Quantity get_leg_quantity_from_another_leg_quantity (const LegPropertyData& source_leg,
|
||||
Quantity source_leg_quantity,
|
||||
const LegPropertyData& target_leg){
|
||||
double rv = std::abs(target_leg.size_multiplier () * source_leg_quantity.value () / source_leg.size_multiplier ());
|
||||
double rv = get_leg_quantity_from_another_leg_quantity(source_leg,
|
||||
static_cast<double>(source_leg_quantity.value()),
|
||||
target_leg);
|
||||
return Quantity(_round_leg_qty_computed_from_another_leg_qty(rv));
|
||||
}
|
||||
}
|
||||
// given the quantity of the spreader order, determines the quantity
|
||||
// of the leg
|
||||
static double get_leg_quantity_from_spreader_quantity(const LegPropertyData& leg,
|
||||
double spreader_order_quantity){
|
||||
return std::abs (leg.size_multiplier () * spreader_order_quantity);
|
||||
}
|
||||
static Quantity get_leg_quantity_from_spreader_quantity(const LegPropertyData& leg,
|
||||
Quantity spreader_order_quantity){
|
||||
double rv = std::abs (leg.size_multiplier () * spreader_order_quantity.value());
|
||||
double rv = get_leg_quantity_from_spreader_quantity(leg,static_cast<double>(spreader_order_quantity.value()));
|
||||
return Quantity (_round_leg_qty_computed_from_spreader_qty (rv));
|
||||
}
|
||||
// finds the average weighted price and total quantity
|
||||
@@ -450,53 +461,71 @@ public:
|
||||
out_avg_price = Price((base_price.value() * base_qty.value() + inc_price.value() * inc_qty.value())/out_total_qty.value());
|
||||
}
|
||||
////////////////////////2 legged spreads
|
||||
// given a quoting target qty, finds the "driver" price
|
||||
// and qty, thats needed to hedge the quote. It also
|
||||
// finds the ammended quoting target (since the hedge
|
||||
// book might not have enough to support the entire
|
||||
// quoting target qty)
|
||||
static void compute_driver(Quantity quote_target,
|
||||
const LegPropertyData& quote_leg_data,
|
||||
const LegPropertyData& hedge_leg_data,
|
||||
const bts::orderman::Book& hedge_leg_book,
|
||||
Price& out_hedge_price,
|
||||
Quantity& out_hedge_quantity,
|
||||
Quantity& out_quote_target){
|
||||
// effective_mult is the number of contracts needed to
|
||||
// hedge one quoting contract
|
||||
double effective_mult = std::abs(hedge_leg_data.leg_params().quote_params().lean_quantity_ratio() * hedge_leg_data.size_multiplier() / quote_leg_data.size_multiplier());
|
||||
uint32_t hedge_requirement = _round_hedge_target (effective_mult * quote_target.value()); // rounding hedging up
|
||||
SpreaderUtils::traverse_depth_for_quantity(hedge_leg_book,
|
||||
SpreaderUtils::invert_side (hedge_leg_data.trading_side()),
|
||||
Quantity(hedge_requirement),
|
||||
out_hedge_price,
|
||||
out_hedge_quantity);
|
||||
// when we floor the quoting target (ie _round_quoting_qty)
|
||||
// we are essentially demanding less in terms of hedging, ex :
|
||||
// consider the hedge book depth with qty's 5, 10, 5, 20, 10 (5 is the inside)
|
||||
// and hedge ratio 20 and target qty = 5, so effectively target = 100,
|
||||
// we get 50 at the last level, out_qty = _round_quoting_qty (50/20) = 2,
|
||||
// which means we need 2 * 20 and should re-evaluate
|
||||
uint32_t out_quote_target_uint = _round_quoting_qty (out_hedge_quantity.value() / effective_mult); // rounding quoting down
|
||||
out_quote_target_uint = std::min(out_quote_target_uint,quote_target.value());
|
||||
out_quote_target = Quantity (out_quote_target_uint);
|
||||
}
|
||||
// calculates the computed price and quantity for a quoting
|
||||
// leg given the data for the other leg
|
||||
static void compute_price_quantity_2_legs(Price target_spread_price,
|
||||
Quantity this_leg_target_quantity,
|
||||
const LegPropertyData& this_leg_data,
|
||||
const LegPropertyData& other_leg_data,
|
||||
const bts::orderman::Book& other_leg_book,
|
||||
Price& price, Quantity& quantity){
|
||||
// other_leg_price and other_leg_quantity are the price and quantity calculated
|
||||
// from traversing the other book, for the requires size
|
||||
Price other_leg_price; Quantity other_leg_quantity;
|
||||
// this is the size ratio of the other leg vs this leg
|
||||
double effective_mult = std::abs(other_leg_data.leg_params().quote_params().lean_quantity_ratio() * other_leg_data.size_multiplier() / this_leg_data.size_multiplier());
|
||||
// to be conservative round the required size up
|
||||
uint32_t other_leg_target_quantity = _round_hedge_target (effective_mult * this_leg_target_quantity.value());
|
||||
SpreaderUtils::traverse_depth_for_quantity(other_leg_book, SpreaderUtils::invert_side (other_leg_data.trading_side()), Quantity(other_leg_target_quantity), other_leg_price, other_leg_quantity);
|
||||
// when we floor we essentially go back up the hedging ladder
|
||||
// so we should re-evaluate
|
||||
// ex, consider the hedge book depth with qty's 5, 10, 5, 20, 10 (5 is the inside)
|
||||
// and hedge ratio 20 and target qty = 5, so effectively target = 100,
|
||||
// we get 50 at the last level, out_qty = floor (50/20) = 2, so the
|
||||
// price should not be the last level but the second last level
|
||||
uint32_t out_quantity = _round_quoting_qty (other_leg_quantity.value() / effective_mult);
|
||||
quantity = Quantity (out_quantity);
|
||||
double out_price = (target_spread_price.value() - other_leg_price.value() * other_leg_data.price_multiplier())/this_leg_data.price_multiplier();
|
||||
out_price = bts::securities::raw_round_to_quote_price (out_price, this_leg_data.tick_size(), this_leg_data.trading_side());
|
||||
price = Price(out_price);
|
||||
Quantity quote_target,
|
||||
const LegPropertyData& quote_leg_data,
|
||||
const LegPropertyData& hedge_leg_data,
|
||||
const bts::orderman::Book& hedge_leg_book,
|
||||
Price& quote_price,
|
||||
Quantity& quote_qty){
|
||||
Price hedge_price;
|
||||
Quantity hedge_qty;
|
||||
compute_driver(quote_target,quote_leg_data,hedge_leg_data,hedge_leg_book,
|
||||
hedge_price,hedge_qty,quote_qty);
|
||||
double out_price = (target_spread_price.value() - hedge_price.value() * hedge_leg_data.price_multiplier())/quote_leg_data.price_multiplier();
|
||||
out_price = bts::securities::raw_round_to_quote_price (out_price, quote_leg_data.tick_size(), quote_leg_data.trading_side());
|
||||
quote_price = Price(out_price);
|
||||
}
|
||||
static void compute_hedge_price_size_2_legs (Price spread_price,
|
||||
const LegPropertyData& quote_filled_leg,
|
||||
Price quote_fill_price,
|
||||
Quantity quote_fill_quantity,
|
||||
double quote_fill_quantity,
|
||||
const LegPropertyData& hedge_leg,
|
||||
Price& out_hedge_price,
|
||||
Quantity& out_hedge_quantity){
|
||||
// for ratio spreads hedge sizes are simply rounded off (the fraction can be a setting)
|
||||
double& out_hedge_quantity){
|
||||
out_hedge_quantity = get_leg_quantity_from_another_leg_quantity (quote_filled_leg, quote_fill_quantity, hedge_leg);
|
||||
double out_hedge_price_d = (spread_price.value() - quote_fill_price.value() * quote_filled_leg.price_multiplier())/hedge_leg.price_multiplier();
|
||||
out_hedge_price_d = bts::securities::raw_round_to_quote_price (out_hedge_price_d, hedge_leg.tick_size(), hedge_leg.trading_side());
|
||||
out_hedge_price = Price(out_hedge_price_d);
|
||||
out_hedge_price = Price(bts::securities::raw_round_to_quote_price (out_hedge_price_d, hedge_leg.tick_size(), hedge_leg.trading_side()));
|
||||
}
|
||||
////////////////////////N legged case
|
||||
static void compute_quoting_price_size_n_legs (Price target_spread_price,
|
||||
Quantity quoting_leg_target_quantity,
|
||||
size_t quoting_leg_index,
|
||||
const ISpreaderManager& spreader,
|
||||
std::vector<QuoterLegDriverData>& driver_vector,
|
||||
std::vector<QuoteLegDriver>& driver_vector,
|
||||
Price& out_price,
|
||||
Quantity& out_quantity){
|
||||
compute_drivers (quoting_leg_target_quantity, quoting_leg_index, spreader, driver_vector);
|
||||
@@ -514,19 +543,23 @@ public:
|
||||
static void compute_drivers(Quantity quoting_leg_target_quantity,
|
||||
size_t quoting_leg_index,
|
||||
const ISpreaderManager& spreader,
|
||||
std::vector<QuoterLegDriverData>& out_driver_vector){
|
||||
const LegPropertyData& quoting_leg_data = spreader.get_leg_data (quoting_leg_index);
|
||||
std::vector<QuoteLegDriver>& out_driver_vector){
|
||||
const LegPropertyData& quote_leg_data = spreader.get_leg_data (quoting_leg_index);
|
||||
size_t n_legs = out_driver_vector.size();
|
||||
for (size_t idx = 0; idx < n_legs; idx++)
|
||||
if (idx != quoting_leg_index){
|
||||
const bts::orderman::Book& driver_leg_book = spreader.get_book(idx);
|
||||
const LegPropertyData& driver_leg_data = spreader.get_leg_data(idx);
|
||||
double driver_effective_mult = std::abs(driver_leg_data.leg_params().quote_params().lean_quantity_ratio() * driver_leg_data.size_multiplier() / quoting_leg_data.size_multiplier());
|
||||
uint32_t driver_leg_target_quantity = _round_hedge_target (driver_effective_mult * quoting_leg_target_quantity.value());
|
||||
Price driver_price; Quantity driver_quantity;
|
||||
SpreaderUtils::traverse_depth_for_quantity(driver_leg_book, SpreaderUtils::invert_side (driver_leg_data.trading_side()), Quantity(driver_leg_target_quantity), driver_price, driver_quantity);
|
||||
Quantity quoting_leg_calc_quantity = Quantity (_round_quoting_qty (driver_quantity.value() / driver_effective_mult));
|
||||
out_driver_vector[idx].set (driver_price, driver_quantity, quoting_leg_calc_quantity);
|
||||
const bts::orderman::Book& hedge_leg_book = spreader.get_book(idx);
|
||||
const LegPropertyData& hedge_leg_data = spreader.get_leg_data(idx);
|
||||
Price hedge_price;
|
||||
Quantity hedge_qty, quote_qty;
|
||||
compute_driver(quoting_leg_target_quantity,
|
||||
quote_leg_data,
|
||||
hedge_leg_data,
|
||||
hedge_leg_book,
|
||||
hedge_price,
|
||||
hedge_qty,
|
||||
quote_qty);
|
||||
out_driver_vector[idx].set (hedge_price, hedge_qty, quote_qty);
|
||||
}
|
||||
}
|
||||
// computes the quoting price from the leg drivers
|
||||
@@ -534,13 +567,13 @@ public:
|
||||
static Price compute_quoting_price_from_drivers(Price target_spread_price,
|
||||
size_t quoting_leg_index,
|
||||
const ISpreaderManager& spreader,
|
||||
const std::vector<QuoterLegDriverData>& driver_vector){
|
||||
const std::vector<QuoteLegDriver>& driver_vector){
|
||||
const LegPropertyData& quoting_leg_data = spreader.get_leg_data (quoting_leg_index);
|
||||
size_t n_legs = driver_vector.size();
|
||||
double sum = 0;
|
||||
for (size_t idx = 0; idx < n_legs; idx++)
|
||||
if (idx != quoting_leg_index)
|
||||
sum += driver_vector[idx].driver_price.value() * spreader.get_leg_data (idx).price_multiplier();
|
||||
sum += driver_vector[idx].driver_price().value() * spreader.get_leg_data (idx).price_multiplier();
|
||||
double rv = (target_spread_price.value() - sum)/quoting_leg_data.price_multiplier();
|
||||
rv = bts::securities::raw_round_to_quote_price (rv, quoting_leg_data.tick_size(), quoting_leg_data.trading_side());
|
||||
return Price(rv);
|
||||
@@ -549,26 +582,40 @@ public:
|
||||
// what each hedging leg can support. this function
|
||||
// simply finds that minimum
|
||||
static Quantity compute_quoting_quantity_from_drivers (size_t quoting_leg_index,
|
||||
const std::vector<QuoterLegDriverData>& driver_vector){
|
||||
const std::vector<QuoteLegDriver>& driver_vector){
|
||||
uint32_t rv = std::numeric_limits<uint32_t>::max();
|
||||
for (size_t idx = 0; idx < driver_vector.size(); idx++)
|
||||
if (idx != quoting_leg_index)
|
||||
if (driver_vector[idx].quoter_leg_quantity.value() < rv)
|
||||
rv = driver_vector[idx].quoter_leg_quantity.value();
|
||||
if (driver_vector[idx].quoter_leg_quantity().value() < rv)
|
||||
rv = driver_vector[idx].quoter_leg_quantity().value();
|
||||
return Quantity(rv);
|
||||
}
|
||||
static void compute_hedge_price_size_n_legs (const LegPropertyData& quote_filled_leg,
|
||||
Price,
|
||||
Quantity quote_fill_quantity,
|
||||
double quote_fill_quantity,
|
||||
const LegPropertyData& hedge_leg,
|
||||
const QuoterLegDriverData& leg_driver_data,
|
||||
Price& out_hedge_price, Quantity& out_hedge_quantity){
|
||||
out_hedge_price = leg_driver_data.driver_price;
|
||||
out_hedge_quantity = get_leg_quantity_from_another_leg_quantity (quote_filled_leg, quote_fill_quantity, hedge_leg);
|
||||
const QuoteLegDriver& leg_driver_data,
|
||||
Price& out_hedge_price,
|
||||
double& out_hedge_quantity){
|
||||
out_hedge_price = Price(leg_driver_data.driver_price().value());
|
||||
out_hedge_quantity = get_leg_quantity_from_another_leg_quantity (quote_filled_leg,
|
||||
quote_fill_quantity,
|
||||
hedge_leg);
|
||||
}
|
||||
////////////////////////////////////////////////////
|
||||
// Rounding functions //////////////////////////////
|
||||
////////////////////////////////////////////////////
|
||||
// rounds a double to a whole number if its too close
|
||||
// to it (this is ued in the case of rounding errors
|
||||
// when 0.9999999 or 1.0000001 is really 1)
|
||||
static double round_if_near_whole(double qty,
|
||||
double vicinity=0.000000001){
|
||||
double round_qty = std::round(qty);
|
||||
if (std::abs(round_qty - qty) <= vicinity)
|
||||
return round_qty;
|
||||
else
|
||||
return qty;
|
||||
}
|
||||
static uint32_t _round_quantity(double qty, bool is_up){
|
||||
double round_err = 0.000000001;
|
||||
double round_qty = std::round(qty);
|
||||
|
||||
+1
-2
@@ -10,8 +10,7 @@ namespace orderman {
|
||||
class SpreaderChildOrderState;
|
||||
class Book;
|
||||
class SingleFillHedger;
|
||||
class IHedgerStateWorker
|
||||
{
|
||||
class IHedgerStateWorker{
|
||||
public:
|
||||
// called once when transition to this state happens
|
||||
virtual void on_transition () = 0;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "UnhedgedDelta.h"
|
||||
#include <cmath>
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
|
||||
namespace bts{
|
||||
namespace orderman{
|
||||
|
||||
UnhedgedDelta::UnhedgedDelta()
|
||||
:
|
||||
_qty(0),
|
||||
_avg_price(0){
|
||||
}
|
||||
void UnhedgedDelta::add_unhedged(double price,double qty){
|
||||
bts_assert(qty >= 0);
|
||||
auto total_qty=_qty+qty;
|
||||
_avg_price = (_qty*_avg_price + qty*price)/total_qty;
|
||||
_qty=total_qty;
|
||||
}
|
||||
uint32_t UnhedgedDelta::subtract_hedge_if_available(){
|
||||
uint32_t rv = 0;
|
||||
double qty = SpreaderUtils::round_if_near_whole(_qty);
|
||||
if (qty >= 1){
|
||||
rv = std::floor(qty);
|
||||
_qty = qty-rv;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace bts{
|
||||
namespace orderman{
|
||||
// in the case when the hedge quantity is not a full contract
|
||||
// (ex if we get a 1 lot fill on a 1 X 3 spreader, where 3 is
|
||||
// a quoting leg), this class is used to accumulate the unhedged
|
||||
// delta
|
||||
class UnhedgedDelta{
|
||||
private:
|
||||
double _qty;
|
||||
double _avg_price;
|
||||
public:
|
||||
UnhedgedDelta();
|
||||
// adds to the unhedged qty and updates the
|
||||
// running average price
|
||||
void add_unhedged (double price, double qty);
|
||||
// if >= 1 unhedged delta is available, it
|
||||
// subtracts the integer part and returns it.
|
||||
// else returns 0
|
||||
uint32_t subtract_hedge_if_available();
|
||||
};
|
||||
}}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "QuoteDriverSet.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
QuoteDriverSet::QuoteDriverSet(size_t n_legs)
|
||||
:
|
||||
_quote_seq_no(0),
|
||||
_quote_price(0.0),
|
||||
_drivers(){
|
||||
_drivers.reserve(n_legs);
|
||||
for (size_t i=0; i<n_legs; i++)
|
||||
_drivers.emplace_back();
|
||||
}
|
||||
void QuoteDriverSet::update(uint16_t quote_seq_no,
|
||||
Price quote_price,
|
||||
const std::vector<QuoteLegDriver>& drivers){
|
||||
_quote_seq_no=quote_seq_no;
|
||||
_quote_price=quote_price;
|
||||
bts_assert(_drivers.size()==drivers.size());
|
||||
int i=0;
|
||||
for(const auto& d:drivers)
|
||||
_drivers[i++]=d;
|
||||
}
|
||||
const QuoteLegDriver& QuoteDriverSet::leg_driver(size_t leg_index) const{
|
||||
bts_assert(leg_index<_drivers.size());
|
||||
return _drivers[leg_index];
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "QuoteLegDriver.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
// this is the list of all drivers for a
|
||||
// single quoting leg
|
||||
class QuoteDriverSet{
|
||||
private:
|
||||
uint16_t _quote_seq_no;
|
||||
Price _quote_price;
|
||||
std::vector<QuoteLegDriver> _drivers;
|
||||
public:
|
||||
QuoteDriverSet (size_t n_legs);
|
||||
uint16_t quote_seq_no() const{
|
||||
return _quote_seq_no;
|
||||
}
|
||||
Price quote_price() const{
|
||||
return _quote_price;
|
||||
}
|
||||
void update(uint16_t quote_seq_no,
|
||||
Price quote_price,
|
||||
const std::vector<QuoteLegDriver>& drivers);
|
||||
const QuoteLegDriver& leg_driver (size_t leg_index) const;
|
||||
};
|
||||
}}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
#include "QuoteDriverSetHistory.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
QuoteDriverSetHistory::QuoteDriverSetHistory(size_t n_legs,
|
||||
size_t cache_size,
|
||||
double tick_size)
|
||||
:
|
||||
_n_legs(n_legs),
|
||||
_cache_size(cache_size),
|
||||
_tick_size(tick_size),
|
||||
_quote_seq_no(0),
|
||||
_driver_sets(),
|
||||
_h_ptr(0){
|
||||
_driver_sets.reserve(cache_size);
|
||||
for(size_t i=0;i<cache_size;i++)
|
||||
_driver_sets.emplace_back(n_legs);
|
||||
}
|
||||
void QuoteDriverSetHistory::add(const std::vector<QuoteLegDriver>& drivers,
|
||||
Price quote_price){
|
||||
bts_assert(_h_ptr<_cache_size);
|
||||
bts_assert(_cache_size>0);
|
||||
_quote_seq_no++;
|
||||
_driver_sets[_h_ptr].update(_quote_seq_no,quote_price,drivers);
|
||||
// move it to the next writeable place
|
||||
if(_h_ptr == _cache_size-1)
|
||||
_h_ptr = 0;
|
||||
else
|
||||
_h_ptr++;
|
||||
}
|
||||
const QuoteDriverSet* QuoteDriverSetHistory::_latest() const{
|
||||
if (_quote_seq_no>0)
|
||||
return &(_driver_sets[_h_ptr==0 ? _cache_size-1 : _h_ptr-1]);
|
||||
return
|
||||
nullptr;
|
||||
}
|
||||
const QuoteDriverSet* QuoteDriverSetHistory::_find_driver_set(Price quote_price) const{
|
||||
const QuoteDriverSet* rv=nullptr;
|
||||
for(const auto& d : _driver_sets)
|
||||
if (bts::securities::test_price_equality(d.quote_price().value(),
|
||||
quote_price.value(),
|
||||
_tick_size))
|
||||
if (rv==nullptr||d.quote_seq_no()>rv->quote_seq_no())
|
||||
rv=&d;
|
||||
return rv!=nullptr ? rv:_latest();
|
||||
}
|
||||
const QuoteLegDriver* QuoteDriverSetHistory::find_driver(Price quote_price,
|
||||
size_t leg_index) const{
|
||||
const QuoteDriverSet* driver_set_ptr=_find_driver_set(quote_price);
|
||||
return driver_set_ptr==nullptr ? nullptr : &(driver_set_ptr->leg_driver(leg_index));
|
||||
}
|
||||
}}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "QuoteDriverSet.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class QuoteDriverSetHistory{
|
||||
private:
|
||||
const size_t _n_legs, _cache_size;
|
||||
const double _tick_size;
|
||||
uint16_t _quote_seq_no;
|
||||
std::vector<QuoteDriverSet> _driver_sets;
|
||||
uint16_t _h_ptr;
|
||||
const QuoteDriverSet* _latest() const;
|
||||
const QuoteDriverSet* _find_driver_set(Price quote_price) const;
|
||||
public:
|
||||
QuoteDriverSetHistory(size_t n_legs,
|
||||
size_t cache_size,
|
||||
double tick_size);
|
||||
void add(const std::vector<QuoteLegDriver>& drivers,
|
||||
Price quote_price);
|
||||
const QuoteLegDriver* find_driver(Price quote_price, size_t leg_index) const;
|
||||
};
|
||||
|
||||
}}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "QuoteLegDriver.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
QuoteLegDriver::QuoteLegDriver()
|
||||
:
|
||||
_driver_price(0.0),
|
||||
_driver_quantity(0),
|
||||
_quoter_leg_quantity(0){
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
// Should be read as Quote_LegDriver, this is the
|
||||
// price and qty in a hedging leg that can sustain
|
||||
// a "_quoter_leg_quantity" sized quote in the
|
||||
// quoting leg
|
||||
class QuoteLegDriver
|
||||
{
|
||||
private:
|
||||
Price _driver_price;
|
||||
Quantity _driver_quantity;
|
||||
Quantity _quoter_leg_quantity;
|
||||
public:
|
||||
// driver_price and driver_quantity are the price and quantity
|
||||
// in the hedging (or driver) leg that can drive a quoter_leg_quantity
|
||||
// sized order in the quoting leg
|
||||
QuoteLegDriver();
|
||||
QuoteLegDriver& operator=(const QuoteLegDriver& s){
|
||||
set(s.driver_price(),s.driver_quantity(), s.quoter_leg_quantity());
|
||||
return *this;
|
||||
}
|
||||
void set(Price driver_price,
|
||||
Quantity driver_quantity,
|
||||
Quantity quoter_leg_quantity){
|
||||
_driver_price = driver_price;
|
||||
_driver_quantity = driver_quantity;
|
||||
_quoter_leg_quantity = quoter_leg_quantity;
|
||||
}
|
||||
Price driver_price() const{
|
||||
return _driver_price;
|
||||
}
|
||||
Quantity driver_quantity() const{
|
||||
return _driver_quantity;
|
||||
}
|
||||
Quantity quoter_leg_quantity() const{
|
||||
return _quoter_leg_quantity;
|
||||
}
|
||||
};
|
||||
}}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
// the concept of a "driver" is the size and price of a leg that
|
||||
// can sustain a quote in the quoted leg
|
||||
class QuoterLegDriverData
|
||||
{
|
||||
public:
|
||||
// driver_price and driver_quantity are the price and quantity
|
||||
// in the hedging (or driver) leg that can drive a quoter_leg_quantity
|
||||
// sized order in the quoting leg
|
||||
Price driver_price;
|
||||
Quantity driver_quantity;
|
||||
Quantity quoter_leg_quantity;
|
||||
|
||||
QuoterLegDriverData()
|
||||
:
|
||||
driver_price(0.0),
|
||||
driver_quantity(0),
|
||||
quoter_leg_quantity(0)
|
||||
{}
|
||||
void set(Price new_driver_price, Quantity new_driver_quantity, Quantity new_quoter_leg_quantity)
|
||||
{
|
||||
driver_price = new_driver_price;
|
||||
driver_quantity = new_driver_quantity;
|
||||
quoter_leg_quantity = new_quoter_leg_quantity;
|
||||
}
|
||||
};
|
||||
}}
|
||||
+1
-1
@@ -51,7 +51,7 @@ void QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act (orderman::TickT
|
||||
Price out_price; Quantity out_quantity;
|
||||
_quoter->_compute_price_quantity (out_price, out_quantity);
|
||||
if (out_quantity.value() > 0){
|
||||
_quoter->_commit_drivers ();
|
||||
_quoter->_commit_drivers (out_price);
|
||||
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, cid);
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ void QuoterWorkingStateWorker::_modify_order (orderman::TickToTradeCorID cid, Pr
|
||||
bts::securities::test_price_equality(_quoter->_price_quantity_state.out_price().value(),
|
||||
price.value(), _quoter->_this_leg_data.tick_size());
|
||||
if (!same_price_qty){
|
||||
_quoter->_commit_drivers ();
|
||||
_quoter->_commit_drivers (price);
|
||||
_quoter->_modify_order(price, quantity, cid);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-3
@@ -40,13 +40,19 @@ void SpreaderStartStateWorker::on_init_recovered(const bts::api::orderman::Order
|
||||
const SpreaderPreActivationOrderServices& spreader_pao_services)
|
||||
{
|
||||
bts_assert(!_spreader->_inited());
|
||||
// we are not handling this yet, so lets send it directly to END
|
||||
// we are not handling recovery, so if order is not in a terminal state,
|
||||
// instead of carrying on from where we left, we go directly to the end state
|
||||
// and mark the order cancelled. if its already in a terminal state we
|
||||
// just go to end and not do anything
|
||||
bts::api::orderman::OrderRecord order_record_copy = order_record;
|
||||
if (!SpreaderUtils::order_in_terminal_state(order_record_copy.mutable_state()->status()))
|
||||
order_record_copy.mutable_state()->set_status(bts::api::orderman::OrderStatus::ORDER_STATUS_CANCELED);
|
||||
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader,
|
||||
order_record,
|
||||
order_record_copy,
|
||||
spreader_pao_services,
|
||||
std::vector<LegPropertyData>());
|
||||
LOG(OrderMan, Warn, "SpreaderStartStateWorker.on_init_recovered key: {} , going to END", _spreader->key().value());
|
||||
std::string msg = "Recovered spreader orders and not supported";
|
||||
std::string msg = "Recovered spreader orders (from last session) are cancelled";
|
||||
_spreader->_transform_to_END (&msg);
|
||||
}
|
||||
void SpreaderStartStateWorker::on_activate(Price price, Quantity quantity, SpreaderOrderServices& order_services){
|
||||
|
||||
+24
-12
@@ -97,19 +97,31 @@ void SpreaderWorkingStateWorker::on_quoter_order_fill(SpreaderLegData& leg_data)
|
||||
order_update_message.last_update_filled_quantity(),
|
||||
order_update_message.last_update_filled_price());
|
||||
bts_assert (order_update_message.last_update_filled_quantity().value() > 0);
|
||||
for (size_t q_idx = 0; q_idx < _spreader->_bookkeeping_data().n_legs(); q_idx++){
|
||||
SpreaderLegData& other_leg_data = _spreader->_bookkeeping_data().get_leg_data (q_idx);
|
||||
for (size_t q_idx = 0; q_idx < _spreader->_bookkeeping_data().n_legs(); q_idx++){
|
||||
if (q_idx != leg_data.leg_property_data().leg_index()){
|
||||
// if it is an actively quoting leg, inform the quoter about it
|
||||
// so it can decrease the proportional size
|
||||
if (other_leg_data.leg_property_data().is_active_quoting())
|
||||
other_leg_data.quoter().update_other_leg_fills(leg_data.leg_property_data().leg_index(),
|
||||
order_update_message.last_update_filled_quantity());
|
||||
// a hedge is sent regardless
|
||||
_spreader->_send_hedge (leg_data.leg_property_data().leg_index(),
|
||||
order_update_message.last_update_filled_price(),
|
||||
order_update_message.last_update_filled_quantity(),
|
||||
q_idx);
|
||||
SpreaderLegData& other_leg_data = _spreader->_bookkeeping_data().get_leg_data (q_idx);
|
||||
Price hedge_price;
|
||||
double hedge_qty;
|
||||
_spreader->_compute_hedge_price_quantity(leg_data.leg_property_data().leg_index(),
|
||||
order_update_message.last_update_filled_price(),
|
||||
order_update_message.last_update_filled_quantity().value(),
|
||||
q_idx,
|
||||
hedge_price,
|
||||
hedge_qty);
|
||||
UnhedgedDelta& leg_unhedged_delta = other_leg_data.unhedged_delta();
|
||||
leg_unhedged_delta.add_unhedged(hedge_price.value(), hedge_qty);
|
||||
uint32_t total_unhedged_qty=leg_unhedged_delta.subtract_hedge_if_available();
|
||||
if (total_unhedged_qty != 0){
|
||||
// if it is an actively quoting leg, inform the quoter about it
|
||||
// so it can decrease the proportional size
|
||||
if (other_leg_data.leg_property_data().is_active_quoting())
|
||||
other_leg_data.quoter().update_other_leg_fills(leg_data.leg_property_data().leg_index(),
|
||||
Quantity(total_unhedged_qty));
|
||||
// a hedge is sent regardless
|
||||
_spreader->_send_hedge(other_leg_data.leg_property_data().leg_index(),
|
||||
hedge_price,
|
||||
Quantity(total_unhedged_qty));
|
||||
}
|
||||
}
|
||||
}
|
||||
// this is done at the end (i dont like ordering though) because
|
||||
|
||||
@@ -483,7 +483,7 @@ TEST_CASE ("compute quoting price quantity 3 legs", "[spreader]")
|
||||
std::vector<LegSpreaderParams> params; params.push_back(leg_params_1);params.push_back(leg_params_2);params.push_back(leg_params_3);
|
||||
|
||||
SpreaderManagerForTest* sm = create_spreader_manager_n_legs (OrderKey(), legs, params);
|
||||
std::vector<QuoterLegDriverData> driver_vector;
|
||||
std::vector<QuoteLegDriver> driver_vector;
|
||||
for(size_t i = 0; i < legs.size(); i++) driver_vector.emplace_back();
|
||||
|
||||
SECTION("basic joining")
|
||||
@@ -570,8 +570,9 @@ TEST_CASE ("compute quoting price quantity 3 legs", "[spreader]")
|
||||
TEST_CASE ("compute hedge price quantity 2 legs", "[spreader]")
|
||||
{
|
||||
ContractLeg leg_1, leg_2;
|
||||
Price out_hedge_price; Quantity out_hedge_quantity;
|
||||
Price spreader_price = Price (0.35);
|
||||
Price out_hedge_price;
|
||||
double out_hedge_quantity;
|
||||
Price spreader_price = Price(0.35);
|
||||
double size_mult_1 = 0.25, size_mult_2 = -1.1, // same used for price and qty
|
||||
price_mult_1 = 0.35, price_mult_2 = -1.5;
|
||||
double lean_mult = 1.0;
|
||||
@@ -580,23 +581,23 @@ TEST_CASE ("compute hedge price quantity 2 legs", "[spreader]")
|
||||
LegPropertyData leg_prop_data_1 = create_leg_property_data (leg_1, 0, price_mult_1, true, lean_mult);
|
||||
LegPropertyData leg_prop_data_2 = create_leg_property_data (leg_2, 1, price_mult_2, true, lean_mult);
|
||||
// md
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price (2500.5), Quantity (3),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price(2500.5), 3,
|
||||
leg_prop_data_1, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 1);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 0.6818181818181818181) < 0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 10717.25);
|
||||
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price (2500.5), Quantity (2),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price(2500.5), 2,
|
||||
leg_prop_data_1, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 0);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 0.45454545454545454545) < 0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 10717.25);
|
||||
// reversing the side of the quote fill
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price (10000.5), Quantity (2),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price(10000.5), 2,
|
||||
leg_prop_data_2, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 9);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 8.8)<0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 2333.25);
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price (9997), Quantity (6),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price(9997), 6,
|
||||
leg_prop_data_2, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 26);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 26.40) < 0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 2332.5);
|
||||
}
|
||||
/************************************************************************************************************/
|
||||
@@ -2341,6 +2342,201 @@ TEST_CASE ("prehedge_payup", "[spreader]"){
|
||||
REQUIRE(bts::securities::test_price_equality(out_price.value(),21.10,0.05));
|
||||
}
|
||||
}
|
||||
// n legged spreader price evaluation /////////////////////
|
||||
///////////////////////////////////////////////////////////
|
||||
TEST_CASE ("nlegs_compute_driver", "[spreader]"){
|
||||
bts::orderman::OrderKey order_key(1);
|
||||
ContractLeg leg_1, leg_2, leg_3, leg_4;
|
||||
fill_future_contract_leg (leg_1, 1, "CL", bts::api::Venue::VENUE_GLBX, 2018, 1, 15, 1.0, 1.0, api::Side::SIDE_BUY, 1.0, 0.01);
|
||||
fill_future_contract_leg (leg_2, 2, "CL", bts::api::Venue::VENUE_GLBX, 2018, 2, 15, 1.0, 1.0, api::Side::SIDE_BUY, 1.0, 0.01);
|
||||
fill_future_contract_leg (leg_3, 3, "CL", bts::api::Venue::VENUE_GLBX, 2018, 3, 15, 1.0, 1.0, api::Side::SIDE_BUY, 1.0, 0.01);
|
||||
fill_future_contract_leg (leg_4, 4, "CL", bts::api::Venue::VENUE_GLBX, 2018, 4, 15, -3.0, -3.0, api::Side::SIDE_SELL, 1.0, 0.01);
|
||||
std::vector<ContractLeg> legs;
|
||||
legs.push_back(leg_1);legs.push_back(leg_2);legs.push_back(leg_3);legs.push_back(leg_4);
|
||||
Security sec;
|
||||
fill_synthetic_security (sec, 2000, "4legs", legs);
|
||||
// execution params/order def
|
||||
LegSpreaderParams lsp_1, lsp_2, lsp_3, lsp_4;
|
||||
fill_leg_spreader_params(lsp_1, 1.0, false, 1.0);fill_leg_spreader_params(lsp_2, 1.0, false, 1.0);
|
||||
fill_leg_spreader_params(lsp_3, 1.0, false, 1.0);fill_leg_spreader_params(lsp_4, -4.0, true, 1.0);
|
||||
// // post hedge time out here
|
||||
std::vector<LegSpreaderParams> params;
|
||||
params.push_back(lsp_1);params.push_back(lsp_2);
|
||||
params.push_back(lsp_3);params.push_back(lsp_4);
|
||||
bts::api::orderman::OrderDefinition order_def;
|
||||
fill_spreader_order_definition(order_def, Side::SIDE_BUY, sec, params);
|
||||
// putting it together in SpreaderBookkeepingData
|
||||
SpreaderPreActivationOrderServices::TestOnlySpreaderPreActivationOrderServices
|
||||
testonly_spreader_pao_services = _create_spreader_pao_services(sec);
|
||||
SpreaderPreActivationOrderServices pao_services (&testonly_spreader_pao_services);
|
||||
std::vector<LegPropertyData> leg_props;
|
||||
SpreaderUtils::resolve_synthetic_spread(pao_services, order_def, leg_props);
|
||||
SpreaderManagerForTest spreader_manager (order_key, order_def, pao_services, leg_props);
|
||||
SpreaderBookkeepingData& bookkeeping_data = spreader_manager.bookkeeping_data();
|
||||
SECTION("compute_1"){
|
||||
Quantity quoting_leg_target (300);
|
||||
Price spread_price(0.25);
|
||||
fill_book(spreader_manager, 0, new double[5]{65.31,65.32,65.34,65.36, 65.40},
|
||||
new uint16_t[5]{12,18,20,19,23},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 1, new double[5]{64.40,64.41,64.43,64.45,64.50},
|
||||
new uint16_t[5]{1,5,20,12,15},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 2, new double[5]{64.10,64.18,64.19,64.20,64.21},
|
||||
new uint16_t[5]{10,43,17,50,15},bts::api::Side::SIDE_SELL,true);
|
||||
std::vector<QuoteLegDriver> out_driver_vector;
|
||||
for (int i=0; i<4; i++)
|
||||
out_driver_vector.emplace_back();
|
||||
SpreaderUtils::compute_drivers(quoting_leg_target, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],92,65.40,276);
|
||||
_verify_driver (out_driver_vector[1],53,64.50,159);
|
||||
_verify_driver (out_driver_vector[2],100,64.20,300);
|
||||
// size of the quoting leg
|
||||
Quantity computed_quoted_qty=SpreaderUtils::compute_quoting_quantity_from_drivers(3,out_driver_vector);
|
||||
REQUIRE(computed_quoted_qty.value()==159);
|
||||
// 2nd pass drivers
|
||||
SpreaderUtils::compute_drivers(computed_quoted_qty, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],53,65.36,159);
|
||||
_verify_driver (out_driver_vector[1],53,64.50,159);
|
||||
_verify_driver (out_driver_vector[2],53,64.18,159);
|
||||
// price of the quoting leg
|
||||
Price computed_price=SpreaderUtils::compute_quoting_price_from_drivers(spread_price,3,spreader_manager,out_driver_vector);
|
||||
REQUIRE(computed_price.value()==64.60);
|
||||
//hedging
|
||||
Price hedge_price; double hedge_quantity;
|
||||
SpreaderUtils::compute_hedge_price_size_n_legs(spreader_manager.get_leg_data(3),
|
||||
Price(0),
|
||||
18,
|
||||
spreader_manager.get_leg_data(0),
|
||||
out_driver_vector[0],
|
||||
hedge_price,
|
||||
hedge_quantity);
|
||||
REQUIRE(hedge_price.value()==65.36);
|
||||
REQUIRE(hedge_quantity==6);
|
||||
}
|
||||
SECTION("compute_2_fractional"){
|
||||
Quantity quoting_leg_target (100);
|
||||
Price spread_price(0.25);
|
||||
fill_book(spreader_manager, 0, new double[5]{65.31,65.32,65.34,65.36, 65.40},
|
||||
new uint16_t[5]{12,18,20,19,23},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 1, new double[5]{64.40,64.41,64.43,64.45,64.50},
|
||||
new uint16_t[5]{1,5,20,12,15},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 2, new double[5]{64.10,64.18,64.19,64.20,64.21},
|
||||
new uint16_t[5]{11,1,1,5,1},bts::api::Side::SIDE_SELL,true);
|
||||
std::vector<QuoteLegDriver> out_driver_vector;
|
||||
for (int i=0; i<4; i++)
|
||||
out_driver_vector.emplace_back();
|
||||
SpreaderUtils::compute_drivers(quoting_leg_target, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],34,65.34,100);
|
||||
_verify_driver (out_driver_vector[1],34,64.45,100);
|
||||
_verify_driver (out_driver_vector[2],19,64.21,57);
|
||||
// size of the quoting leg
|
||||
Quantity computed_quoted_qty=SpreaderUtils::compute_quoting_quantity_from_drivers(3,out_driver_vector);
|
||||
REQUIRE(computed_quoted_qty.value()==57);
|
||||
// // 2nd pass drivers
|
||||
SpreaderUtils::compute_drivers(computed_quoted_qty, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],19,65.32,57);
|
||||
_verify_driver (out_driver_vector[1],19,64.43,57);
|
||||
_verify_driver (out_driver_vector[2],19,64.21,57);
|
||||
// // price of the quoting leg
|
||||
Price computed_price=SpreaderUtils::compute_quoting_price_from_drivers(spread_price,3,spreader_manager,out_driver_vector);
|
||||
REQUIRE(computed_price.value()==64.57);
|
||||
// //hedging
|
||||
Price hedge_price; double hedge_quantity;
|
||||
SpreaderUtils::compute_hedge_price_size_n_legs(spreader_manager.get_leg_data(3),
|
||||
Price(0),
|
||||
10.0,
|
||||
spreader_manager.get_leg_data(0),
|
||||
out_driver_vector[0],
|
||||
hedge_price,
|
||||
hedge_quantity);
|
||||
REQUIRE(hedge_price.value()==65.32);
|
||||
REQUIRE(std::abs(hedge_quantity - 3.333333) < 0.000001);
|
||||
}
|
||||
}
|
||||
TEST_CASE ("quote driver caching", "[spreader]"){
|
||||
SECTION("zero_search"){
|
||||
QuoteDriverSetHistory driver_cache(5,10,0.05);
|
||||
const QuoteLegDriver* driver_ptr=driver_cache.find_driver(Price(100),3);
|
||||
REQUIRE(driver_ptr==nullptr);
|
||||
}
|
||||
SECTION("simple_search"){
|
||||
size_t n_legs=5, cache_sz=10;
|
||||
QuoteDriverSetHistory driver_cache(n_legs,cache_sz,0.05);
|
||||
Price p1(100); std::vector<QuoteLegDriver> d1;
|
||||
generate_driver_set(d1,n_legs);
|
||||
driver_cache.add(d1,p1);
|
||||
Price p2(100.05); std::vector<QuoteLegDriver> d2;
|
||||
generate_driver_set(d2,n_legs);
|
||||
driver_cache.add(d2,p2);
|
||||
Price p3(100.10); std::vector<QuoteLegDriver> d3;
|
||||
generate_driver_set(d3,n_legs);
|
||||
driver_cache.add(d3,p3);
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p1,i);
|
||||
verify_driver(*d,d1[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p2,i);
|
||||
verify_driver(*d,d2[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p3,i);
|
||||
verify_driver(*d,d3[i]);
|
||||
}
|
||||
// if not found, returns the latest
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(Price(100.01),i);
|
||||
verify_driver(*d,d3[i]);
|
||||
}
|
||||
}
|
||||
SECTION("collision_search and continous add"){
|
||||
// adding multiple updates to test circular nature
|
||||
size_t n_runs=10003, n_legs=4, cache_sz=10;
|
||||
QuoteDriverSetHistory driver_cache(n_legs,cache_sz,0.05);
|
||||
for(size_t i=0; i<n_runs; i++){
|
||||
Price p(gen_rand(1,100));
|
||||
std::vector<QuoteLegDriver> d;
|
||||
generate_driver_set(d,n_legs);
|
||||
driver_cache.add(d,p);
|
||||
}
|
||||
Price p1(100); std::vector<QuoteLegDriver> d1;
|
||||
generate_driver_set(d1,n_legs);
|
||||
driver_cache.add(d1,p1);
|
||||
Price p2(100.05); std::vector<QuoteLegDriver> d2;
|
||||
generate_driver_set(d2,n_legs);
|
||||
driver_cache.add(d2,p2);
|
||||
Price p3(100.10); std::vector<QuoteLegDriver> d3;
|
||||
generate_driver_set(d3,n_legs);
|
||||
driver_cache.add(d3,p3);
|
||||
Price p4(100); std::vector<QuoteLegDriver> d4;
|
||||
generate_driver_set(d4,n_legs);
|
||||
driver_cache.add(d4,p4);
|
||||
Price p5(100.15); std::vector<QuoteLegDriver> d5;
|
||||
generate_driver_set(d5,n_legs);
|
||||
driver_cache.add(d5,p5);
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p1,i);
|
||||
verify_driver(*d,d4[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p2,i);
|
||||
verify_driver(*d,d2[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p3,i);
|
||||
verify_driver(*d,d3[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p4,i);
|
||||
verify_driver(*d,d4[i]);
|
||||
}
|
||||
// 100 is there twice, should returns the
|
||||
// most recently added one
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p5,i);
|
||||
verify_driver(*d,d5[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
class RV
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -292,6 +292,20 @@ bts::orderman::Book get_book(SecurityKey key,
|
||||
{
|
||||
return get_book(key, n_levels, bid_prices, bid_sizes, n_levels, ask_prices, ask_sizes, delete_arrays);
|
||||
}
|
||||
void fill_book (SpreaderManagerForTest& sm,
|
||||
size_t index,
|
||||
double* prices,
|
||||
uint16_t* sizes,
|
||||
bts::api::Side side,
|
||||
bool delete_arrays){
|
||||
Book book;
|
||||
fill_book (book, prices, sizes, side);
|
||||
sm.set_book(book, index);
|
||||
if (delete_arrays){
|
||||
delete[] prices;
|
||||
delete[] sizes;
|
||||
}
|
||||
}
|
||||
/**********************************************************************/
|
||||
/******************************hedger**********************************/
|
||||
/**********************************************************************/
|
||||
@@ -464,14 +478,38 @@ void set_spreader_orders (SpreaderOrder& spreader, double* leg_1_bid_prices, uin
|
||||
/**********************************************************************/
|
||||
/******************************misc************************************/
|
||||
/**********************************************************************/
|
||||
void _verify_driver(const QuoteLegDriver& driver_data,
|
||||
uint16_t driver_qty,
|
||||
double driver_price,
|
||||
uint16_t quoter_leg_quantity){
|
||||
double err = 1e-9;
|
||||
REQUIRE(std::abs(driver_data.driver_price().value() - driver_price)< err);
|
||||
REQUIRE(driver_data.driver_quantity().value() == driver_qty);
|
||||
REQUIRE(driver_data.quoter_leg_quantity().value() == quoter_leg_quantity);
|
||||
}
|
||||
void verify_driver(const QuoteLegDriver& d1,
|
||||
const QuoteLegDriver& d2){
|
||||
_verify_driver(d1,
|
||||
d2.driver_quantity().value(),
|
||||
d2.driver_price().value(),
|
||||
d2.quoter_leg_quantity().value());
|
||||
}
|
||||
double gen_rand (double min, double max)
|
||||
{
|
||||
double f = (double)rand() / RAND_MAX;
|
||||
return min + f * (max - min);
|
||||
}
|
||||
// SpreaderChildOrderState create_order_update(const LegPropertyData& leg_data)
|
||||
// {
|
||||
// SpreaderChildOrderState rv(false, leg_data, true);
|
||||
// return rv;
|
||||
// }
|
||||
}}
|
||||
void fill_random_driver(QuoteLegDriver& driver){
|
||||
driver.set(Price(gen_rand(0,10)),
|
||||
Quantity(gen_rand(1,100)),
|
||||
Quantity(gen_rand(1,300)));
|
||||
}
|
||||
void generate_driver_set(std::vector<QuoteLegDriver>& drivers, size_t n_legs){
|
||||
drivers.clear();
|
||||
for(size_t i=0; i < n_legs; i++){
|
||||
QuoteLegDriver d;
|
||||
fill_random_driver(d);
|
||||
drivers.push_back(d);
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -110,7 +110,7 @@ OrderServiceServer::~OrderServiceServer() {
|
||||
}
|
||||
|
||||
void OrderServiceServer::on_orders(
|
||||
std::vector<api::orderman::OrderRecord const *> const &orders) {
|
||||
std::vector<api::orderman::OrderRecord> const &orders) {
|
||||
LOG(OrderMan, Info, "Received order update for publishing");
|
||||
std::vector<api::orderman::OrderRecord> created;
|
||||
created.reserve(orders.size());
|
||||
@@ -118,12 +118,12 @@ void OrderServiceServer::on_orders(
|
||||
updated.reserve(orders.size());
|
||||
|
||||
for (auto const &rec : orders) {
|
||||
auto const &k = OrderKey(rec->key());
|
||||
auto const &k = OrderKey(rec.key());
|
||||
if (me->known_orders.count(k) == 0) {
|
||||
me->known_orders.insert(k);
|
||||
created.push_back(*rec);
|
||||
created.push_back(rec);
|
||||
} else
|
||||
updated.push_back(*rec);
|
||||
updated.push_back(rec);
|
||||
}
|
||||
|
||||
LOG(OrderMan, Info,
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
void on_orders(std::vector<api::orderman::OrderRecord const*> const&);
|
||||
void on_orders(std::vector<api::orderman::OrderRecord> const&);
|
||||
void on_fills (std::vector<api::orderman::Fill const*> const&);
|
||||
};
|
||||
|
||||
|
||||
@@ -268,4 +268,71 @@ TEST_CASE ("Bootstrapping", "[admin_service]")
|
||||
REQUIRE(account.connections().exchange_connection_names_size()==2);
|
||||
}
|
||||
}
|
||||
TEST_CASE("Sorting Limit","[admin_service]"){
|
||||
bts::api::admin::AdminData admin_data;
|
||||
bts::api::admin::AdminServiceCreateAccountResult account_result;
|
||||
auto account = AdminServiceUtils::create_account(admin_data,
|
||||
0,
|
||||
std::string("A"),
|
||||
std::string("description"),
|
||||
true,
|
||||
account_result);
|
||||
auto l1 = account->mutable_security_clip_limits()->add_limit();
|
||||
l1->mutable_security()->set_exchange(api::Venue::VENUE_GLBX);
|
||||
l1->mutable_security()->set_security_type(api::SecurityType::SECURITYTYPE_FUTURE);
|
||||
l1->mutable_security()->set_security_symbol("*");
|
||||
l1->mutable_security()->set_security_id(0);
|
||||
l1->set_clip_size(100);
|
||||
auto l2 = account->mutable_security_clip_limits()->add_limit();
|
||||
l2->mutable_security()->set_exchange(api::Venue::VENUE_GLBX);
|
||||
l2->mutable_security()->set_security_type(api::SecurityType::SECURITYTYPE_FUTURE);
|
||||
l2->mutable_security()->set_security_symbol("SI");
|
||||
l2->mutable_security()->set_security_id(0);
|
||||
l2->set_clip_size(101);
|
||||
auto l3 = account->mutable_security_clip_limits()->add_limit();
|
||||
l3->mutable_security()->set_exchange(api::Venue::VENUE_GLBX);
|
||||
l3->mutable_security()->set_security_type(api::SecurityType::SECURITYTYPE_FUTURE);
|
||||
l3->mutable_security()->set_security_symbol("CL");
|
||||
l3->mutable_security()->set_security_id(0);
|
||||
l3->set_clip_size(102);
|
||||
auto l4 = account->mutable_security_clip_limits()->add_limit();
|
||||
l4->mutable_security()->set_exchange(api::Venue::VENUE_GLBX);
|
||||
l4->mutable_security()->set_security_type(api::SecurityType::SECURITYTYPE_FUTURE);
|
||||
l4->mutable_security()->set_security_symbol("ZC");
|
||||
l4->mutable_security()->set_security_id(10);
|
||||
l4->set_clip_size(103);
|
||||
auto l5 = account->mutable_security_clip_limits()->add_limit();
|
||||
l5->mutable_security()->set_exchange(api::Venue::VENUE_GLBX);
|
||||
l5->mutable_security()->set_security_type(api::SecurityType::SECURITYTYPE_FUTURE);
|
||||
l5->mutable_security()->set_security_symbol("ZN");
|
||||
l5->mutable_security()->set_security_id(5);
|
||||
l5->set_clip_size(104);
|
||||
auto l6 = account->mutable_security_clip_limits()->add_limit();
|
||||
l6->mutable_security()->set_exchange(api::Venue::VENUE_GLBX);
|
||||
l6->mutable_security()->set_security_type(api::SecurityType::SECURITYTYPE_SPREAD);
|
||||
l6->mutable_security()->set_security_symbol("SI");
|
||||
l6->mutable_security()->set_security_id(0);
|
||||
l6->set_clip_size(105);
|
||||
// tests
|
||||
auto sorted_admin_data = AdminServiceUtils::sort_limits(admin_data);
|
||||
const auto& r_account = sorted_admin_data.accounts(0);
|
||||
const auto& r_l1 = r_account.security_clip_limits().limit(0);
|
||||
REQUIRE(r_l1.security().security_id()==5);
|
||||
REQUIRE(r_l1.clip_size()==104);
|
||||
const auto& r_l2 = r_account.security_clip_limits().limit(1);
|
||||
REQUIRE(r_l2.security().security_id()==10);
|
||||
REQUIRE(r_l2.clip_size()==103);
|
||||
const auto& r_l3 = r_account.security_clip_limits().limit(2);
|
||||
REQUIRE(r_l3.security().security_symbol()=="CL");
|
||||
REQUIRE(r_l3.clip_size()==102);
|
||||
const auto& r_l4 = r_account.security_clip_limits().limit(3);
|
||||
REQUIRE(r_l4.security().security_symbol()=="SI");
|
||||
REQUIRE(r_l4.clip_size()==101);
|
||||
const auto& r_l5 = r_account.security_clip_limits().limit(4);
|
||||
REQUIRE(r_l5.security().security_symbol()=="SI");
|
||||
REQUIRE(r_l5.clip_size()==105);
|
||||
const auto& r_l6 = r_account.security_clip_limits().limit(5);
|
||||
REQUIRE(r_l6.security().security_symbol()=="*");
|
||||
REQUIRE(r_l6.clip_size()==100);
|
||||
}
|
||||
}}
|
||||
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user