Compare commits

...
52 Commits
Author SHA1 Message Date
Kevin Darby 191518d8bc Merged in fix_algo_docs (pull request #316)
removing duplicate mark down describing algos

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
2019-08-08 20:26:11 +00:00
Kevin Darby 50768750d6 removing duplicate mark down describing algos 2019-08-08 16:14:49 -04:00
Jake Norbut 450ef7dd56 Merged in SP-128 (pull request #312)
SP-128 - Allow for configurable stickyness on Algo Favorites

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-08 19:26:03 +00:00
Jake Norbut 89a4f9264a LadderSaveState now at version 2; implementing upgrade mechanism for migrating existing favorite algos to stickied algos 2019-08-08 13:03:26 -05:00
Jake Norbut 55ade15574 provide upgrade mechanism when loading IShellWindow 2019-08-08 13:01:19 -05:00
Kevin Darby 36b8b35e47 Merged in fix_tooltips2 (pull request #315)
fix non-dynamic and non-generic tooltips

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-08-08 16:18:29 +00:00
Kevin Darby 23bb872052 fix non-dynamic and non-generic tooltips 2019-08-08 12:09:48 -04:00
Kevin Darby 5a36af1ed8 more 2.8 release notes 2019-08-08 10:54:57 -04:00
Jake Norbut bc12a3bd42 Merged in SP-172 (pull request #313)
making login view a little wider and shorter so that error state doesn't look as bad

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-08 14:48:31 +00:00
Kevin Darby 4e831b5f89 Merged in spreader_nlegs_hedging (pull request #314)
RTM Spreader nlegs hedging - for Taha

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-08 14:33:18 +00:00
Kevin Darby d886327c1a Merge branch 'master' of bitbucket.org:bluetradesys/spark into spreader_nlegs_hedging 2019-08-08 09:59:46 -04:00
Kevin Darby 533ff8acf7 Merged in server_sdk_docs (pull request #310)
RTM SP-138, SP-139, SP-140 server sdk docs

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-08-07 21:30:35 +00:00
Kevin Darby e64eaec240 lumping client doc changes in with server 2019-08-07 17:27:16 -04:00
Taha Afzal 02a30142b7 Fixing the tests 2019-08-07 11:24:22 -04:00
Taha Afzal 3937939c0a Bug in sells fixed (the LegPropertyData::trading_side() is already adjusted for the spreader side 2019-08-06 18:57:49 -04:00
Taha Afzal be4bfb6152 Hooked up the hedging price calculation to the UI 2019-08-06 17:27:16 -04:00
Jake Norbut 4ff1be11e2 making login view a little wider and shorter so that error state doesn't look as bad 2019-08-06 14:32:32 -05:00
John Colvin dad4c1bf9b ExecutionStrategyEditor uses VisualStates to disable name, ticks, and pricing controls after creation 2019-08-06 13:07:03 -05:00
John Colvin 40aa6c518a added hedge pricing controls to synth editor 2019-08-06 12:58:58 -05:00
Jake Norbut bf8df1d396 always push Limit onto the algo stack; henceforth known as the anchor 2019-08-06 10:24:59 -05:00
Taha Afzal 9f575fcdec Added spreader params for this functionality(recalc) 2019-08-06 10:37:44 -04:00
Taha Afzal a807a09bea Merge branch 'master' into spreader_nlegs_hedging 2019-08-05 22:50:39 -04:00
John Colvin d064af817c remove unreferenced '_sticky_algos' field 2019-08-05 15:51:21 -05:00
John Colvin 7b3c4b76bc LadderVM/OrderPropertiesEditorVM sticky/stack behavior 2019-08-05 15:36:57 -05:00
John Colvin 5de431f279 make LadderVM.send_order_command an IAsyncCommand 2019-08-05 15:36:57 -05:00
John Colvin 7b63d951f0 ShellSettingsDialogVM profilevm resets 'is_dirty' whenever the internal vm's resultant value equals the target value 2019-08-05 15:36:57 -05:00
John Colvin 018887980b LadderSettingsVM 'sticky buttons' impl reacts to FavoriteAlgoItem's value (algo) changes 2019-08-05 15:06:39 -05:00
John Colvin 8b4d649bd4 ExecuteAsync should return the Task directly (no need to await internally) 2019-08-05 15:06:39 -05:00
Jake Norbut 441d3c886b fixing improper use of is 2019-08-05 15:06:39 -05:00
Jake Norbut 472e32f558 removing empty new lines; leftovers from Dispatcher debugging 2019-08-05 15:06:39 -05:00
Jake Norbut 472c9252f9 adding Stay Enabled (sticky) checkbox to Favorites UI 2019-08-05 15:06:39 -05:00
Jake Norbut 98a9ad3ef2 reset order type after non-sticky order entry 2019-08-05 15:06:39 -05:00
Jake Norbut 7b237fedea adding sticky-related bits 2019-08-05 15:06:39 -05:00
Jake Norbut 7281aa8ff1 adding is_sticky property 2019-08-05 15:06:39 -05:00
Jake Norbut d915e2f064 setting algo_def_vm.is_sticky via CollectionChanged event (hmm) 2019-08-05 15:06:39 -05:00
Jake Norbut beb5d855b1 Dispatcher debuggering with John; think does not appear to work since the if condition is never met, but commiting this part of the pair-debugging experience 2019-08-05 15:06:39 -05:00
Jake Norbut 120b86aaae popuplating stickied algo keys for persistence/restoration state 2019-08-05 15:06:39 -05:00
Jake Norbut 44eeff98e7 adding is_sticky member to which the view will Databind 2019-08-05 15:06:39 -05:00
Jake Norbut dc420a097a adding stickied_algos ProtoMember for persistence 2019-08-05 15:06:39 -05:00
Jake Norbut 69ee85dea9 misc - adding OnDispatcher after discussing with John 2019-08-05 15:06:39 -05:00
Kevin Darby bd451f6f65 SP-138, SP-139, SP-140 server sdk docs 2019-08-05 14:29:13 -04:00
Taha Afzal dc922a0299 Fixed the test that was failing when Spreader:n_legs_recomp_hedges_on_fill: true 2019-07-08 14:01:24 -04:00
Taha Afzal 729acc4ad5 deleted default constructors and assignment operators on NLegsHedgeRecompData plus tightened a condition for application of recompute redhedging 2019-07-07 20:52:42 -04:00
Taha Afzal 4d559b8ec1 Reorganized the data for NLeg Hedge Recomp better 2019-07-05 11:16:24 -04:00
Taha Afzal 2561cb3960 Better comments 2019-07-04 19:59:21 -04:00
Taha Afzal c4ba25bdae Merge branch 'master' into spreader_nlegs_hedging 2019-07-04 11:18:51 -04:00
Taha Afzal 7563eb7afe Merge branch 'master' into spreader_nlegs_hedging 2019-06-10 08:30:58 -04:00
Taha Afzal 1b4275db4c Merge branch 'master' into spreader_nlegs_hedging 2019-05-23 16:42:28 -04:00
Taha Afzal 592ad39a32 tests pass 2019-05-23 12:37:30 -04:00
Taha Afzal 1fd66a17ca better tests 2019-05-20 19:57:18 -04:00
Taha Afzal 3ec896d609 code complete, testing remains 2019-05-17 19:51:37 -04:00
Taha Afzal 8f1eab57b7 Basic functions in, need to be hooked 2019-05-17 18:22:36 -04:00
33 changed files with 834 additions and 270 deletions

No files matched your search

+2 -2
View File
@@ -135,13 +135,13 @@
"name": "Spreader Tests",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": [
"test",
"[spreader]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
+4 -1
View File
@@ -14,6 +14,8 @@ insert_anchor_links = "right"
- Fixed a bug that cause cause a failure on startup if the user
loaded a workspace with a Ladder that had a dynamic order type
selected by default
- Increased width of login panel to better show login error messages
- Added optional 'Maker/Taker' pricing to Spreader algo
### Internal changes
- Made it easier to generate simple UI's for new order types
### API Changes
@@ -362,7 +364,8 @@ insert_anchor_links = "right"
- The zoom slider can be enabled from Ladder Form -> Settings button -> Left Pane tab -> check Zoom Slider checkbox
- The zoom functionality can also be performed via hotkey and mouse wheel combinations
- Zoom In = CTRL + Mouse wheel up
- Zoom Out = CTRL + Mouse wheel down␍
- Zoom Out = CTRL + Mouse wheel down
- Reset zoom = CTRL + Mouse wheel click or double click on the zoom slider
### Exchange simulator
- Improved the exchange simulator to track real market data more closely
@@ -374,6 +374,72 @@
Height="{TemplateBinding Height}"
Padding="{TemplateBinding Padding}"
>
<Border.Resources>
<!--Leg Count States-->
<sys:String x:Key="LegCountStates">LegCountStates</sys:String>
<sys:String x:Key="LegCountStates.TwoOrFewer">TwoOrFewerLegsState</sys:String>
<sys:String x:Key="LegCountStates.ThreeOrMore">ThreeOrMoreLegsState</sys:String>
<cv:ValueConverterPipe x:Key="LegCountStateConverter">
<cv:IntGreaterThanConverter Value="2"/>
<cv:BooleanToObjectConverter ValueForFalse="{StaticResource LegCountStates.TwoOrFewer}" ValueForTrue="{StaticResource LegCountStates.ThreeOrMore}"/>
</cv:ValueConverterPipe>
<!--Persistance States-->
<sys:String x:Key="PersistanceStates">PersistanceStates</sys:String>
<sys:String x:Key="PersistanceStates.Unpersisted">UnpersistedState</sys:String>
<sys:String x:Key="PersistanceStates.Persisted">PersistedState</sys:String>
<cv:BooleanToObjectConverter x:Key="PersistanceStateConverter" ValueForFalse="{StaticResource PersistanceStates.Persisted}" ValueForTrue="{StaticResource PersistanceStates.Unpersisted}"/>
</Border.Resources>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding Group.Target.TemplatedParent.LegTuples.Count, RelativeSource={RelativeSource Self}, Converter={StaticResource LegCountStateConverter}}"/>
<u:StateDefinition State="{Binding Group.Target.TemplatedParent.IsNew, RelativeSource={RelativeSource Self}, Converter={StaticResource PersistanceStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{StaticResource LegCountStates}">
<VisualState Name="{StaticResource LegCountStates.TwoOrFewer}"/>
<VisualState Name="{StaticResource LegCountStates.ThreeOrMore}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgePricing_Label" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgePricing_DropDown" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="{StaticResource PersistanceStates}">
<VisualState Name="{StaticResource PersistanceStates.Unpersisted}"/>
<VisualState Name="{StaticResource PersistanceStates.Persisted}">
<Storyboard>
<!--Name-->
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Name_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Name_TextBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<!--Tick Factors-->
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickFactors_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickFactors_Picker" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<!--Pricing-->
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Pricing_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Pricing_DropDown" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -388,8 +454,9 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
<TextBlock x:Name="PART_Name_Label" VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
<TextBox
x:Name="PART_Name_TextBox"
Grid.Column="1"
@@ -408,20 +475,37 @@
</u:Behavior.BindingUpdateGroups>
</TextBox>
<TextBlock
Grid.Column="2" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
x:Name="PART_TickFactors_Label"
Grid.Column="2"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Ticks"
IsEnabled="True"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<c:TickFactorsPicker
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
x:Name="PART_TickFactors_Picker"
Grid.Column="3"
Margin="{StaticResource Margin.LHeaderPanelItem}"
HorizontalAlignment="Stretch"
IsEnabled="True"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" >Pricing</TextBlock>
<TextBlock
x:Name="PART_Pricing_Label"
Grid.Column="4"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
IsEnabled="True"
Text="Pricing"
/>
<ComboBox
x:Name="PART_Pricing_DropDown"
Grid.Column="5"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Width="90"
IsEnabled="{TemplateBinding IsNew}"
IsEnabled="True"
ItemsSource="{x:Static usp:ExecutionStrategyEditor.PriceCalcItems}"
SelectedItem="{Binding price_calc}"
>
@@ -431,6 +515,24 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock
Grid.Column="6"
x:Name="PART_HedgePricing_Label"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Hedge"
Visibility="Collapsed"
/>
<ComboBox
x:Name="PART_HedgePricing_DropDown"
Grid.Column="7"
HorizontalAlignment="Stretch"
Margin="{StaticResource Margin.LHeaderPanelItem}"
ItemsSource="{TemplateBinding HedgePricingItemsSource}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding HedgePricing, RelativeSource={RelativeSource TemplatedParent}}"
Visibility="Collapsed"
/>
</Grid>
<c:DataGridEx
x:Name="PART_DataGrid"
@@ -8,6 +8,7 @@ using mbts.ui.utility;
using mbts.user.settings;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -183,6 +184,29 @@ namespace mbts.ui.spark {
public mbts.spark.ContractPickerModel ContractPickerModel { get => GetValue(ContractPickerModelProperty) as mbts.spark.ContractPickerModel; set => SetValue(ContractPickerModelProperty, value); }
#endregion
#region HedgePricingItemsSource
public static readonly DependencyProperty HedgePricingItemsSourceProperty =
WPFHelper.create_dp<IEnumerable, ExecutionStrategyEditor>(
nameof(HedgePricingItemsSource));
public IEnumerable HedgePricingItemsSource {
get => GetValue(HedgePricingItemsSourceProperty) as IEnumerable;
set => SetValue(HedgePricingItemsSourceProperty, value);
}
#endregion
#region HedgePricing
public static readonly DependencyProperty HedgePricingProperty =
WPFHelper.create_dp<object, ExecutionStrategyEditor>(
nameof(HedgePricing),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
public object HedgePricing {
get => GetValue(HedgePricingProperty);
set => SetValue(HedgePricingProperty, value);
}
#endregion
public override void OnApplyTemplate() =>
_connect_leg_tuples();
@@ -251,7 +275,7 @@ namespace mbts.ui.spark {
public ExecutionStrategyErrorAdorner(ExecutionStrategyEditor editor) : base(editor) {
_name_text_box = child_property<TextBox>("PART_Name_TextBox");
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactorsPicker");
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactors_Picker");
}
protected override void OnRender(DrawingContext drawing_context) {
@@ -34,6 +34,8 @@
LegTuples="{Binding leg_tuples}"
RemoveLegCommand="{Binding remove_leg}"
ContractPickerModel="{Binding contract_picker_model}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
/>
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
</Grid>
@@ -17,6 +17,7 @@ using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
using HedgePricing = bts.spark.ExecutionStrategy.Types.HedgePricing;
namespace mbts.spark {
/// <summary>
@@ -32,6 +33,7 @@ namespace mbts.spark {
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
TickFactors _tick_factors;
ExecutionStrategyImmutable _strategy;
HedgePricing _hedge_pricing;
/// <summary>
/// Inititalizes a new instance of SyntheticEditorVM class. This constructor may only be used for already-existing synthetic securities (id must be non-zero).
@@ -294,6 +296,9 @@ namespace mbts.spark {
public IRaiseCanExecuteChangedCommand commit { get; }
public ICommand remove_leg { get; }
public ContractPickerModel contract_picker_model { get; }
// hedge pricing
public HedgePricing hedge_pricing { get => _hedge_pricing; set => Set(ref _hedge_pricing, value); }
public IEnumerable<HedgePricing> hedge_pricing_options => EnumHelper.get_values<HedgePricing>();
public ExecutionStrategyImmutable get_strategy() {
var mutable_strategy = _strategy.CloneToMutable();
@@ -301,6 +306,7 @@ namespace mbts.spark {
foreach (var lt in leg_tuples) {
mutable_strategy.LegParamsMap.Add(lt.leg.Security.Id, lt.leg_params);
}
mutable_strategy.HedgePricing = hedge_pricing;
return new ExecutionStrategyImmutable(mutable_strategy);
}
@@ -308,6 +314,7 @@ namespace mbts.spark {
_synthetic = synthetic.CloneToMutable();
_synthetic.Exchange = Venue.Bts;
_strategy = strategy;
hedge_pricing = strategy.HedgePricing;
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
RaisePropertyChanged(nameof(name), nameof(tick_factors));
}
@@ -514,6 +514,8 @@
RemoveLegCommand="{Binding remove_leg}"
ContractPickerModel="{Binding contract_picker_model}"
IsEnabled="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}, Converter={StaticResource NotConverter}}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
/>
</AdornerDecorator>
</DataTemplate>
@@ -10,7 +10,7 @@ using System.Reactive.Disposables;
namespace mbts.spark {
static class AlgoParamRowsHelper {
public static IDisposable connect(AlgoDefinitionVM algo, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) =>
algo == null ? null : new CompositeDisposable(algo.observe_algo_root().SubscribeEach(a => {
algo == null ? null : new CompositeDisposable(algo.observe_algo_root().OnDispatcher().SubscribeEach(a => {
rows.clear_collections();
if (a == null)
return Disposable.empty;
@@ -77,7 +77,7 @@ namespace mbts.spark {
include_algo_type_rows ?
(ObservableCollection<AlgoParamRowVM>)new ObservableAppendCollection<AlgoParamRowVM>(child_rows, selector_wrapper) :
child_rows,
s.subscribe_selected_algo().SubscribeEach(a => {
s.subscribe_selected_algo().OnDispatcher().SubscribeEach(a => {
s.value = a;
child_rows.clear_collections();
return _wrap_rows(a, selector_wrapper, child_rows, include_algo_type_rows);
@@ -194,20 +194,27 @@ namespace mbts.spark {
readonly bts.persist.algo.AlgoPersistedKey[] _favorite_algos;
[ProtoMember(2)]
public readonly int column_count;
[ProtoMember(3)]
readonly bts.persist.algo.AlgoPersistedKey[] _stickied_algos;
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count)
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)), column_count) { }
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos)
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)),
column_count,
ArrayHelper.safe_convert(stickied_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted))) { }
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count) {
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count, bts.persist.algo.AlgoPersistedKey[] stickied_algos) {
_favorite_algos = favorite_algos;
this.column_count = column_count;
_stickied_algos = stickied_algos;
}
public IReadOnlyList<AlgoKey> favorite_algos => ArrayHelper.safe_convert(_favorite_algos, k => k.to_algo_key());
public IReadOnlyList<AlgoKey> stickied_algos => ArrayHelper.safe_convert(_stickied_algos, k => k.to_algo_key());
public bool Equals(LadderFavoriteAlgosSettings other) =>
column_count == other.column_count &&
favorite_algos.SequenceEqual(other.favorite_algos);
favorite_algos.SequenceEqual(other.favorite_algos) &&
stickied_algos.SequenceEqual(other.stickied_algos);
public override bool Equals(object obj) =>
EqualityComparer.equals_obj(this, obj);
@@ -215,7 +222,8 @@ namespace mbts.spark {
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
column_count,
EqualityComparer.aggregate_hash_limited(favorite_algos));
EqualityComparer.aggregate_hash_limited(favorite_algos),
EqualityComparer.aggregate_hash_limited(stickied_algos));
public static bool operator ==(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => a.Equals(b);
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
@@ -273,7 +281,7 @@ namespace mbts.spark {
order_entry_panel_initial_width = _default_order_entry_panel_width;
zoom_factor = _default_zoom_factor;
use_defaults = true;
favorite_algos = new LadderFavoriteAlgosSettings(null, 2);
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
show_warn_on_cross = _default_show_warn_on_cross;
}
@@ -11,6 +11,7 @@ using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
@@ -22,6 +23,7 @@ using LadderSettings = mbts.spark.LadderSettings;
namespace bts.shell {
class LadderWindowFactory : ShellWindowFactory<LadderSaveState> {
const string _window_type = "ladder";
const int _version_where_stickied_algos_introduced = 2;
readonly LadderConfig _ladder_conf;
readonly IBus _bus;
readonly IWindowManager _wm;
@@ -38,7 +40,7 @@ namespace bts.shell {
OrdersModel orders_model,
MBOService mbo)
: base(owner, "SparkLadder") {
: base(owner, "SparkLadder", 2) {
_bus = bus;
_wm = wm;
_settings_hub = settings_hub;
@@ -60,6 +62,16 @@ namespace bts.shell {
return null;
}
protected override Task<IShellWindow> try_upgrade_state(Stream load_stream, int version) {
var state = ProtoBuf.Serializer.DeserializeWithLengthPrefix<LadderSaveState>(load_stream, ProtoBuf.PrefixStyle.Fixed32);
if (version < _version_where_stickied_algos_introduced) {
// We want to assign any existing favorite algos to stickied algos upon setting upgrade
var favorite_algos_array = state.settings.favorite_algos.favorite_algos.ToArray();
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(favorite_algos_array, state.settings.favorite_algos.column_count, favorite_algos_array);
}
return load(state);
}
LadderSaveState _get_state(LadderVM vm, uint id) {
LadderSettings s = vm.get_save_state();
if (id != 0) {
@@ -2,6 +2,7 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.persist.algo;
using bts.shell.orderbook;
using bts.utils;
using mbts.collections;
@@ -723,12 +724,15 @@ namespace mbts.spark {
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
int _column_count = 2;
HashSet<AlgoPersistedKey> _sticky_algo_keys;
IDisposable _sticky_algos_hook;
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
_algo_definitions = algo_definitions;
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
_dispose = Disposable.Create(() => Disposable.dispose(ref _sticky_algos_hook));
}
public ObservableCollection<FavoriteAlgoItem> items { get; }
@@ -745,6 +749,17 @@ namespace mbts.spark {
fai.selector.try_select(k);
return fai;
}));
_sticky_algo_keys = new HashSet<AlgoPersistedKey>(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
Disposable.replace(ref _sticky_algos_hook,
ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
x.selector.subscribe_value().Subscribe(algo => {
if (algo != null &&
algo.key.is_persisted &&
(x.is_sticky = _sticky_algo_keys.Remove(new AlgoPersistedKey(algo.key.persisted))) &&
_sticky_algo_keys.Count == 0)
//
Disposable.dispose(ref _sticky_algos_hook);
})));
}
public LadderFavoriteAlgosSettings create() =>
@@ -753,7 +768,13 @@ namespace mbts.spark {
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray(),
column_count);
column_count,
_raw_items
.Where(fai => fai.is_sticky)
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray());
FavoriteAlgoItem _item_factory() =>
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
@@ -766,6 +787,7 @@ namespace mbts.spark {
public static readonly FavoriteAlgoItem AddButtonPlaceholderItem = new _AddButtonPlaceholderItem();
internal readonly KeyedItemSelector<AlgoDefinitionVM, AlgoKey> selector;
bool _is_sticky;
protected FavoriteAlgoItem() { }
@@ -776,6 +798,7 @@ namespace mbts.spark {
public ICollectionView<AlgoDefinitionVM> algo_definitions_view { get; }
public AlgoDefinitionVM algo_definition { get => selector.value; set => selector.value = value; }
public bool is_sticky { get => _is_sticky; set => Set(ref _is_sticky, value); }
class _AddButtonPlaceholderItem : FavoriteAlgoItem, ITemplateSelectorKey {
public string key => LadderSettingsView.FavoriteAlgoAddButtonTemplateKey;
@@ -1,9 +1,11 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.co2;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.utils;
@@ -32,6 +34,7 @@ namespace mbts.spark {
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
_show_cancel_sells, _show_cancel_all, _show_zoom;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
double _position, _day_position;
bool _show_account_position, _show_day_position;
ulong _misc_time = 0;
@@ -133,7 +136,7 @@ namespace mbts.spark {
//_window_height = backing(nameof(window_height), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 600);
//_window_width = backing(nameof(window_width), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 420);
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(_show_day_position ? day_position : position)));
send_order_command = new DelegateCommand<(double price, Side? side, MouseButton button)>(send_order);
send_order_command = new AsyncCommand<(double price, Side? side, MouseButton button)>(send_order_async);
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
show_book_command = new DelegateCommand(_show_book);
show_ticket_command = new DelegateCommand(_show_ticket);
@@ -223,7 +226,10 @@ namespace mbts.spark {
.SelectManyInSeries(a => _model.order_gateway.on_security_positions(security.Id)
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
return Disposable.Create(
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
settings.OnDispatcher().Subscribe(s => {
_sticky_algos.SetItems(s.favorite_algos.stickied_algos);
order_properties_vm.on_favorite_algos(s.favorite_algos);
}),
order_properties_vm,
position_src.OnDispatcher().Subscribe(pos => {
double total, day;
@@ -400,20 +406,19 @@ namespace mbts.spark {
settings_invalidated?.Invoke();
}
public void send_order((double price, Side? side, MouseButton button) args) {
if (order_properties_vm == null || args.button == MouseButton.Middle)
return;
var size_from_alternate = args.button == MouseButton.Right;
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && current_settings.alternate_order_size == 0)) {
return;
}
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
}
else {
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
send_order_async(args.side.Value, size, args.price).ignore();
public Task send_order_async((double price, Side? side, MouseButton button) args) {
if (order_properties_vm != null && args.button != MouseButton.Middle) {
var size_from_alternate = args.button == MouseButton.Right;
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || current_settings.alternate_order_size > 0)) {
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
} else {
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
return send_order_async(args.side.Value, size, args.price);
}
}
}
return Task.CompletedTask;
}
public async Task send_order_async(Side side, uint size, double price) {
@@ -433,6 +438,13 @@ namespace mbts.spark {
await _model.order_gateway.send_order(order, price, size);
if (auto_clear)
size_calculator.value = 0;
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
if (fav_algo != null && !_sticky_algos.Contains(fav_algo.key) && !order_properties_vm.try_pop_selected_algo_definition()) {
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
order_properties_vm.selected_algo_definition = r;
}
}
}
void _update_header() {
@@ -805,6 +805,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding DataContext.remove_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding algo_definitions_view}" SelectedItem="{Binding algo_definition}">
@@ -814,6 +815,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
</Grid>
</DataTemplate>
</u:KeyedTemplateSelector.Default>
@@ -823,6 +825,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" HorizontalAlignment="Stretch" Command="{Binding DataContext.add_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}">Add</Button>
</Grid>
@@ -60,6 +60,7 @@ namespace mbts.spark {
readonly ListCollectionView<AlgoDefinitionVM> _algo_definitions_view, _favorite_algo_definitions_view;
readonly TableUpdateBuffer<AlgoData, AlgoKey> _definitions_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(d => d.key);
readonly ObservableAggregateCollection<AlgoParamRowVM> _algo_param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
readonly AlgoDefinitionVM the_anchor_algo_def_vm;
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _algo_metadata = DictionaryHelper.empty<AlgoKey, AlgoDefinitionMetadata>();
IReadOnlyDictionary<AlgoKey, int> _favorite_algos_index = DictionaryHelper.empty<AlgoKey, int>();
IDisposable _algo_param_rows_hook;
@@ -68,6 +69,7 @@ namespace mbts.spark {
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityMD> security_md_source, IObservable<InlineOrderProperties> order_properties) {
_model = model;
the_anchor_algo_def_vm = AlgoDefinitionVM.create(_model, OrderDataHelper.Defaults.AlgoData(CustomOrderDefinition.CustomOneofCase.Limit));
var security_source = security_md_source.Select(smd => smd.security);
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
security_source = _security.observe_current(true);
@@ -237,11 +239,15 @@ namespace mbts.spark {
void _selected_algo_definition_Changed(AlgoDefinitionVM old_value, AlgoDefinitionVM new_value, SelectionReason reason) {
Disposable.dispose(ref _algo_param_rows_hook);
if (old_value != null)
// Taha feels that we should always revert to a Limit order either when the user explicity unchecks a sticky button
// or when a non-sticky button is pressed. Rather than completely remove the stack at this time (b/c it may be come
// back in the future), let's just always push a specfic def_vm.key onto it [Limit] ;)
//_selected_algo_stack.Push(old_value.key);
_selected_algo_stack.Push(the_anchor_algo_def_vm.key);
if (new_value != null) {
if (use_algo_param_grid)
_algo_param_rows_hook = _connect_algo_param_rows(new_value, _algo_param_rows);
if (reason != SelectionReason.Explicit)
_selected_algo_stack.Push(new_value.key);
}
if (open_algo_popup_on_select) {
if (reason == SelectionReason.UserSelectedNewValue && new_value?.algo_root is IAlgoVM ar && ar.can_expand)
@@ -277,6 +283,18 @@ namespace mbts.spark {
get => _selected_algo_definition.selected_item;
set => _selected_algo_definition.selected_item = value;
}
public bool try_get_algo_definition(AlgoKey a, out AlgoDefinitionVM result) =>
_algo_definitions.TryGetValue(a, out result);
public bool try_pop_selected_algo_definition() {
if (_selected_algo_stack.TryPop(out AlgoKey k) && _algo_definitions.TryGetValue(k, out AlgoDefinitionVM vm)) {
_selected_algo_definition.select_explicit(vm);
return true;
}
return false;
}
public AlgoDefinitionVM selected_favorite_algo_definition {
get => selected_algo_definition?.is_favorite ?? false ? selected_algo_definition : null;
set {
@@ -353,6 +371,7 @@ namespace mbts.spark {
d.favorite_index = _get_favorite_algo_index(d.key);
}
favorite_algos_column_count = value.column_count;
RaisePropertyChanged(nameof(selected_favorite_algo_definition));
}
static IDisposable _connect_algo_param_rows(AlgoDefinitionVM def, ObservableAggregateCollection<AlgoParamRowVM> rows) =>
@@ -371,4 +390,4 @@ namespace mbts.spark {
algo_definitions_view.Refresh();
}
}
}
}
@@ -34,13 +34,14 @@ namespace bts.shell {
Disposable.dispose(ref _dispose);
}
public abstract Task<IShellWindow> load(T state);
protected virtual Task<IShellWindow> try_upgrade_state(Stream load_stream, int version) => null;
public Task<IShellWindow> load_window(Stream load_stream, int version) {
if (version == this.version) {
var t = ProtoBuf.Serializer.DeserializeWithLengthPrefix<T>(load_stream, ProtoBuf.PrefixStyle.Fixed32);
return load(t);
}
return null;
return try_upgrade_state(load_stream, version);
}
public void save(T data, Stream output) {
var header = new ShellSerializationHeader() {
@@ -687,7 +687,10 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">Tick Offset</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" >Tick Offset</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="How many ticks off the inside market to deploy the order, this guards against crossing the market inadventently" />
</StackPanel>
<c:TickUpDownUInt x:Name="PART_TickOffsetOnDeploy_TickUpDownUInt" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
@@ -905,7 +908,10 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Show Quantity</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Show Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="Maximum size to show on GLBX, this is the exchange provided iceberg" />
</StackPanel>
<c:TickUpDownNullableUInt x:Name="PART_MaxShowQuantity_TickUpDownNullableUInt" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding max_show_quantity}"/>
</Grid>
</DataTemplate>
@@ -944,7 +950,10 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Show Quantity</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Show Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="Maximum size to show on exchange, this is the BTS provided iceberg" />
</StackPanel>
<c:TickUpDownDouble x:Name="PART_ShowQuantity_TickUpDownDouble" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding show_quantity}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
@@ -1001,6 +1010,8 @@
LegTuples="{Binding leg_tuples}"
RemoveLegCommand="{Binding remove_leg}"
ContractPickerModel="{Binding contract_picker_model}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
/>
<c:DialogSkirt Grid.Row="1" Margin="-4" VerticalAlignment="Top" RightItems="{Binding skirt_items}"/>
</Grid>
@@ -1020,9 +1031,16 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Expose Quantity</TextBlock>
<StackPanel Orientation="Horizontal" Grid.Row="0" >
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Expose Quantity</TextBlock>
<c:HelpButton Margin="4,0,0,0" ToolTip="The quantity we want visible on the exchange" />
</StackPanel>
<c:TickUpDownUInt Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding exposed}"/>
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Order Quantity</TextBlock>
<StackPanel Orientation="Horizontal" Grid.Row="1" >
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Order Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="No single order will have a quantity greater than this quantity (Clip Size)" />
</StackPanel>
<c:TickUpDownUInt Grid.Row="1" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding max_order_qty}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
@@ -236,12 +236,12 @@ namespace mbts.ui {
throw new NotImplementedException("This ICommand does not implement IRaiseCanExecuteChanged");
}
public static async Task ExecuteAsync(this ICommand command, object parameter) {
public static Task ExecuteAsync(this ICommand command, object parameter) {
ExceptionAssert.Argument.NotNull(command, nameof(command));
if (command is IAsyncCommand acmd)
await acmd.Execute(parameter);
return acmd.Execute(parameter);
else
await Task.Run(() => command.Execute(parameter));
return Task.Run(() => command.Execute(parameter));
}
public static void TryExecuteIfCan(this ICommand command, object parameter) {
@@ -10,8 +10,8 @@
xmlns:autocomplete="clr-namespace:WPFTextBoxAutoComplete;assembly=WPFTextBoxAutoComplete"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Name="_login_view"
Width="460"
Height="254"
Width="520"
Height="230"
WindowStyle="None"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
@@ -27,7 +27,7 @@
</ui:StateManager.Groups>
<Window.Resources>
<ResourceDictionary>
<sys:Double x:Key="LoginControlPaddingLeft">40</sys:Double>
<sys:Double x:Key="LoginControlPaddingLeft">10</sys:Double>
<sys:Double x:Key="LoginControlPaddingTop">10</sys:Double>
<sys:Double x:Key="LoginControlPaddingRight">22</sys:Double>
<converters:BooleanToVisibilityConverter x:Key="InverseBoolToVisibility" VisibilityForFalse="Visible" VisibilityForTrue="Collapsed"/>
@@ -206,7 +206,7 @@
x:Name="_error_control"
Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
HorizontalAlignment="Left"
Visibility="Hidden"
Opacity="0"
>
@@ -251,10 +251,10 @@
<c:DialogSkirt x:Name="_dialog_skirt" Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button x:Name="_login_button" Width="60" Margin="10" IsDefault="True" IsHitTestVisible="True" Command="{Binding LoginCommand}">
<Button x:Name="_login_button" Width="60" Margin="10,0" IsDefault="True" IsHitTestVisible="True" Command="{Binding LoginCommand}">
Login
</Button>
<Button x:Name="_cancel_button" Width="60" Margin="10" IsCancel="True" IsHitTestVisible="True" Command="{Binding CancelCommand}">
<Button x:Name="_cancel_button" Width="60" Margin="10,0" IsCancel="True" IsHitTestVisible="True" Command="{Binding CancelCommand}">
Cancel
</Button>
</x:Array>
@@ -136,8 +136,8 @@ namespace mbts.spark {
var skip_local = profiles.WrapSkip(1);
var skip_local_wrapper = skip_local.WrapSelector(
p => new SettingsProfile<T>(p.id, p.name, p.value.create(), p.security_criteria),
ns => new ProfileVM(ns.id, vm_factory(ns.value), ns.name, ns.security_criteria));
profiles.Add(_local_profile = new ProfileVM(0, vm_factory(init), "Custom", template_key: ShellSettingsDialog.LocalProfileItemTemplateKey));
ns => new ProfileVM(ns.id, vm_factory(ns.value), ns.name, ns.security_criteria, target_value: ns.value));
profiles.Add(_local_profile = new ProfileVM(0, vm_factory(init), "Custom", template_key: ShellSettingsDialog.LocalProfileItemTemplateKey, target_value: init));
_selected_profile = backing(nameof(selected_profile), _local_profile);
// dialog skirt items
_apply_item = new DialogSkirtItem("Apply", new DelegateCommand(apply));
@@ -325,26 +325,37 @@ namespace mbts.spark {
//
SecurityCriteria _security_criteria;
SettingsProfile<T> _committed, _last_result;
T _target_value;
string _name, _template_key;
bool _is_selected;
IDisposable _dispose;
public ProfileVM(uint id, TVM value, string name, SecurityCriteria security_criteria = null, string template_key = null) {
public ProfileVM(uint id, TVM value, string name, SecurityCriteria security_criteria = null, string template_key = null, T target_value = null) {
this.id = id;
this.value = value;
_name = name;
_security_criteria = security_criteria;
_template_key = template_key;
_target_value = target_value;
_committed = _last_result = create();
_is_dirty = backing(nameof(is_dirty), () => !_last_result.Equals(_committed));
_refresh_is_dirty = new DeferrableActionWrapper(_is_dirty.refresh);
var sub = value.Subscribe(v => {
_last_result = _last_result.with(value: v);
_refresh_is_dirty.execute();
_on_value_changed();
});
_dispose = value is IDisposable d ? Disposable.Create(sub, d) : sub;
}
void _on_value_changed() {
if (_target_value != null && is_dirty && _last_result.value.Equals(_target_value)) {
_target_value = null;
_committed = _last_result;
_refresh_is_dirty.execute();
}
}
public uint id { get; }
public TVM value { get; }
public string name {
@@ -380,21 +391,26 @@ namespace mbts.spark {
/// assign directly to <see cref="_last_result"/>, to capture props that aren't visible in the dialog (i.e., props that are manipulated in the main window)
_last_result = data;
}
_on_value_changed();
}
public void load_value_from(T value) {
using (_refresh_is_dirty.defer_execution())
this.value.load_from(value);
_on_value_changed();
}
public void commit(SettingsProfile<T> data) {
using (_refresh_is_dirty.defer_execution())
_load_from(_committed = data);
_on_value_changed();
}
public void commit_current() {
_committed = _last_result;
_target_value = _committed.value;
_refresh_is_dirty.execute();
_on_value_changed();
}
public void revert() {
@@ -402,6 +418,7 @@ namespace mbts.spark {
_load_from(_committed);
_last_result = _committed;
}
_on_value_changed();
}
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
@@ -414,6 +431,7 @@ namespace mbts.spark {
public override string ToString() => _name;
void _load_from(SettingsProfile<T> data) {
_target_value = data.value;
name = data.name;
security_criteria = data.security_criteria;
value.load_from(data.value);
+1 -12
View File
@@ -87,22 +87,11 @@ Example:
### Excel
```
spark_sdk\bin\Debug\net471> start spark_excel-addin64-packed.xll
spark_sdk\bin\Debug\net471> start spark_excel-addin64.xll
```
* This will start excel and load the bts addin
* Check the [```excel```](excel/) directory for an example spreadsheet
### Dotnet core
* Right now we only support c# Windows 10 clients, but the package targets both net471 and .net core 2.1.6, so in theory one could run a .net core or c++ client on linux with a little work.
* https://dotnet.microsoft.com/
```
spark_client_sdk\src\spark_sdk> dotnet build
...> dotnet spark_sdk.dll
```
* Send us an email for help: api_support@bluetradesys.com
+1 -1
View File
@@ -142,7 +142,7 @@ namespace bts.sdk
Console.WriteLine("This command subscribes to market data for an exchange-type-symbol channel and writes to console.");
Console.WriteLine("Marketdata is read off the wire on the same thread as is used to read and print");
Console.WriteLine(" example: md GLBX Future ES --user=bts --pass=password");
Console.WriteLine(" md GLBX Future ES ESZ8 --user=bts --pass=password");
Console.WriteLine(" md SIM Future ES SIM:ESZ8 --user=bts --pass=password");
Console.WriteLine(" md GLBX Spread ES --user=bts --pass=password");
Environment.Exit(1);
}
@@ -6,9 +6,13 @@
#include "lib/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderLegData.h"
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderBookkeepingData.h"
#include "lib/orderman/orders/spreader_orders/spreader/NLegsHedgeRecompData.h"
#include "lib/orderman/events/SettlementUpdateEvent.h"
#include "lib/tracing/bts_trace.h"
#include "lib/utils/helper_macros.h"
#include "lib/config/config.h"
#include "lib/config/protos/spreader.pb.h"
#include "lib/os/os.h"
namespace bts {
namespace orderman {
@@ -337,7 +341,7 @@ bool SpreaderOrder::_all_hedger_orders_in_terminal_state(){
}
bool SpreaderOrder::_any_quoter_in_irrecoverable_error() const{
for (size_t q_idx = 0; q_idx < _bookkeeping_data().n_legs(); q_idx++){
const SpreaderLegData& leg_data = _bookkeeping_data().get_leg_data(q_idx);
const SpreaderLegData& leg_data = _bookkeeping_data().get_leg_data(q_idx);
if (leg_data.leg_property_data().is_active_quoting())
if (leg_data.quoter().irrecoverable_error())
return true;
@@ -373,6 +377,28 @@ void SpreaderOrder::_send_hedge(size_t hedge_leg_index,
hedge_price,
hedge_qty);
}
void SpreaderOrder::_compute_nlegs_crossed_hedges(size_t filled_leg_index,
Price filled_price,
double filled_quantity){
SpreaderBookkeepingData& bk_data = _bookkeeping_data();
NLegsHedgeRecompData& recomp_data = bk_data.hedge_recomp_data();
recomp_data.set_last_call_succ(false);
if(recomp_data.activated){
const LegPropertyData& q_leg_data =
bk_data.get_leg_data(filled_leg_index).leg_property_data();
const ISpreaderManager& spreader = *this;
bool is_succ =
SpreaderUtils::compute_nlegs_crossed_hedges(bk_data.side(),
bk_data.effective_price(),
bk_data.n_legs(),
q_leg_data,
filled_price,
filled_quantity,
spreader,
recomp_data.hedges());
recomp_data.set_last_call_succ(is_succ);
}
}
void SpreaderOrder::_compute_hedge_price_quantity(size_t filled_leg_index,
Price fill_price,
double fill_quantity,
@@ -392,16 +418,22 @@ void SpreaderOrder::_compute_hedge_price_quantity(size_t filled_leg_index,
computed_hedge_price,
hedge_quantity);
else{
const QuoteLegDriver& driver_data = filled_leg_data.quoter().driver_data(quote_order_price, hedge_leg_index);
// QuoteLegDriver driver_data;
// driver_data.set(quote_order_price,Quantity(1),Quantity(1));
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);
NLegsHedgeRecompData& recomp_data = _bookkeeping_data().hedge_recomp_data();
if(recomp_data.activated && recomp_data.last_call_succ()){
auto& crossed_hedge = recomp_data.hedges()[hedge_leg_index];
computed_hedge_price = crossed_hedge.first;
hedge_quantity = crossed_hedge.second;
}
else{
const QuoteLegDriver& driver_data = filled_leg_data.quoter().driver_data(quote_order_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(),
@@ -18,8 +18,6 @@
#include "lib/orderman/events/OrderStateUpdateEvent.h"
#include "lib/orderman/events/BookUpdateEvent.h"
#include "lib/mercury/events/TimerEvent.h"
#include "lib/config/config.h"
#include "lib/config/protos/spreader.pb.h"
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderCacheWarmingState.h"
namespace bts {
@@ -63,7 +61,7 @@ private:
// this is the main data
bool _cancel_hedges_on_cancel;
// helps with forced calls, and all the artificiality of these
// calls are trapped here
// calls are trapped here
CacheWarmingState _cw_helper;
// timeout before cancel waiting goes to end
const int64_t _cancel_timeout;
@@ -236,6 +234,13 @@ private:
void _send_hedge(size_t hedge_leg_index,
Price hedge_price,
Quantity hedge_qty);
// computes the hedges in the case of nlegs
// from crossing the hedge markets. If the
// crossing doesnt result in better hedge prices
// 0's are stored in the qty's in
void _compute_nlegs_crossed_hedges(size_t filled_leg_index,
Price filled_price,
double filled_quantity);
void _compute_hedge_price_quantity(size_t filled_leg_index,
Price filled_price,
double filled_quantity,
@@ -0,0 +1,23 @@
#include "NLegsHedgeRecompData.h"
#include "lib/config/config.h"
#include "lib/config/protos/spreader.pb.h"
namespace bts{
namespace orderman {
NLegsHedgeRecompData::NLegsHedgeRecompData(
size_t n_legs,
bts::spark::ExecutionStrategy::HedgePricing hedge_pricing)
:
_last_call_succ(false),
_hedges(),
activated(n_legs > 2 &&
hedge_pricing == bts::spark::ExecutionStrategy::RECALCULATE_CROSSES){
if(activated){
_hedges.reserve(n_legs);
for(size_t i=0; i<n_legs; i++)
_hedges.emplace_back(std::pair<Price, double>(os::nan(),0.0));
}
}
}}
@@ -0,0 +1,44 @@
#pragma once
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/sparkproto/SparkProtos.pb.h"
namespace bts{
namespace orderman {
// This is the data related to recomputing the hedges
// for >2 legs on a fill.
// When this is activated, then on a fill, the hedge markets
// are analyzed to reduce the chances of getting legged
class NLegsHedgeRecompData{
private:
bool _last_call_succ;
// this is used as an out variable to the function
// that calculates the hedges based on crossing the
// hedge markets.
// this could have been a return variable from the
// function, but this is allocated once and reused to
// avoid allocations in the critical path
std::vector<std::pair<Price,double>> _hedges;
public:
// activated if > 2 legs and configured
const bool activated;
public:
NLegsHedgeRecompData(size_t n_legs,
bts::spark::ExecutionStrategy::HedgePricing hedge_pricing);
NLegsHedgeRecompData() = delete;
NLegsHedgeRecompData(const NLegsHedgeRecompData&) = delete;
NLegsHedgeRecompData(NLegsHedgeRecompData&&) = delete;
NLegsHedgeRecompData& operator=(const NLegsHedgeRecompData&) = delete;
NLegsHedgeRecompData& operator=(NLegsHedgeRecompData&&) = delete;
bool last_call_succ() const{
return _last_call_succ;
}
std::vector<std::pair<Price,double>>& hedges(){
return _hedges;
}
void set_last_call_succ (bool val){
_last_call_succ = val;
}
};
}}
@@ -1,4 +1,5 @@
#include "SpreaderBookkeepingData.h"
#include "lib/os/os.h"
namespace bts{
namespace orderman{
@@ -18,8 +19,11 @@ SpreaderBookkeepingData::SpreaderBookkeepingData (
_inited_new (inited_new),
_n_legs(legs.size()),
_n_quoting_legs(_calc_n_quoting_legs(legs)),
_side(order_def.side()),
_order_def(order_def),
_reported_state(){
_reported_state(),
_hedge_recomp_data(legs.size(),
order_def.custom_def().spreader().execution_strategy().hedge_pricing()){
LOG(OrderMan, Info, "SpreaderBookkeepingData.cons started "
"key: {} n_legs: {}",
spreader_order_key.value(), _n_legs);
@@ -10,9 +10,10 @@
#include "lib/orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.h"
#include "lib/orderman/orders/spreader_orders/spreader/hedger/UnhedgedDelta.h"
#include "lib/orderman/orders/spreader_orders/spreader/hedger/HedgerManager.h"
#include "NLegsHedgeRecompData.h"
namespace bts{
namespace orderman {
namespace orderman {
class SingleFillHedger;
// encapsulates, organizes and provides easy access
// and lookups for data needed by the spreader.
@@ -24,6 +25,7 @@ private:
const bool _inited_new;
const size_t _n_legs;
const size_t _n_quoting_legs;
const api::Side _side;
// when the order is not created from the disk
// (ie created live) this is set to true
const api::orderman::OrderDefinition _order_def;
@@ -35,6 +37,7 @@ private:
Price _input_price;
Price _effective_price;
Quantity _quantity;
NLegsHedgeRecompData _hedge_recomp_data;
private:
size_t _calc_n_quoting_legs(const std::vector<LegPropertyData>& legs);
public:
@@ -61,10 +64,14 @@ public:
/////////////////////////////////
size_t n_legs() const { return _n_legs; }
bool all_legs_resolved() const {return n_legs() > 0;}
api::Side side() const {return _side;}
const api::orderman::OrderDefinition& order_def() const {return _order_def;}
Price input_price () const {return _input_price;}
Price effective_price () const {return _effective_price;}
Quantity quantity () const {return _quantity;}
NLegsHedgeRecompData& hedge_recomp_data(){
return _hedge_recomp_data;
}
// gets leg data from leg's sec key
SpreaderLegData& get_leg_data (SecurityKey security_key){
boost::container::flat_map<uint32_t, size_t>::const_iterator l_it = _sec_key_to_index.find(security_key.value());
@@ -794,6 +794,58 @@ public:
quote_fill_quantity,
hedge_leg);
}
// Given a quoting leg fill (ie leg id, fill price and
// fill qty), this function calculates the hedge prices,
// computed by crossing into the hedge market to get the
// required fill size.
// Function returns false if
// i. one of the hedging markets can not support the hedging
// size requirement
// ii. the computed spread price =
// q_leg_ratio*q_fill_price + sum(hedge_leg_ratio*hedge_cross_price)
// is worse than the spreader price
static bool compute_nlegs_crossed_hedges(
api::Side spr_side,
Price spr_price,
size_t n_legs,
const LegPropertyData& q_leg,
Price q_fill_price,
double q_fill_qty,
const ISpreaderManager& spreader,
std::vector<std::pair<Price,double>>& out_drivers){
// spr_cross_price = q_leg_ratio*q_fill_price +
// sum(hedge_leg_ratio*hedge_cross_price)
double spr_cross_price = q_fill_price.value()*q_leg.price_multiplier();
for (size_t i=0;i<n_legs;i++)
if(i!=q_leg.leg_index()){
Price hedge_price;
Quantity hedge_qty;
const auto& hedge_leg=spreader.get_leg_data(i);
double hedge_qty_needed_raw =
get_leg_quantity_from_another_leg_quantity(q_leg,
q_fill_qty,
hedge_leg);
Quantity hedge_qty_needed_rounded_up =
Quantity(_round_hedge_target(hedge_qty_needed_raw));
// since it is being hedged, we look at the opp side
api::Side hedge_market_side = invert_side(hedge_leg.trading_side());
traverse_depth_for_quantity(
spreader.get_book(i),
hedge_market_side,
hedge_qty_needed_rounded_up,
hedge_price,
hedge_qty);
if(hedge_qty.value()<hedge_qty_needed_rounded_up.value())
return false;
spr_cross_price += hedge_price.value()*hedge_leg.price_multiplier();
auto& driver_i=out_drivers[i];
driver_i.first=hedge_price;
driver_i.second=hedge_qty_needed_raw;
}
return securities::is_equal_or_better(spr_side,
spr_price,
Price(spr_cross_price));
}
////////////////////////////////////////////////////
// Rounding functions //////////////////////////////
////////////////////////////////////////////////////
@@ -99,6 +99,15 @@ void SpreaderWorkingStateWorker::on_quoter_order_fill(SpreaderLegData& leg_data)
// getting overfilled
Quantity filled_qty=cw.comp_fill_qty(updated_order);
bts_assert (filled_qty.value() > 0);
// this is a round about way to do this, so we
// dont have to change the structure of this code.
// this function fills in the value of the cross hedges
// in _bookkeeping_data().nlegs_crossed_hedges(), which
// is later retreived in _compute_hedge_price_quantity
_spreader->_compute_nlegs_crossed_hedges(
leg_data.leg_property_data().leg_index(),
updated_order.last_update_filled_price(),
filled_qty.value());
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()){
SpreaderLegData& other_leg_data =
@@ -2614,6 +2614,237 @@ TEST_CASE ("nlegs_compute_driver", "[spreader]"){
REQUIRE(std::abs(hedge_quantity - 3.333333) < 0.000001);
}
}
TEST_CASE ("nlegs_crossed_hedges", "[spreader]"){
SECTION ("buy"){
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, 2021, 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, 2021, 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, 2021, 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, 2021, 4, 15, -1.0, -1.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, -1.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;
std::vector<bts::api::orderman::OrderDefinition> quoter_ord_defs,
hedger_ord_defs;
SpreaderUtils::resolve_synthetic_spread(pao_services, order_def, leg_props,quoter_ord_defs,hedger_ord_defs);
SpreaderManagerForTest spreader_manager (order_key, order_def, pao_services, leg_props,quoter_ord_defs,hedger_ord_defs);
SpreaderBookkeepingData& bookkeeping_data = spreader_manager.bookkeeping_data();
fill_book(spreader_manager, 0, new double[5]{65.31,65.32,65.34,65.36, 65.40},
new uint16_t[5]{12,18,31,19,20},
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]{35,5,20,12,28},
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,3,2,4,80},
bts::api::Side::SIDE_SELL,
true);
// buying at -0.03
std::vector<std::pair<Price,double>> drivers;
drivers.reserve(4);
// not enough to hedge
bool res = SpreaderUtils::compute_nlegs_crossed_hedges(
api::Side::SIDE_BUY,
Price(-0.03),
4,
spreader_manager.get_leg_data(3),
Price(193.99),
101,
spreader_manager,
drivers);
REQUIRE(!res);
// 65.34 + 64.41 + 64.21 - 193.99 = -0.03
res = SpreaderUtils::compute_nlegs_crossed_hedges(
api::Side::SIDE_BUY,
Price(-0.03),
4,
spreader_manager.get_leg_data(3),
Price(193.99),
40,
spreader_manager,
drivers);
REQUIRE(res);
REQUIRE(securities::is_same(drivers[0].first,Price(65.34)));
REQUIRE(securities::is_same(drivers[1].first,Price(64.41)));
REQUIRE(securities::is_same(drivers[2].first,Price(64.21)));
REQUIRE(securities::is_same(Price(drivers[0].second),Price(40)));
REQUIRE(securities::is_same(Price(drivers[1].second),Price(40)));
REQUIRE(securities::is_same(Price(drivers[2].second),Price(40)));
// quote gets better fill
res = SpreaderUtils::compute_nlegs_crossed_hedges(
api::Side::SIDE_BUY,
Price(-0.03),
4,
spreader_manager.get_leg_data(3),
Price(193.99+0.01),
40,
spreader_manager,
drivers);
REQUIRE(res);
// quote gets worse fill
res = SpreaderUtils::compute_nlegs_crossed_hedges(
api::Side::SIDE_BUY,
Price(-0.03),
4,
spreader_manager.get_leg_data(3),
Price(193.99-0.01),
40,
spreader_manager,
drivers);
REQUIRE(!res);
// // generalize the above
for (int i=0;i<1000;i++){
double quote_fill_price=gen_rand(193.99-1.0,193.99+1.0,0.01);
res = SpreaderUtils::compute_nlegs_crossed_hedges(
api::Side::SIDE_BUY,
Price(-0.03),
4,
spreader_manager.get_leg_data(3),
Price(quote_fill_price),
40,
spreader_manager,
drivers);
if(quote_fill_price>=193.99)
REQUIRE(res);
else
REQUIRE(!res);
}
}
SECTION ("sell"){
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, 2021, 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, 2021, 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, 2021, 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, 2021, 4, 15, -1.0, -1.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, -1.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_SELL, 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;
std::vector<bts::api::orderman::OrderDefinition> quoter_ord_defs,
hedger_ord_defs;
SpreaderUtils::resolve_synthetic_spread(pao_services, order_def, leg_props,quoter_ord_defs,hedger_ord_defs);
SpreaderManagerForTest spreader_manager (order_key, order_def, pao_services, leg_props,quoter_ord_defs,hedger_ord_defs);
SpreaderBookkeepingData& bookkeeping_data = spreader_manager.bookkeeping_data();
fill_book(spreader_manager, 0, new double[5]{62.1, 62, 61.99, 61.95, 61.93},
new uint16_t[5]{18,12,30,19,21},
bts::api::Side::SIDE_BUY,
true);
fill_book(spreader_manager, 1, new double[5]{63.2, 63.15, 62.99, 62.93, 61.90},
new uint16_t[5]{11,15,2,32,40},
bts::api::Side::SIDE_BUY,
true);
fill_book(spreader_manager, 2, new double[5]{65.2, 65.18, 64.98, 64.93, 64.90},
new uint16_t[5]{11,12,19,9,50},
bts::api::Side::SIDE_BUY,
true);
// selling at 0.02, fill of 101
// 61.93 + 61.90 + 64.90 - 189.83 = -1.1
std::vector<std::pair<Price,double>> drivers;
drivers.reserve(4);
// not enough to hedge
bool res = SpreaderUtils::compute_nlegs_crossed_hedges(
api::Side::SIDE_SELL,
Price(0.02),
4,
spreader_manager.get_leg_data(3),
Price(189.83),
101,
spreader_manager,
drivers);
REQUIRE(!res);
// selling at 0.02, fill of 50 @ 189.83
// 61.99 + 62.93 + 64.93 = 189.85
// 189.85 - 189.83 = 0.02
res = SpreaderUtils::compute_nlegs_crossed_hedges(
api::Side::SIDE_SELL,
Price(0.02),
4,
spreader_manager.get_leg_data(3),
Price(189.83),
50,
spreader_manager,
drivers);
REQUIRE(res);
// quote gets better fill
// 189.85 - x >= 0.02
// x <= 189.83
res = SpreaderUtils::compute_nlegs_crossed_hedges(
api::Side::SIDE_SELL,
Price(0.02),
4,
spreader_manager.get_leg_data(3),
Price(189.83-0.01),
50,
spreader_manager,
drivers);
REQUIRE(res);
// quote gets worse fill
res = SpreaderUtils::compute_nlegs_crossed_hedges(
api::Side::SIDE_SELL,
Price(0.02),
4,
spreader_manager.get_leg_data(3),
Price(189.83+0.01),
50,
spreader_manager,
drivers);
REQUIRE(!res);
// generalize the above
for (int i=0;i<1000;i++){
double quote_fill_price=gen_rand(189.83-1.0,189.83+1.0,0.01);
res = SpreaderUtils::compute_nlegs_crossed_hedges(
api::Side::SIDE_SELL,
Price(0.02),
4,
spreader_manager.get_leg_data(3),
Price(quote_fill_price),
50,
spreader_manager,
drivers);
if(quote_fill_price<=189.83)
REQUIRE(res);
else
REQUIRE(!res);
}
}
}
TEST_CASE ("quote driver caching", "[spreader]"){
SECTION("zero_search"){
QuoteDriverSetHistory driver_cache(5,10,0.05);
@@ -2834,6 +3065,7 @@ TEST_CASE ("spreader_perf_2legs", "[spreader]")
}
TEST_CASE ("spreader_perf_4legs", "[spreader]")
{
bool recomp = gen_rand(0,10) > 5;
bts::orderman::OrderKey order_key(0);
Price spreader_price = Price (40);
Quantity spreader_quantity = Quantity (250);
@@ -3016,7 +3248,7 @@ TEST_CASE ("spreader_perf_4legs", "[spreader]")
0.005));
REQUIRE(securities::test_price_equality(
bookkeeping_data.get_leg_data (2).hedger_manager().test_only_get_hedger_at (0).order_state().out_price().value(),
19.995,
recomp ? 19.990 : 19.995,
0.005));
// back to leg1's hedger, this is what will
// be mainly tested here on
@@ -3025,7 +3257,7 @@ TEST_CASE ("spreader_perf_4legs", "[spreader]")
REQUIRE(hedger_1_1.order_state().out_quantity().value() == 1);
REQUIRE(securities::test_price_equality(
hedger_1_1.order_state().out_price().value(),
20.015,
recomp ? 20.005 : 20.015,
0.005));
REQUIRE(hedger_1_1.state()==HedgerState::WAITING_FOR_FIRST_ACK);
spreader.test_only_on_child_order_update (hedger_1_1.order_state().order_key(),
@@ -3060,7 +3292,7 @@ TEST_CASE ("spreader_perf_4legs", "[spreader]")
REQUIRE(hedger_1_1.order_state().out_quantity().value() == 1);
REQUIRE(securities::test_price_equality(
hedger_1_1.order_state().out_price().value(),
20.015,
recomp ? 20.005 : 20.015,
0.005));
REQUIRE(hedger_1_1.state()==HedgerState::WAITING_FOR_FIRST_ACK);
spreader.test_only_on_child_order_update (hedger_1_1.order_state().order_key(),
@@ -3097,7 +3329,7 @@ TEST_CASE ("spreader_perf_4legs", "[spreader]")
REQUIRE(h.order_state().out_quantity().value()==2);
REQUIRE(securities::test_price_equality(
h.order_state().out_price().value(),
20.015,
recomp ? 20.005 : 20.015,
0.005));
}
// send order acks //////////
@@ -3112,7 +3344,7 @@ TEST_CASE ("spreader_perf_4legs", "[spreader]")
REQUIRE(h.order_state().out_quantity().value()==2);
REQUIRE(securities::test_price_equality(
h.order_state().out_price().value(),
20.015,
recomp ? 20.005 : 20.015,
0.005));
}
REQUIRE(hm_1.n_active_hedgers()==4);
@@ -3129,7 +3361,7 @@ TEST_CASE ("spreader_perf_4legs", "[spreader]")
REQUIRE(h.order_state().out_quantity().value()==2);
REQUIRE(securities::test_price_equality(
h.order_state().out_price().value(),
20.015,
recomp ? 20.005 : 20.015,
0.005));
}
REQUIRE(hm_1.n_active_hedgers()==4);
@@ -3155,60 +3387,6 @@ TEST_CASE ("spreader_perf_4legs", "[spreader]")
REQUIRE(securities::test_price_equality(t_price.value(),20.03,0.005));
REQUIRE(t_qty.value()==10);
}
// SECTION ("N_requotes_1_hedge_M_times"){
// // N book updates followed by M fills
// // (does the hedge time stays the same as
// // in a tight loop in the "hedges" test ?)
// uint32_t n_runs=5,
// n_book_updates=200,
// n_hedges = 5;
// // since its the same quoting order we are filling
// // and the code downstream expects accumulated filled qty
// uint32_t total_filled_qty = 0, hi = 0;
// for (uint32_t i=0; i<n_runs; i++){
// test_sleep(5000,false);
// std::vector<double> hedge_perfs;
// hedge_perfs.reserve(n_hedges);
// auto t0=utils::TimeStamp::now();
// for (uint32_t b=0; b<n_book_updates; b++){
// spreader.test_only_on_book_update (BookUpdateEvent(book_1_alt,
// trace::CorID()));
// spreader.test_only_on_book_update (BookUpdateEvent(book_2_alt,
// trace::CorID()));
// spreader.test_only_on_book_update (BookUpdateEvent(book_3_alt,
// trace::CorID()));
// spreader.test_only_on_book_update (BookUpdateEvent(book_1,
// trace::CorID()));
// spreader.test_only_on_book_update (BookUpdateEvent(book_2,
// trace::CorID()));
// spreader.test_only_on_book_update (BookUpdateEvent(book_3,
// trace::CorID()));
// spreader.test_only_on_child_order_update (qleg_data_4.order_key(),
// api::orderman::OrderStatus::ORDER_STATUS_WORKING,
// Quantity (total_filled_qty),
// Price (20.045));
// }
// auto t1=utils::TimeStamp::now();
// double avg_bookupdate_time=(t1-t0).nanoseconds()/(1000.0 * 6*n_book_updates);
// for(uint32_t h=0; h<n_hedges; h++){
// auto t0=utils::TimeStamp::now();
// spreader.test_only_on_child_order_update (qleg_data_4.order_key(),
// api::orderman::OrderStatus::ORDER_STATUS_WORKING,
// Quantity (++total_filled_qty),
// Price (20.045));
// auto t1=utils::TimeStamp::now();
// hedge_perfs.push_back((t1-t0).nanoseconds());
// }
// for (auto d : hedge_perfs)
// std::cout << ++hi << " hedge: " << d << std::endl;
// double min, max, mean, median;
// stats(hedge_perfs,min,max,mean,median);
// std::cout << "1Q 3H legs, requoting mean=" << avg_bookupdate_time << std::endl;
// std::cout << "1Q 3H legs, hedging median=" << median << " mean=" << mean << std::endl;
// std::cout << "------------------------------------------------------" << std::endl;
// }
// }
}
TEST_CASE ("simple book addition", "[spreader]"){
SECTION ("direct, book"){
+5
View File
@@ -465,6 +465,11 @@ message ExecutionStrategy
bool deleted = 1;
map<uint32, LegSpreaderParams> leg_params_map = 20;
bool cancel_hedges_on_cancel = 30;
enum HedgePricing{
CACHED_DRIVERS = 0;
RECALCULATE_CROSSES = 1;
}
HedgePricing hedge_pricing = 40;
}
// EVERYTHING BELOW THIS LINE IS OBSOLETE
+88 -80
View File
@@ -9,11 +9,8 @@ Our offering consists of a client UI ([Spark Client](https://www.bluetradingsyst
This API extension point is intended for users that require adding functionality to our Spark Server. Using the **Spark Server SDK** users can create [new order types](https://github.com/BlueTradingSystems/spark_sdk/blob/master/examples/order_plugin_example.cpp), [new market data sources](https://github.com/BlueTradingSystems/spark_sdk/blob/master/examples/md_publisher_example.cpp) and soon new execution gateways. In this case users implement the new functionality in C++, producing a shared library that is loaded at run time by our Spark Server.
If you are interested in sending orders or otherwise controlling a spark server remotely please see the BTS [Spark Client SDK](https://github.com/BlueTradingSystems/spark_client_sdk) repo.
If you are interested in sending orders to or otherwise controlling a spark server remotely please see the BTS [Spark Client SDK](https://github.com/BlueTradingSystems/spark_client_sdk) repo.
### In Depth
* [Spark Algo Descriptions](doc/algos.md)
## Getting Started
@@ -32,118 +29,129 @@ $ docker pull bluetradingsystems/linux_docker
2) Start the docker container
```
$ docker run \
--net host \
--shm-size=1024m \
--cap-add=SYS_PTRACE --security-opt seccomp=unconfined \
-w $PWD \
-v /home:/home \
--name bts_sdk \
-i \
-t bluetradingsystems/linux_docker \
/bin/bash
--rm \
--net host \
-i \
-h $(hostname)_spark \
-v /etc/passwd:/etc/passwd \
-e "TZ=America/New_York" \
--security-opt seccomp=unconfined \
-v /var/log/syslog:/var/log/syslog \
-v /etc/group:/etc/group \
-v /tmp:/tmp \
--shm-size=2048m \
-u $UID \
-v /home:/home \
-w /home/$USER \
--name bts_sdk \
-t bluetradingsystems/linux_docker \
/bin/bash
```
* Note that the lifetime of docker instance above is tied to the life of the shell instance, so if you exit bash, the container stops running. To start and re-attach to the container, use:
```
$ docker start --attach -i bts_sdk
```
* If you prefer to run docker as a daemon, check out [``~/spark_sdk/sdk_shell.sh``](sdk_shell.sh)
### Git
* We assume the git you are using is git-lfs compatible; if you're using the git that comes with our docker, you're all set.
* else go here: https://github.com/git-lfs/git-lfs/wiki/Installation
Now that you're ssh'd into the docker container, clone the repo into ```~/spark_sdk/``` and cd to that directory
Now that you're inside the docker container, clone the repo into ```~/spark_sdk/```
```
$ cd
$ git clone https://github.com/BlueTradingSystems/spark_sdk.git
$ cd spark_sdk
~$ git clone https://github.com/BlueTradingSystems/spark_sdk.git
```
### Start the server
```
~$ cd ~/spark_sdk/lib
~/spark_sdk/lib$ ./sparkd run
```
### Connect to the Spark Admin tool via the browser
```
Browse to http://<your_ip>:8000
```
* You should see a Spark Admin login screen, enter user bts with no password
* Add or manage user trading and market data permissions by clicking the Users icon on the left side
* Add or manage accounts and risk limits by clicking on the Accounts icon on the left side
### Connect Spark Front End (Windows)
* Download spark windows executable here: <spark_client>
* Then start the client and point it at your running server:
```
> spark.exe -ConfigServer http://<your_ip>:8000/config
```
### Development
## Algo Development
Algo Orders typically implement the [IAlgoOrder](include/spark/lib/orderman/orders/algo/IAlgoOrder.h) interface to respond to events and use the [IAlgoServices](include/spark/lib/orderman/orders/algo/IAlgoServices.h) interface to create and control child orders as well as manage their own life cycles. See [examples/order_plugin_example.cpp](examples/order_plugin_example.cpp) for a fully fledged example
Look at [examples/md_publisher_example.cpp](examples/md_publisher_example.cpp) to look at how to build a custom market data producer
First kill your running sparkd by hitting ctrl-c
```
bts@spark-vm_spark:~/spark_sdk/lib$ ./sparkd run
^C
bts@spark-vm_spark:~/spark_sdk/lib$
```
run ```build_examples.sh``` to make sure you can build the examples, this will produce an ```order_plugin_example.so```
Run ```build_examples.sh``` to make sure you can build the examples, this will produce an ```order_plugin_example.so```
```
$ cd ~/spark_sdk/examples
$ ./build_examples.sh
~$ cd ~/spark_sdk/examples
~/spark_sdk/examples$ ./build_examples.sh
```
#### Server Layout
* Spark servers reside on a port, usually 8000, but your production instance could be on a different $PORT
The server process looks in the ```plugins``` directory to load these shared objects. First navigate to the plugins directory to verify the shared object (```order_plugin_example.so```) has been built
```
$ cd ~/spark_sdk/lib/plugins
~/spark_sdk/lib/plugins$ ls
order_plugin_example.so
~/spark_sdk/lib/plugins$
```
Now move back to the lib directory and bounce the server
```
~$ cd ~/spark_sdk/lib
~/spark_sdk/lib$ ./sparkd run
```
## Dev Notes
##### SDK Package Layout
* The sdk puts logs in ``~/sparkdata/data/logging/log``
* The sdk package has binaries in ``spark_skd/lib``
* Put plugins in ``spark_sdk/lib/plugins/`` for debugging
* The sdk package has binaries in ``~/spark_sdk/lib``
* Plugins go in ``~/spark_sdk/lib/plugins/``
##### Production layout
* Production Logs are in ``~/$PORT/sparkdata/data/logging/log`` on the host
* Spark servers reside on a port, usually 8000, but your production instance could be on a different PORT
* Production Logs are in ``~/PORT/sparkdata/data/logging/log`` on the host
* Production binaries are in ``/sparkbin`` inside the docker container
* Production algo plugins should go into the ``/sparkbin/plugins`` directory
#### Recorded market data
* We first check for http://mdreplay/spark/futures.coconut
* Then fall back to ``./data_files/ relative to the sparkd binary``
#### Debugging
## Debugging
Copy the order_plugin_example.so to ``spark_sdk/lib/plugins/`` and start the sparkd server process inside gdb
Use this command to start the sparkd server process inside gdb
```
$ cp -f order_plugin_example.so ~/spark_sdk/lib/plugins/
$ cd ~/spark_sdk/lib
$ gdb --args ./sparkd run
~/spark_sdk/lib$ gdb --args ./sparkd run
```
In gdb:
* https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf
```
(gdb) break order_plugin_example.cpp:366
```
## Running without debugger
```
$ cp -f order_plugin_example.so ~/spark_sdk/lib/plugins/
$ cd ~/spark_sdk/lib
$ ./sparkd run
```
## Connect to Admin (Risk) UI to set limits or add users
* Browse to http://location:$PORT
* Login as bts, no password
* Add or manage user trading and market data permissions by clicking the Users icon on the left side
* Add or manage accounts and risk limits by clicking on the Accounts icon on the left side
## Other interesting web accessible things
* When you have a running server you can:
* Browse to http://location/$PORT/config to check configuration overrides
* Browse to http://location:$PORT/perf_trace to see socket to socket latencies
* Send us an email for help: api_support@bluetradesys.com
#### Spark windows client
* Download spark windows executable here: <spark_client>
## Connect Spark windows client
* To start a spark client, use the -ConfigServer parameter to point at this new server
```
> spark.exe -ConfigServer http://location:$PORT/config
```
Or create a file called spark.conf at %APPDATA%\bts\conf\ that contains:
```
include: "http://location:$PORT/config/"
```
## Download the Spark windows client
* Download the bts installer here:
-46
View File
@@ -1,46 +0,0 @@
+++
title = "BTS Basic Algos"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## Lean
The goal of the lean order is to watch the opposite-inside market size and cross when that size goes below the threshold (this also implies a cross if that size goes to zero and the market trades away from you) If the market trades toward you, you can opt into several different behaviors
##### Parameters
* Cross Thresh - size thresh in absolute or ratio terms
* Max Chase Ticks - max levels to pay up, in ticks
## Join
The goal of the join order is to watch the opposite-inside market and join when the market flips (that is, when the best offer becomes the best bid) and has at least threshold size.
##### Parameters
* Join Thresh - size thresh in absolute or ratio terms
## Top
The goal of the top order is to not trade by yourself, but the intent at the time of order entry is to get filled.If the order is on the market (say current bid price) AND the quantity on that bid price is below top thresh then cancel the order else remain working.
##### Parameters
* Top Thresh - size thresh in absolute or ratio terms
## Cover
A Cover order’s purpose is to fire a hedge order (of any type) in response to fills from it’s child order on the opposite side of the market N ticks away
##### Parameters
* Profit Ticks - ticks above or below fill price to place profit / cover order
## OCO
An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill.
Composing this order with Cover simulates scalping functionality
##### Parameters
* Stop Ticks - ticks below or above main order to place stop / other order