Compare commits

..
4 Commits
24 changed files with 631 additions and 213 deletions

No files matched your search

@@ -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);
@@ -245,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;
@@ -261,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();
@@ -328,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));
}
@@ -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
}
}
@@ -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
@@ -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}";
+226 -139
View File
@@ -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 &quot;<paramref name="tick_base"/> * 10^n,&quot; where 'n' is a natural number (including 0).
/// <para>Note: If <paramref name="display_digits_override"/> is non-zero, 'n' will be &quot;<paramref name="display_digits_override"/> - Math.Ceiling(Log10(<paramref name="tick_base"/>))&quot;</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 &quot;reduced_target_base * 10^n,&quot; 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;
}
}
}
@@ -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));
+7 -3
View File
@@ -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 {
+3 -3
View File
@@ -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 << ", ";
+12
View File
@@ -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);
+9 -7
View File
@@ -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: {
+1 -1
View File
@@ -4,7 +4,7 @@ SETLOCAL
if "%SPARKVM_PORT%"=="" set SPARKVM_PORT=22
if "%1" == "" goto :err
echo.
ssh bts@sparkvm -p %SPARKVM_PORT% -o StrictHostKeyChecking=no -t "cd /home/bts/spark && git checkout %1 && git reset --hard && git clean -fd && git pull && git submodule update"
ssh bts@sparkvm -p %SPARKVM_PORT% -o StrictHostKeyChecking=no -t "cd /home/bts/spark ; git reset --hard ; git clean -fd ; git checkout %1 ; git pull ; git submodule update"
ENDLOCAL
goto :eof