Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed06924df1 | ||
|
|
b78179ccee | ||
|
|
5a00c5eb2a | ||
|
|
74fa41f151 | ||
|
|
32d5fc2c37 | ||
|
|
1049046d0b | ||
|
|
1f8e0a1e2b | ||
|
|
5d3d5251c7 | ||
|
|
f7cee30511 | ||
|
|
98690ee747 | ||
|
|
3e14d83dde | ||
|
|
746622161c | ||
|
|
c064cdd215 | ||
|
|
acce1e3f7d | ||
|
|
9bdf084838 | ||
|
|
5132f81bc1 | ||
|
|
ab797a311e | ||
|
|
d032b23841 | ||
|
|
8c68a66ef1 | ||
|
|
d617d1597c | ||
|
|
5206a378ff | ||
|
|
7d2047d7ce | ||
|
|
8a43a4d157 | ||
|
|
230e9e525f | ||
|
|
034318db5e | ||
|
|
0758dd6f46 | ||
|
|
83f9f3375c | ||
|
|
98a6616804 | ||
|
|
b46f811cb5 | ||
|
|
6d918c0962 | ||
|
|
5b5274ab5f | ||
|
|
2d5211ac9b | ||
|
|
e62d116a44 |
No files matched your search
@@ -47,7 +47,7 @@ namespace mbts.ui
|
||||
//public static ImageSource Ticket { get { return load_image("Ticket.png"); } } // replaced with AddOrders
|
||||
public static ImageSource Date { get { return load_image("Date.png"); } }
|
||||
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; /*return load_image("Cube-Molecule.png");*/ } }
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; } }
|
||||
public static ImageSource Algo { get { return Cube3d; } }
|
||||
public static ImageSource Quote { get { return Math; } }
|
||||
public static ImageSource Math { get { return (DrawingImage)_resources["Surface"]; } }
|
||||
@@ -62,9 +62,7 @@ namespace mbts.ui
|
||||
#pragma warning restore CS0618
|
||||
|
||||
// Vector icons
|
||||
public static ImageSource Favorites { get { return Favorites_Gold; } }
|
||||
public static ImageSource Favorites_Gold { get { return (DrawingImage)_resources["FavoritesGold"]; } }
|
||||
public static ImageSource Favorites_White { get { return (DrawingImage)_resources["FavoritesWhite"]; } }
|
||||
public static ImageSource Bookmarks { get { return (DrawingImage)_resources["Bookmarks"]; } }
|
||||
public static ImageSource Window { get { return (DrawingImage)_resources["Window"]; } }
|
||||
public static ImageSource ActiveWindow { get { return (DrawingImage)_resources["ActiveWindow"]; } }
|
||||
public static ImageSource Trash { get { return (DrawingImage)_resources["Trash"]; } }
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -3,33 +3,17 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mbts="clr-namespace:mbts.ui">
|
||||
<DrawingImage x:Key="FavoritesGold">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#ffd932" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingImage x:Key="FavoritesWhite">
|
||||
<DrawingImage x:Key="Bookmarks">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
<Pen Thickness="3" Brush="Black" />
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#ffd932" />
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using bts.spark;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.ui {
|
||||
[Export(typeof(IShellModule))]
|
||||
public class BookmarkShellModule : ILazyMenuModule {
|
||||
readonly WindowManager _wm;
|
||||
readonly IBookmarks _bookmarks;
|
||||
readonly ISavedWindowFactory _window_factory;
|
||||
readonly WorkspacesModel _workspaces;
|
||||
|
||||
[ImportingConstructor]
|
||||
public BookmarkShellModule(
|
||||
WorkspacesModel workspaces,
|
||||
WindowManager wm,
|
||||
IBookmarks bm,
|
||||
ISavedWindowFactory swf) {
|
||||
_workspaces = workspaces;
|
||||
_wm = wm;
|
||||
_bookmarks = bm;
|
||||
_window_factory = swf;
|
||||
}
|
||||
|
||||
public ImageSource icon => Icons.Bookmarks;
|
||||
|
||||
public string name => "My Windows";
|
||||
|
||||
public int priority => 35;
|
||||
|
||||
public IEnumerable<SparkCommand> actions {
|
||||
get {
|
||||
// The windows that are open
|
||||
var open =
|
||||
_wm.list_documents()
|
||||
.Select(td => td.ViewModel)
|
||||
.Where(vm => vm.HasCustomTitle)
|
||||
.Select(vm =>
|
||||
SparkCommand.create(vm.Title, vm.Icon, SparkTags.ActivateWindow, () => vm.Model))
|
||||
.Cast<SparkCommand>();
|
||||
|
||||
var open_set = new HashSet<string>(open.Select(c => c.title));
|
||||
|
||||
// The windows that are not open
|
||||
var closed =
|
||||
_bookmarks.bookmarks
|
||||
.Where(state => !open_set.Contains(state.title))
|
||||
.Select(state =>
|
||||
SparkCommand.create(state.title, Icons.Bookmarks, SparkTags.ShowWindowAsync,
|
||||
() => {
|
||||
_bookmarks.remove(state);
|
||||
return _window_factory.restore(state);
|
||||
}))
|
||||
.Cast<SparkCommand>();
|
||||
|
||||
// todo: The windows that are closed
|
||||
foreach (var w in open.Concat(closed).OrderBy(w => w.title)) {
|
||||
yield return w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) {
|
||||
// This module shouldn't have any windows
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.ui.shell {
|
||||
|
||||
public interface IBookmarks {
|
||||
void save_bookmark(IShellWindow window);
|
||||
void save_bookmark(WindowState window);
|
||||
void remove(WindowState window);
|
||||
void clear();
|
||||
IEnumerable<WindowState> bookmarks { get; }
|
||||
}
|
||||
|
||||
[Export(typeof(IBookmarks))]
|
||||
public class BookmarksModel : IBookmarks {
|
||||
readonly ISettingsCollector _settings_collector;
|
||||
readonly Dictionary<string, WindowState> _bookmarks = new Dictionary<string, WindowState>();
|
||||
|
||||
[ImportingConstructor]
|
||||
public BookmarksModel(ISettingsCollector settings) {
|
||||
_settings_collector = settings;
|
||||
}
|
||||
public void save_bookmark(IShellWindow window) {
|
||||
var state = _settings_collector.get_window_state(window);
|
||||
if (state != null) {
|
||||
_bookmarks[state.title] = state;
|
||||
}
|
||||
}
|
||||
public void save_bookmark(WindowState window) {
|
||||
_bookmarks[window.title] = window;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
_bookmarks.Clear();
|
||||
}
|
||||
|
||||
public void remove(WindowState window) {
|
||||
_bookmarks.Remove(window.title);
|
||||
}
|
||||
|
||||
public IEnumerable<WindowState> bookmarks => _bookmarks.Values;
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace bts.ui {
|
||||
|
||||
public int priority => 40;
|
||||
|
||||
public IEnumerable<SparkCommand> actions => new []{
|
||||
public IEnumerable<SparkCommand> actions => new SparkCommand[]{
|
||||
SparkCommands.ShowWorkspaces
|
||||
//SparkCommands.ShowSettings
|
||||
};
|
||||
|
||||
@@ -173,7 +173,8 @@ namespace mbts.spark {
|
||||
}
|
||||
_is_setting_price_mults = false;
|
||||
};
|
||||
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
|
||||
//_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
|
||||
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => true), true);
|
||||
} else {
|
||||
_can_add_leg = backing(nameof(can_add_leg), false);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace mbts.ui.tests
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
Assert.IsNotNull(Application.Current);
|
||||
Assert.IsNotNull(Application.Current.Windows);
|
||||
Assert.AreEqual(Application.Current.Windows.Count, 0);
|
||||
@@ -100,7 +100,7 @@ namespace mbts.ui.tests
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_slow_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
Assert.AreEqual(Application.Current.Windows.Count, 0);
|
||||
|
||||
// an ISlowWindow gets its own thread
|
||||
|
||||
@@ -5,6 +5,7 @@ using bts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.spark.SparkBusData;
|
||||
|
||||
namespace mbts.spark
|
||||
@@ -34,6 +35,8 @@ namespace mbts.spark
|
||||
public static ITag<(PartialCommand<SecurityMD>, SecurityCriteria)> FilterPickOne { get; } = new SparkTag<(PartialCommand<SecurityMD>, SecurityCriteria)>("FilterPick1");
|
||||
|
||||
public static ITag<IShellWindow> ShowWindow { get; } = new SparkTag<IShellWindow>("Show Window");
|
||||
public static ITag<Task<IShellWindow>> ShowWindowAsync { get; } = new SparkTag<Task<IShellWindow>>("ShowWindowAsync");
|
||||
public static ITag<IShellWindow> ActivateWindow { get; } = new SparkTag<IShellWindow>("Activate Window");
|
||||
public static ITag<Nothing> ShowWorkspaces { get; } = new SparkTag<Nothing>(nameof(ShowWorkspaces));
|
||||
public static ITag<Nothing> ShowSettings { get; } = new SparkTag<Nothing>(nameof(ShowSettings));
|
||||
public static ITag<Uri> ShowUri { get; } = new SparkTag<Uri>("ShowUri");
|
||||
|
||||
@@ -48,10 +48,10 @@ namespace System.Collections.Generic {
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<T> ToIEnumerable<T>(this T item) {
|
||||
yield return item;
|
||||
}
|
||||
// use "new [] { item }" - avoid putting this on the intellisense for every item everywhere
|
||||
//public static IEnumerable<T> ToIEnumerable<T>(this T item) {
|
||||
// yield return item;
|
||||
//}
|
||||
|
||||
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
|
||||
TAccumulate a = seed;
|
||||
|
||||
@@ -69,16 +69,18 @@ namespace mbts.ui.settings
|
||||
if (window == null || window.Owner == null) return null;
|
||||
MemoryStream out_stream = new MemoryStream();
|
||||
window.GetState(out_stream);
|
||||
|
||||
var pos = _interop.GetPosition(window);
|
||||
var state = new WindowState() {
|
||||
owner = window.Owner.key(),
|
||||
app_data = out_stream.ToArray(),
|
||||
position = _interop.GetPosition(window),
|
||||
title = window.Title
|
||||
position = pos,
|
||||
title = string.IsNullOrEmpty(pos.custom_title) ? window.Title : pos.custom_title
|
||||
};
|
||||
return state;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
Logger.log(Level.Error, Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ namespace mbts.ui.shell {
|
||||
[ProtoMember(102), DefaultValue(true)]
|
||||
public bool always_on_top = true;
|
||||
//[ProtoMember(103)] //used
|
||||
|
||||
[ProtoMember(104)]
|
||||
public string custom_title;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -58,7 +61,7 @@ namespace mbts.ui.shell {
|
||||
public WindowPosition position;
|
||||
|
||||
[ProtoMember(9)]
|
||||
public string title;
|
||||
public string title; // for debugging
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace mbts.ui
|
||||
Task<IShellWindow> create(Stream load_stream);
|
||||
}
|
||||
|
||||
// ILazyModule may also impement IShellMenu to get opening/closing notification
|
||||
public interface ILazyMenuModule : IShellModule { }
|
||||
|
||||
public interface IPersistShellModule : IShellModule {
|
||||
// Called at startup before the UI is shown
|
||||
// This is an opportunity for you to kick off background services
|
||||
|
||||
@@ -17,5 +17,5 @@ namespace mbts.ui
|
||||
WindowPosition GetPosition(IShellWindow window);
|
||||
bool IsVisible(IShellWindow window);
|
||||
void PinWindow(IShellWindow window);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,11 @@ namespace mbts.ui.controls {
|
||||
static SparkMenu() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(SparkMenu), new FrameworkPropertyMetadata(typeof(SparkMenu)));
|
||||
|
||||
public SparkMenu() =>
|
||||
public SparkMenu() {
|
||||
SetCurrentValue(FocusableProperty, false);
|
||||
}
|
||||
|
||||
|
||||
public SparkMenu(IEnumerable<SparkCommand> commands, IBus bus) : this() =>
|
||||
ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
|
||||
|
||||
|
||||
@@ -61,16 +61,16 @@ namespace mbts.ui
|
||||
_source_window = window as FloatingWindow;
|
||||
if (_source != null) {
|
||||
_mouse_start = e.GetPosition(window);
|
||||
if (!is_button_descendant((DependencyObject)e.OriginalSource)) {
|
||||
if (!is_clickable_descendant((DependencyObject)e.OriginalSource)) {
|
||||
Mouse.Capture(_source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_button_descendant(DependencyObject target) {
|
||||
static bool is_clickable_descendant(DependencyObject target) {
|
||||
while (target != null && !(target is TabDocument)) {
|
||||
target = VisualTreeHelper.GetParent(target);
|
||||
if (target is Button) return true;
|
||||
if (target is Button || target is TextBox) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -24,38 +24,7 @@ namespace mbts.ui
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task FromEvent(this object target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
Action result = () => { tcs.TrySetResult(null); };
|
||||
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
|
||||
try {
|
||||
evt.AddEventHandler(target, d);
|
||||
await tcs.Task;
|
||||
}
|
||||
finally {
|
||||
evt.RemoveEventHandler(target, d);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<T> FromEvent<T>(this object target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
Action<T> result = (arg) => { tcs.TrySetResult(arg); };
|
||||
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
|
||||
try {
|
||||
evt.AddEventHandler(target, d);
|
||||
await tcs.Task;
|
||||
return tcs.Task.Result;
|
||||
}
|
||||
finally {
|
||||
evt.RemoveEventHandler(target, d);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this object target, string event_name) {
|
||||
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this DependencyObject target, string event_name) {
|
||||
var evt = target.GetType().GetEvent(event_name);
|
||||
var delegate_type = evt.EventHandlerType;
|
||||
var tcs = new TaskCompletionSource<Tuple<T1,T2>>();
|
||||
|
||||
@@ -14,18 +14,19 @@ namespace mbts.ui.shell
|
||||
public partial class FloatingWindow : Window
|
||||
{
|
||||
bool _closed = false;
|
||||
private IUndoActions _undo;
|
||||
readonly IBookmarks _bookmark;
|
||||
private IWindowManager _wm;
|
||||
private ISavedWindowFactory _window_factory;
|
||||
private ISettingsCollector _window_settings;
|
||||
private Stack<Action> _recently_closed_windows = new Stack<Action>();
|
||||
|
||||
// if is_loaded is true, will NOT set width/height
|
||||
internal FloatingWindow(TabDocument document, IWindowManager wm, IUndoActions undo, ISettingsCollector settings, ISavedWindowFactory factory, bool is_loaded=false) {
|
||||
internal FloatingWindow(TabDocument document,
|
||||
IWindowManager wm, IBookmarks bookmarks, ISettingsCollector settings, ISavedWindowFactory factory, bool is_loaded=false) {
|
||||
_wm = wm;
|
||||
_undo = undo;
|
||||
_window_settings = settings;
|
||||
_window_factory = factory;
|
||||
_bookmark = bookmarks;
|
||||
|
||||
InitializeComponent();
|
||||
if (string.IsNullOrEmpty(document.ViewModel.host_id)) {
|
||||
@@ -67,6 +68,7 @@ namespace mbts.ui.shell
|
||||
|
||||
|
||||
private ItemCollection Items { get { return _tabs.Items; } }
|
||||
public IEnumerable<TabDocument> Documents => _tabs.Items.OfType<TabDocument>();
|
||||
public bool IsEmpty { get { return _tabs.Items.Count == 0; } }
|
||||
public bool SingleChild { get { return _tabs.Items.Count == 1; } }
|
||||
public string host_id { get; private set; }
|
||||
@@ -88,10 +90,10 @@ namespace mbts.ui.shell
|
||||
if (sender != this) {
|
||||
var undoaction = create_undo_action(document);
|
||||
if (undoaction != null) {
|
||||
IDisposable undoitem = _undo.push("Restore Tab '{0}'".fmt(document.Title), undoaction);
|
||||
_recently_closed_windows.Push(() => { undoaction(); undoitem.Dispose(); });
|
||||
_recently_closed_windows.Push(() => { undoaction(); });
|
||||
}
|
||||
}
|
||||
_bookmark.save_bookmark(document.Model);
|
||||
}
|
||||
|
||||
public void remove_document(TabDocument document) {
|
||||
@@ -144,22 +146,9 @@ namespace mbts.ui.shell
|
||||
|
||||
protected override void OnClosed(EventArgs e) {
|
||||
_closed = true;
|
||||
TabDocument[] items = _tabs.Items.OfType<TabDocument>().ToArray();
|
||||
if (items.Length > 0) {
|
||||
// If we're closing and still have Tabs open we save the whole "window" worth of state in the undo stack
|
||||
// If the application is shutting down this might be unnecessary work but it's not too costly
|
||||
var undo_actions = items.Select(t => create_undo_action(t.ViewModel)).Where(item => item != null).ToArray();
|
||||
if (undo_actions.Length > 0) {
|
||||
string name = "Restore Window: '{0}'".fmt(string.Join("', '", items.Select(t => t.ViewModel.Title)));
|
||||
_undo.push(name, () => {
|
||||
foreach (var u in undo_actions) { u(); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
base.OnClosed(e);
|
||||
|
||||
foreach (TabDocument document in items) {
|
||||
foreach (TabDocument document in _tabs.Items.OfType<TabDocument>().ToList()) {
|
||||
if (document != null && document.ViewModel.CloseCommand.CanExecute(this)) {
|
||||
document.ViewModel.CloseCommand.Execute(this);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d" Style="{StaticResource {x:Type TabItem}}" d:DesignWidth="125"
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
xmlns:convert="clr-namespace:mbts.ui.converters"
|
||||
mc:Ignorable="d" d:DesignWidth="125"
|
||||
Header="{Binding}" Content="{Binding AppContent}"
|
||||
Background="#e6e6e6">
|
||||
|
||||
@@ -18,6 +20,18 @@
|
||||
<Storyboard x:Key="_slide_back">
|
||||
<DoubleAnimation Storyboard.TargetName="translate_transform" Storyboard.TargetProperty="X" To="100" Duration="0:0:0.25" FillBehavior="Stop" />
|
||||
</Storyboard>
|
||||
<DataTemplate x:Key="_edit_template">
|
||||
<TextBox Text="{Binding TitleOverride}"
|
||||
ui:TextBoxBehavior.CommitOnEnter="True"
|
||||
ui:TextBoxBehavior.FocusOnLoaded="True"
|
||||
ui:TextBoxBehavior.SelectAllOnFocus="True"
|
||||
ui:TextBoxBehavior.ReleaseOnEscape="True"
|
||||
Width="150" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="_template">
|
||||
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0"></TextBlock>
|
||||
</DataTemplate>
|
||||
<convert:BooleanToObjectConverter x:Key="MenuHeaderConverter" ValueForTrue="Rename Window" ValueForFalse="Add to My Windows" />
|
||||
</ResourceDictionary>
|
||||
</TabItem.Resources>
|
||||
<TabItem.LayoutTransform>
|
||||
@@ -28,25 +42,32 @@
|
||||
<TabItem.RenderTransform>
|
||||
<TranslateTransform x:Name="translate_transform" X="0" />
|
||||
</TabItem.RenderTransform>
|
||||
<TabItem.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0"> </TextBlock>
|
||||
</DataTemplate>
|
||||
</TabItem.HeaderTemplate>
|
||||
|
||||
<TabItem.Template>
|
||||
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||
<Border x:Name="bd" BorderBrush="Black" BorderThickness="0,0,0.5,0" Background="#d6d6d6" Width="{DynamicResource TabWidth}" Height="{DynamicResource TabHeight}" MinWidth="40" Margin="0" SnapsToDevicePixels="True" DockPanel.Dock="Left">
|
||||
<Border.ToolTip>
|
||||
<TextBlock Text="{Binding Title}" />
|
||||
</Border.ToolTip>
|
||||
<Border.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{Binding HasCustomTitle, Converter={StaticResource MenuHeaderConverter}}" Command="{Binding rename_command}">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="{StaticResource Bookmarks}" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Remove from My Windows" Command="{Binding unrename_command}" IsEnabled="{Binding HasCustomTitle}" />
|
||||
</ContextMenu>
|
||||
</Border.ContextMenu>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image SnapsToDevicePixels="True" MaxHeight="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Margin="5,0,5,0" Source="{Binding Icon, Mode=OneTime}" RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
<Image SnapsToDevicePixels="True" MaxHeight="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Margin="5,0,5,0"
|
||||
Source="{Binding Icon, Mode=OneTime}" RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
<!--<Image MaxHeight="10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Stretch="Uniform" Margin="5"
|
||||
Source="{StaticResource Bookmarks}" Visibility="{Binding HasCustomTitle, Converter={StaticResource BoolToVisibility}}" />-->
|
||||
<ContentPresenter Grid.Column="1" x:Name="content" ContentSource="Header" TextBlock.Foreground="#666"
|
||||
RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -62,12 +83,18 @@
|
||||
<Setter Property="Panel.ZIndex" Value="1" />
|
||||
<Setter Property="Background" TargetName="bd" Value="#F9F9F9" />
|
||||
<Setter Property="TextBlock.Foreground" TargetName="content" Value="black" />
|
||||
<!--<Setter Property="BorderThickness" TargetName="bd" Value="0,4,0.5,0" />-->
|
||||
<!--<Setter Property="Margin" Value="-2,-2,-2,-1" />
|
||||
<Setter Property="Margin" TargetName="content" Value="0,0,0,1" />
|
||||
<Setter Property="Margin" TargetName="bd" Value="0,0,0,0" />-->
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</TabItem.Template>
|
||||
<TabItem.Style>
|
||||
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource _template}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding EditMode}" Value="True">
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource _edit_template}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TabItem.Style>
|
||||
</TabItem>
|
||||
@@ -18,8 +18,15 @@ namespace mbts.ui.shell {
|
||||
public ImageSource IconSource { get { return _module.icon; } }
|
||||
public UIElement MenuContent => new SparkMenu(_module.actions, _bus);
|
||||
|
||||
public void opening() { }
|
||||
public void opening() {
|
||||
(_module as IShellMenu)?.opening();
|
||||
if (_module is ILazyMenuModule) {
|
||||
RaisePropertyChanged(nameof(MenuContent));
|
||||
}
|
||||
}
|
||||
|
||||
public void closing() { }
|
||||
public void closing() {
|
||||
(_module as IShellMenu)?.closing();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,11 @@ namespace mbts.ui.shell
|
||||
Dispatcher _dispatcher;
|
||||
HwndContentHost _hwnd_host;
|
||||
|
||||
|
||||
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = "") {
|
||||
Logger.assert(model != null);
|
||||
_model = model;
|
||||
_model.TitleChanged += () => RaisePropertyChanged("Title");
|
||||
_model.TitleChanged += () => { if (!HasCustomTitle) RaisePropertyChanged("Title"); };
|
||||
_model.IconChanged += () => { _icon = null; RaisePropertyChanged("Icon"); };
|
||||
_wm = wm;
|
||||
host_id = host;
|
||||
@@ -40,13 +41,55 @@ namespace mbts.ui.shell
|
||||
if (HasTabAffinity) {
|
||||
this.host_id = ((ITabAffineWindow)model).tab_group;
|
||||
}
|
||||
|
||||
_edit_mode = backing(nameof(EditMode), false);
|
||||
}
|
||||
|
||||
public string host_id { get; set; }
|
||||
public Dispatcher Dispatcher { get { return _dispatcher; } }
|
||||
public string Title { get { return _model.Title; } }
|
||||
|
||||
public string Title { get { return HasCustomTitle? TitleOverride : _model.Title; } }
|
||||
|
||||
string _title_override;
|
||||
public string TitleOverride {
|
||||
get => _title_override;
|
||||
set {
|
||||
if (_title_override != value) {
|
||||
if (value == _model.Title) {
|
||||
_title_override = null;
|
||||
}
|
||||
else {
|
||||
_title_override = value;
|
||||
}
|
||||
RaisePropertyChanged(nameof(TitleOverride), nameof(Title), nameof(HasCustomTitle));
|
||||
}
|
||||
_edit_mode.set(false);
|
||||
}
|
||||
}
|
||||
public bool HasCustomTitle => !string.IsNullOrWhiteSpace(TitleOverride);
|
||||
|
||||
Property<bool> _edit_mode;
|
||||
public bool EditMode => _edit_mode.Value;
|
||||
|
||||
|
||||
public ICommand rename_command => new DelegateCommand(begin_rename);
|
||||
public ICommand unrename_command => new DelegateCommand(remove_custom);
|
||||
|
||||
private void begin_rename() {
|
||||
if (string.IsNullOrEmpty(TitleOverride)) {
|
||||
_title_override = Title;
|
||||
}
|
||||
_edit_mode.set(true);
|
||||
}
|
||||
|
||||
private void remove_custom() {
|
||||
TitleOverride = string.Empty;
|
||||
//begin_close(this);
|
||||
}
|
||||
|
||||
public string OwnerName { get { return _model.Owner == null ? "" : _model.Owner.name; } }
|
||||
public ICommand CloseCommand { get { return new DelegateCommand(begin_close); } }
|
||||
|
||||
public double InitialWidth { get; private set; }
|
||||
public double InitialHeight { get; private set; }
|
||||
|
||||
@@ -155,18 +198,14 @@ namespace mbts.ui.shell
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetSaveState() {
|
||||
MemoryStream stream = new MemoryStream();
|
||||
_model.GetState(stream);
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
public IShellWindow Model { get { return _model; } }
|
||||
|
||||
public bool IsSlow { get { return _model is ISlowWindow; } }
|
||||
public bool IsShared { get { return _model is ISharedWindow; } }
|
||||
public bool HasTabAffinity { get { return _model is ITabAffineWindow; } }
|
||||
|
||||
|
||||
|
||||
public void Dispose() {
|
||||
if (IsShared) {
|
||||
// Shared threads continue running
|
||||
|
||||
@@ -20,25 +20,31 @@ namespace mbts.ui.shell {
|
||||
|
||||
[Export(typeof(IWindowManager))]
|
||||
[Export(typeof(IWindowsInterop))]
|
||||
[Export]
|
||||
public class WindowManager : IWindowManager, IWindowsInterop, IDisposable {
|
||||
Dictionary<IShellWindow, TabDocument> _documents = new Dictionary<IShellWindow, TabDocument>();
|
||||
Dictionary<string, FloatingWindow> _windows = new Dictionary<string, FloatingWindow>();
|
||||
IShellThreadFactory _threads;
|
||||
readonly IShellThreadFactory _threads;
|
||||
Lazy<ISettingsCollector> _window_settings;
|
||||
ISavedWindowFactory _window_factory;
|
||||
IUndoActions _undo;
|
||||
readonly ISavedWindowFactory _window_factory;
|
||||
readonly Lazy<IBookmarks> _bookmarks;
|
||||
IDisposable _dispose;
|
||||
object _lock = new object();
|
||||
|
||||
[ImportingConstructor]
|
||||
public WindowManager(
|
||||
IShellThreadFactory threads, Lazy<ISettingsCollector> settings,
|
||||
ISavedWindowFactory factory, IUndoActions undo, IBus bus) {
|
||||
ISavedWindowFactory factory, Lazy<IBookmarks> bookmarks, IBus bus) {
|
||||
_threads = threads;
|
||||
_window_settings = settings;
|
||||
_window_factory = factory;
|
||||
_undo = undo;
|
||||
_dispose = bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore());
|
||||
_bookmarks = bookmarks;
|
||||
_dispose =
|
||||
Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore()),
|
||||
bus.subscribe(SparkTags.ShowWindowAsync).OnDispatcher().Subscribe(w => add(w).ignore()),
|
||||
bus.subscribe(SparkTags.ActivateWindow).OnDispatcher().Subscribe(MakeVisible)
|
||||
);
|
||||
}
|
||||
|
||||
#region Window creation
|
||||
@@ -48,6 +54,7 @@ namespace mbts.ui.shell {
|
||||
var state = model.start_position();
|
||||
if (state != null) {
|
||||
viewmodel.host_id = state.host_id ?? "";
|
||||
viewmodel.TitleOverride = state.custom_title;
|
||||
}
|
||||
var window = CreateWindow(content, state);
|
||||
|
||||
@@ -66,10 +73,10 @@ namespace mbts.ui.shell {
|
||||
}
|
||||
} else {
|
||||
if (position != null) {
|
||||
window = new FloatingWindow(document, this, _undo, _window_settings.Value, _window_factory, true);
|
||||
window = new FloatingWindow(document, this, _bookmarks.Value, _window_settings.Value, _window_factory, true);
|
||||
window.set_position(position);
|
||||
} else
|
||||
window = new FloatingWindow(document, this, _undo, _window_settings.Value, _window_factory);
|
||||
window = new FloatingWindow(document, this, _bookmarks.Value, _window_settings.Value, _window_factory);
|
||||
_windows.Add(window.host_id, window);
|
||||
window.Closed += (o, e) => _windows.Remove(window.host_id);
|
||||
}
|
||||
@@ -81,12 +88,19 @@ namespace mbts.ui.shell {
|
||||
public IReadOnlyList<IShellWindow> list_windows() =>
|
||||
_documents.Keys.ToList();
|
||||
|
||||
public IReadOnlyList<TabDocument> list_documents() => _documents.Values.ToList();
|
||||
|
||||
public async Task add(IShellWindow window) {
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
await Application.Current.Dispatcher.InvokeAsync(() => add(window));
|
||||
Logger.info(bts.log.Subsystem.Ui, $"Opening window {window.diagnostic_name()}.");
|
||||
lock (_lock)
|
||||
lock (_lock) {
|
||||
_documents[window] = _create_document(window);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task add(Task<IShellWindow> window) {
|
||||
await add(await window);
|
||||
}
|
||||
|
||||
public void remove(IShellWindow window) {
|
||||
@@ -139,14 +153,13 @@ namespace mbts.ui.shell {
|
||||
|
||||
#region IWindowsInterop
|
||||
public WindowPosition GetPosition(IShellWindow shell_window) {
|
||||
return GetPosition(shell_window, null);
|
||||
}
|
||||
public WindowPosition GetPosition(IShellWindow shell_window, FloatingWindow owning_window) {
|
||||
FloatingWindow window = owning_window == null ? window_for(shell_window) : owning_window;
|
||||
FloatingWindow window = window_for(shell_window);
|
||||
var doc = window.Documents.FirstOrDefault(td => td.ViewModel.Model == shell_window);
|
||||
if (window != null) {
|
||||
WindowPosition pos = window.get_position();
|
||||
pos.tab_index = window.document_index(shell_window);
|
||||
pos.host_id = window.host_id;
|
||||
pos.custom_title = doc?.ViewModel?.TitleOverride;
|
||||
return pos;
|
||||
}
|
||||
Logger.log(bts.log.Level.Warn, bts.log.Subsystem.Ui, "IShellWindow '{0}' does not have a valid position. It may not be displayed".fmt(shell_window.Title));
|
||||
|
||||
@@ -19,6 +19,7 @@ using mbts.persist;
|
||||
|
||||
namespace mbts.ui.shell
|
||||
{
|
||||
|
||||
[Export]
|
||||
public class WorkspacesModel : ViewModelBaseWithValidation
|
||||
{
|
||||
@@ -35,10 +36,11 @@ namespace mbts.ui.shell
|
||||
private bool _closing = false;
|
||||
private ILauncher _launcher;
|
||||
private IUndoActions _undo;
|
||||
readonly IBookmarks _bookmarks;
|
||||
|
||||
[ImportingConstructor]
|
||||
public WorkspacesModel(ISettingsCollector settings, ISavedWindowFactory window_restore,
|
||||
IWindowManager windows, ObservableCollection<IShellModule> modules, IPersist persist,
|
||||
IWindowManager windows, ObservableCollection<IShellModule> modules, IPersist persist, IBookmarks bm,
|
||||
IUserPersist user_persist, ILauncher launcher, IUndoActions undo, [Import("workspace")]string initial_workspace) {
|
||||
_settings_collector = settings;
|
||||
_window_restore = window_restore;
|
||||
@@ -48,6 +50,7 @@ namespace mbts.ui.shell
|
||||
_modules = modules;
|
||||
_launcher = launcher;
|
||||
_undo = undo;
|
||||
_bookmarks = bm;
|
||||
_launcher.closing += _launcher_closing;
|
||||
Workspaces = new ObservableCollection<WorkspaceViewModel>();
|
||||
init(initial_workspace).ignore();
|
||||
@@ -150,6 +153,7 @@ namespace mbts.ui.shell
|
||||
workspace.windows = _settings_collector.get_window_state().ToList();
|
||||
workspace.modules = _settings_collector.get_module_state().ToList();
|
||||
workspace.launcher_state = _settings_collector.get_launcher_state();
|
||||
workspace.named_windows = _bookmarks.bookmarks.ToList();
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@@ -166,6 +170,7 @@ namespace mbts.ui.shell
|
||||
await Task.Yield();
|
||||
}
|
||||
workspace.launcher_state = _settings_collector.get_launcher_state();
|
||||
workspace.named_windows = _bookmarks.bookmarks.ToList();
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@@ -226,10 +231,15 @@ namespace mbts.ui.shell
|
||||
}
|
||||
}
|
||||
|
||||
// Notify workspace changed
|
||||
if (WorkspaceLoaded != null) {
|
||||
WorkspaceLoaded(name);
|
||||
_bookmarks.clear();
|
||||
if (def.named_windows != null) {
|
||||
foreach (var window in def.named_windows) {
|
||||
_bookmarks.save_bookmark(window);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify workspace changed
|
||||
WorkspaceLoaded?.Invoke(name);
|
||||
}
|
||||
|
||||
public Task delete_workspace(string name, WorkspaceDefinition def) {
|
||||
@@ -244,18 +254,20 @@ namespace mbts.ui.shell
|
||||
}
|
||||
return (def.is_shared ? _public : _private).clear(WORKSPACE_NAMESPACE, name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The serialized data for the Workspace
|
||||
[ProtoContract(Name = "WorkspaceDefinition")]
|
||||
public class WorkspaceDefinition
|
||||
{
|
||||
// Converted the protobuf type ShellSettings to c#
|
||||
// Avoid re-using id: 1,3,4,5,6,7
|
||||
|
||||
[ProtoMember(2, IsRequired = false)]
|
||||
public List<WindowState> windows { get; set; }
|
||||
|
||||
// named windows which are not currently open
|
||||
[ProtoMember(3)]
|
||||
public List<WindowState> named_windows { get; set; }
|
||||
|
||||
[ProtoMember(9, IsRequired = false)]
|
||||
public List<ModuleState> modules { get; set; }
|
||||
|
||||
|
||||
@@ -37,13 +37,13 @@ namespace bts.spark
|
||||
if (this.IsRoot) {
|
||||
return Enumerable.Empty<OrderNode>();
|
||||
}
|
||||
return this.Parent.ToIEnumerable().Concat(this.Parent.Ancestors);
|
||||
return new[] { this.Parent }.Concat(this.Parent.Ancestors);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<OrderNode> MeAndDescedants {
|
||||
get {
|
||||
return this.ToIEnumerable().Concat(this.children.SelectMany(c => c.MeAndDescedants));
|
||||
return new[] { this }.Concat(this.children.SelectMany(c => c.MeAndDescedants));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ size_t load_securities(
|
||||
|
||||
void store_if_new(const std::vector<Security*>& securities) {
|
||||
auto dbptr = open_db();
|
||||
auto insert = utils::prepare_query(dbptr, "insert into securities (key, symbol, venue, type, security_blob, expiration) values (?,?,?,?,?,?)");
|
||||
auto insert = utils::prepare_query(dbptr, "insert or ignore into securities (key, symbol, venue, type, security_blob, expiration) values (?,?,?,?,?,?)");
|
||||
sqlite3_exec_nofail(dbptr, "BEGIN TRANSACTION");
|
||||
std::string buffer;
|
||||
for (const api::Security* psecurity : securities) {
|
||||
@@ -100,10 +100,7 @@ void store_if_new(const std::vector<Security*>& securities) {
|
||||
SQLITE_CHK(sqlite3_bind_int(insert.get(), 4, securitytype_of(*psecurity)), dbptr);
|
||||
SQLITE_CHK(sqlite3_bind_blob(insert.get(), 5, buffer.c_str(), buffer.size(), SQLITE_STATIC), dbptr);
|
||||
SQLITE_CHK(sqlite3_bind_int(insert.get(), 6, expiration(*psecurity).to_short()), dbptr);
|
||||
auto result = sqlite3_step(insert.get());
|
||||
if (result != SQLITE_DONE) {
|
||||
LOG(md, Warn, "Failed to insert security into security db. key={}", psecurity->id());
|
||||
}
|
||||
sqlite3_step(insert.get());
|
||||
sqlite3_reset(insert.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,8 @@ void SecurityIDRecorder::work_db() {
|
||||
// Once a security has a key it is public so record it in the security database
|
||||
std::shared_ptr<SecurityIDEntry> temp;
|
||||
vector<std::shared_ptr<SecurityIDEntry>> new_entries;
|
||||
// SPARK-1446 Need to try to add existing securities to the db in case we deleted them by hand
|
||||
vector<std::shared_ptr<SecurityIDEntry>> existing_securities;
|
||||
vector<std::shared_ptr<SecurityIDEntry>> defer;
|
||||
vector<string> new_descriptors;
|
||||
size_t count = 0;
|
||||
@@ -150,6 +152,7 @@ void SecurityIDRecorder::work_db() {
|
||||
if (key != invalid_security_key) {
|
||||
temp->security.set_id(key);
|
||||
temp->callback(temp->security);
|
||||
existing_securities.push_back(move(temp));
|
||||
}
|
||||
else {
|
||||
// always push in parallel
|
||||
@@ -168,7 +171,7 @@ void SecurityIDRecorder::work_db() {
|
||||
}
|
||||
|
||||
|
||||
if (new_entries.size() > 0) {
|
||||
if (!new_entries.empty()) {
|
||||
sqlite3_exec_nofail(_self->db.get(), "BEGIN TRANSACTION");
|
||||
|
||||
for (size_t x = 0; x < new_entries.size(); ++x) {
|
||||
@@ -179,15 +182,22 @@ void SecurityIDRecorder::work_db() {
|
||||
}
|
||||
|
||||
sqlite3_exec_nofail(_self->db.get(), "COMMIT TRANSACTION");
|
||||
}
|
||||
|
||||
// record the securities
|
||||
// record the securities
|
||||
if (!new_entries.empty() || !existing_securities.empty()) {
|
||||
std::vector<Security*> save_these;
|
||||
save_these.reserve(new_entries.size());
|
||||
save_these.reserve(new_entries.size() + existing_securities.size());
|
||||
for (const auto& e : new_entries) {
|
||||
save_these.push_back(&e->security);
|
||||
}
|
||||
for (const auto& e : existing_securities) {
|
||||
save_these.push_back(&e->security);
|
||||
}
|
||||
db::store_if_new(save_these);
|
||||
}
|
||||
|
||||
if (!new_entries.empty()) {
|
||||
for (size_t x = 0; x < new_entries.size(); ++x) {
|
||||
Security& security = new_entries[x]->security;
|
||||
new_entries[x]->callback(security);
|
||||
|
||||
@@ -216,6 +216,9 @@ unique_ptr<onixs::Handler> MDP3Main::prepare_handler(const string& channel_id) {
|
||||
if (options.book_type() == CMEOptions::BOOKTYPE_CONSOLIDATED) {
|
||||
settings.bookManagement().consolidatedBooks().maintain(true);
|
||||
}
|
||||
else {
|
||||
settings.bookManagement().directBooks().maintain(true);
|
||||
}
|
||||
|
||||
// 10k default is *far* too low
|
||||
settings.recoveryQueueMaxSize(options.recovery_queue_size());
|
||||
|
||||
@@ -53,7 +53,7 @@ void SpreaderOrder::on_activate(Price price, Quantity quantity,
|
||||
}
|
||||
void SpreaderOrder::on_update(Price price, Quantity quantity,TickToTradeCorID){
|
||||
if (!_active) return;
|
||||
LOG(OrderMan, Info, "SpreaderOrder.on_update key: {} price: {} quantity: {}", key().value(), price.value(), quantity.value());
|
||||
LOG(OrderMan, Info, "SpreaderOrder.on_update key: {} price: {} quantity: {}", key().value(), price.value(), quantity.value());
|
||||
_state_worker->on_update(price, quantity);
|
||||
_report_state();
|
||||
}
|
||||
@@ -242,45 +242,50 @@ bool SpreaderOrder::_quoters_can_accept_modification (){
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void SpreaderOrder::_send_hedge(size_t filled_leg_index,
|
||||
Price filled_price,
|
||||
Quantity filled_quantity,
|
||||
size_t hedge_leg_index){
|
||||
LOG(OrderMan, Info, "SpreaderOrder::_send_hedge key: {}, filled_leg_index: {} filled_price: {} filled_quantity: {} hedge_leg_index: {}",
|
||||
key().value(), filled_leg_index, filled_price.value(), filled_quantity.value(), hedge_leg_index);
|
||||
Price hedge_price;
|
||||
Quantity hedge_quantity;
|
||||
_compute_hedge_price_quantity (filled_leg_index, filled_price,
|
||||
filled_quantity, hedge_leg_index,
|
||||
hedge_price, hedge_quantity);
|
||||
SingleFillHedger& hedger = _bookkeeping_data().add_new_hedger (*this,
|
||||
void SpreaderOrder::_send_hedge(size_t hedge_leg_index,
|
||||
Price hedge_price,
|
||||
Quantity hedge_qty){
|
||||
LOG(OrderMan, Info, "SpreaderOrder::_send_hedge key: {}, hedge_leg_index: {} hedge_price: {} hedge_qty: {}",
|
||||
key().value(), hedge_leg_index, hedge_price, hedge_qty);
|
||||
SingleFillHedger& hedger = _bookkeeping_data().add_new_hedger (*this,
|
||||
hedge_leg_index,
|
||||
hedge_price,
|
||||
hedge_quantity);
|
||||
hedge_qty);
|
||||
hedger.init ();
|
||||
}
|
||||
void SpreaderOrder::_compute_hedge_price_quantity(size_t filled_leg_index,
|
||||
Price fill_price, Quantity fill_quantity,
|
||||
Price fill_price,
|
||||
double fill_quantity,
|
||||
size_t hedge_leg_index,
|
||||
Price& hedge_price, Quantity& hedge_quantity){
|
||||
Price& hedge_price,
|
||||
double& hedge_quantity){
|
||||
Price computed_hedge_price;
|
||||
SpreaderLegData& filled_leg_data = _bookkeeping_data().get_leg_data(filled_leg_index);
|
||||
SpreaderLegData& hedged_leg_data = _bookkeeping_data().get_leg_data(hedge_leg_index);
|
||||
if (_bookkeeping_data().n_legs() == 2)
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (_bookkeeping_data().price(),
|
||||
filled_leg_data.leg_property_data(), fill_price, fill_quantity,
|
||||
hedged_leg_data.leg_property_data(), computed_hedge_price, hedge_quantity);
|
||||
filled_leg_data.leg_property_data(),
|
||||
fill_price,
|
||||
fill_quantity,
|
||||
hedged_leg_data.leg_property_data(),
|
||||
computed_hedge_price,
|
||||
hedge_quantity);
|
||||
else{
|
||||
const QuoterLegDriverData& driver_data = filled_leg_data.quoter().driver_data(hedge_leg_index);
|
||||
SpreaderUtils::compute_hedge_price_size_n_legs (filled_leg_data.leg_property_data(), fill_price, fill_quantity,
|
||||
hedged_leg_data.leg_property_data(), driver_data,
|
||||
computed_hedge_price, hedge_quantity);
|
||||
const QuoteLegDriver& driver_data = filled_leg_data.quoter().driver_data(fill_price, hedge_leg_index);
|
||||
SpreaderUtils::compute_hedge_price_size_n_legs (filled_leg_data.leg_property_data(),
|
||||
fill_price,
|
||||
fill_quantity,
|
||||
hedged_leg_data.leg_property_data(),
|
||||
driver_data,
|
||||
computed_hedge_price,
|
||||
hedge_quantity);
|
||||
}
|
||||
hedge_price = SpreaderUtils::apply_pre_hedge_parameters (hedged_leg_data.leg_property_data().leg_params().pre_hedge_params(),
|
||||
hedged_leg_data.book(),
|
||||
hedged_leg_data.leg_property_data().trading_side(),
|
||||
hedged_leg_data.leg_property_data().tick_size(),
|
||||
computed_hedge_price, hedge_quantity);
|
||||
Price(computed_hedge_price),
|
||||
Quantity(hedge_quantity));
|
||||
}
|
||||
void SpreaderOrder::_report_state (){
|
||||
if (_activated()){
|
||||
|
||||
@@ -166,17 +166,15 @@ private:
|
||||
// on leg 'leg_to_hedge_index' in response to a fill
|
||||
// on leg 'filled_leg_index' with price 'filled_price'
|
||||
// quantity 'filled_quantity'
|
||||
void _send_hedge(size_t filled_leg_index, Price filled_price,
|
||||
Quantity filled_quantity,
|
||||
size_t leg_to_hedge_index);
|
||||
// purely a computating function that computes the
|
||||
// hedge price and quantity (last 2 args) for leg
|
||||
// 'hedged_leg_index' for a fill on leg 'filled_leg_index'
|
||||
// with price 'filled_price' and quantity 'filled_quantity'
|
||||
void _send_hedge(size_t hedge_leg_index,
|
||||
Price hedge_price,
|
||||
Quantity hedge_qty);
|
||||
void _compute_hedge_price_quantity(size_t filled_leg_index,
|
||||
Price filled_price, Quantity filled_quantity,
|
||||
Price filled_price,
|
||||
double filled_quantity,
|
||||
size_t hedged_leg_index,
|
||||
Price& hedge_price, Quantity& hedge_quantity);
|
||||
Price& hedge_price,
|
||||
double& hedge_quantity);
|
||||
// sends the report out
|
||||
void _report_state ();
|
||||
SpreaderBookkeepingData& _bookkeeping_data(){
|
||||
|
||||
@@ -20,6 +20,7 @@ _spreader(spreader),
|
||||
_this_leg_data (this_leg_property),
|
||||
_n_spreader_legs (n_spreader_legs),
|
||||
_child_order_state (child_order_state),
|
||||
_driver_cache(n_spreader_legs,n_spreader_legs>2?10:0,this_leg_property.tick_size()),
|
||||
_price_quantity_state(Price(0), Quantity(0), child_order_state),
|
||||
_is_permenantly_cancelled (false),
|
||||
_worker(nullptr),
|
||||
@@ -33,8 +34,7 @@ _end_state_worker(this){
|
||||
if (_active_quoting){
|
||||
if (!_two_legged){
|
||||
for (size_t q_idx = 0; q_idx < _n_spreader_legs; q_idx++){
|
||||
_drivers_commited.push_back (QuoterLegDriverData());
|
||||
_drivers_temp_usage.push_back (QuoterLegDriverData());
|
||||
_drivers_temp_usage.push_back (QuoteLegDriver());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ _this_leg_data (other._this_leg_data),
|
||||
_n_spreader_legs (other._n_spreader_legs),
|
||||
_child_order_state (other._child_order_state),
|
||||
_drivers_temp_usage(other._drivers_temp_usage),
|
||||
_drivers_commited (other._drivers_commited),
|
||||
_driver_cache(other._driver_cache),
|
||||
_price_quantity_state(other._price_quantity_state.target_spread_price(),
|
||||
other._price_quantity_state.target_leg_quantity(),
|
||||
other._price_quantity_state.order_state()),
|
||||
@@ -131,13 +131,8 @@ bool SingleLegQuoter::is_terminal_state () const {
|
||||
bool SingleLegQuoter::accept_modifications() const {
|
||||
return _worker->state() == QuoterState::WAITING_FOR_SIZE || _worker->state() == QuoterState::WORKING;
|
||||
}
|
||||
Price SingleLegQuoter::driver_price (size_t leg_idx) const{
|
||||
bts_assert (leg_idx < _n_spreader_legs);
|
||||
return _drivers_commited[leg_idx].driver_price;
|
||||
}
|
||||
void SingleLegQuoter::_incorporate_other_leg_fill (size_t leg_index, Quantity filled_quantity){
|
||||
Quantity this_leg_reduction = SpreaderUtils::get_leg_quantity_from_another_leg_quantity (_spreader.get_leg_data(leg_index), filled_quantity, _this_leg_data);
|
||||
_price_quantity_state.add_hedged_reduction (this_leg_reduction);
|
||||
void SingleLegQuoter::_incorporate_other_leg_fill (size_t, Quantity hedge_reduction){
|
||||
_price_quantity_state.add_hedged_reduction (hedge_reduction);
|
||||
}
|
||||
void SingleLegQuoter::_compute_price_quantity(Price& price, Quantity& quantity){
|
||||
Quantity remaining_quantity = _price_quantity_state.remaining_to_fill();
|
||||
@@ -152,11 +147,9 @@ void SingleLegQuoter::_compute_price_quantity(Price& price, Quantity& quantity){
|
||||
SpreaderUtils::compute_quoting_price_size_n_legs (_price_quantity_state.target_spread_price(), remaining_quantity,
|
||||
_this_leg_data.leg_index(), _spreader, _drivers_temp_usage, price, quantity);
|
||||
}
|
||||
void SingleLegQuoter::_commit_drivers(){
|
||||
if (!_two_legged)
|
||||
// this could be done with a pointer swap too
|
||||
for (size_t idx = 0; idx < _n_spreader_legs; idx++)
|
||||
_drivers_commited[idx] = _drivers_temp_usage[idx];
|
||||
void SingleLegQuoter::_commit_drivers(Price quote_price){
|
||||
if (!_two_legged)
|
||||
_driver_cache.add(_drivers_temp_usage,quote_price);
|
||||
}
|
||||
/*********************************************************************************/
|
||||
/*******************************state transformations*****************************/
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForCancelAckStateWorker.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWorkingStateWorker.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterEndStateWorker.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoterLegDriverData.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoteLegDriver.h"
|
||||
#include "server/sparkd/orderman/actors/order_manager/ChildOrder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderChildOrderState.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoteDriverSetHistory.h"
|
||||
#include <sstream>
|
||||
|
||||
|
||||
@@ -24,7 +25,6 @@ namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class ISpreaderManager;
|
||||
//class SpreaderChildOrderState;
|
||||
class SingleLegQuoter
|
||||
{
|
||||
friend QuoterStartStateWorker;
|
||||
@@ -45,8 +45,8 @@ private:
|
||||
// legs requires auxiliary storage for drivers, which
|
||||
// _drivers_temp_usage provides. once we are ready
|
||||
// to send the orders, the drivers are committed to
|
||||
// _drivers_commited
|
||||
std::vector<QuoterLegDriverData> _drivers_temp_usage, _drivers_commited;
|
||||
std::vector<QuoteLegDriver> _drivers_temp_usage;
|
||||
QuoteDriverSetHistory _driver_cache;
|
||||
QuoteLegPriceQuantityState _price_quantity_state;
|
||||
bool _is_permenantly_cancelled;
|
||||
// state workers
|
||||
@@ -108,13 +108,10 @@ public:
|
||||
// the quoter returns true if it is in a state where orders
|
||||
// can be modified
|
||||
bool accept_modifications() const;
|
||||
// for a given leg, returns the driver price that was used
|
||||
// to price the current order
|
||||
Price driver_price (size_t leg_idx) const;
|
||||
const QuoterLegDriverData& driver_data (size_t leg_idx) const
|
||||
{
|
||||
bts_assert (leg_idx < _drivers_commited.size());
|
||||
return _drivers_commited[leg_idx];
|
||||
const QuoteLegDriver& driver_data (Price quote_price, size_t leg_idx) const{
|
||||
const QuoteLegDriver* leg_driver = _driver_cache.find_driver(quote_price,leg_idx);
|
||||
bts_assert(leg_driver!=nullptr);
|
||||
return *leg_driver;
|
||||
}
|
||||
// tells if it is expecting to get a cancel (this helps catch
|
||||
// unsolicited cancels). returns true is its expecting a cancel
|
||||
@@ -152,12 +149,12 @@ private:
|
||||
}
|
||||
void _cancel_order () {_child_order_state.cancel_order();}
|
||||
// this updates the hedge reduction in _price_quantity_state
|
||||
void _incorporate_other_leg_fill (size_t leg_index, Quantity filled_quantity);
|
||||
void _incorporate_other_leg_fill (size_t leg_index, Quantity hedge_reduction);
|
||||
// this is the function that computes the quoting price and the
|
||||
// quantity for this leg.
|
||||
void _compute_price_quantity(Price& price, Quantity& quantity);
|
||||
// copies the computed driver to committed dri
|
||||
void _commit_drivers ();
|
||||
void _commit_drivers (Price quote_price);
|
||||
void _transform_to_START ();
|
||||
void _transform_to_WAITING_FOR_SIZE ();
|
||||
void _transform_to_WAITING_FOR_FIRST_ACK ();
|
||||
|
||||
+10
-3
@@ -23,6 +23,7 @@ _reported_state(){
|
||||
_quoter_child_order_states.reserve(_n_legs);
|
||||
_quoters.reserve (_n_legs);
|
||||
_hedgers.reserve (_n_legs);
|
||||
_unhedged_deltas.reserve(_n_legs);
|
||||
_leg_data.reserve (_n_legs);
|
||||
for (const auto& lpd_iter : legs){
|
||||
_leg_prop_data.push_back(lpd_iter);
|
||||
@@ -34,9 +35,15 @@ _reported_state(){
|
||||
_quoter_child_order_states.emplace_back (empty_order,leg_property_data.leg_security_key(),_is_live,true);
|
||||
_quoters.emplace_back(spreader_manager, leg_property_data, _n_legs, _quoter_child_order_states.back());
|
||||
_hedgers.emplace_back ();
|
||||
_leg_data.emplace_back(leg_property_data, _quoters.back(), _quoter_order_builders.back(),
|
||||
_hedger_order_builders.back(), _hedgers.back (), _quoter_child_order_states.back(),
|
||||
_books.back());
|
||||
_unhedged_deltas.emplace_back();
|
||||
_leg_data.emplace_back(leg_property_data,
|
||||
_quoters.back(),
|
||||
_quoter_order_builders.back(),
|
||||
_hedger_order_builders.back(),
|
||||
_hedgers.back (),
|
||||
_quoter_child_order_states.back(),
|
||||
_books.back(),
|
||||
_unhedged_deltas.back());
|
||||
bts_assert (_sec_key_to_index.find(leg_property_data.security_key()) == _sec_key_to_index.end());
|
||||
_sec_key_to_index.insert(std::pair<uint32_t, size_t>(leg_property_data.security_key(), leg_property_data.leg_index()));
|
||||
}
|
||||
|
||||
+3
-1
@@ -8,6 +8,7 @@
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/PreActivationOrderServices.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/hedger/UnhedgedDelta.h"
|
||||
|
||||
namespace bts{
|
||||
namespace orderman {
|
||||
@@ -32,7 +33,8 @@ private:
|
||||
std::vector<SpreaderChildOrderState> _quoter_child_order_states;
|
||||
std::vector<SingleLegQuoter> _quoters;
|
||||
std::vector<std::vector<SingleFillHedger>> _hedgers;
|
||||
std::vector<SpreaderLegData> _leg_data;
|
||||
std::vector<UnhedgedDelta> _unhedged_deltas;
|
||||
std::vector<SpreaderLegData> _leg_data;
|
||||
boost::container::flat_map<uint32_t, size_t> _sec_key_to_index;
|
||||
// state thats sent out in report (its kept here amd modified
|
||||
// vs creating a new one everytime)
|
||||
|
||||
@@ -9,7 +9,8 @@ SpreaderLegData::SpreaderLegData (const LegPropertyData& leg_property_data,
|
||||
SpreaderOrderBuilder& hedger_order_builder,
|
||||
std::vector<SingleFillHedger>& hedger_data_vector,
|
||||
SpreaderChildOrderState& quoter_child_order_state,
|
||||
Book& book)
|
||||
Book& book,
|
||||
UnhedgedDelta& unhedged_delta)
|
||||
:
|
||||
_leg_property_data (leg_property_data),
|
||||
_quoter(quoter),
|
||||
@@ -17,7 +18,8 @@ _quoter_order_builder (quoter_order_builder),
|
||||
_hedger_order_builder (hedger_order_builder),
|
||||
_quoter_child_order_state (quoter_child_order_state),
|
||||
_hedger_data_vector (hedger_data_vector),
|
||||
_book(book)
|
||||
_book(book),
|
||||
_unhedged_delta(unhedged_delta)
|
||||
{}
|
||||
|
||||
SpreaderLegData::SpreaderLegData(SpreaderLegData&& other)
|
||||
@@ -28,7 +30,8 @@ _quoter_order_builder(other._quoter_order_builder),
|
||||
_hedger_order_builder(other._hedger_order_builder),
|
||||
_quoter_child_order_state (other._quoter_child_order_state),
|
||||
_hedger_data_vector (other._hedger_data_vector),
|
||||
_book(other._book){
|
||||
_book(other._book),
|
||||
_unhedged_delta(other._unhedged_delta){
|
||||
bts_assert (false);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "SpreaderChildOrderState.h"
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/hedger/UnhedgedDelta.h"
|
||||
|
||||
namespace bts{
|
||||
namespace orderman{
|
||||
@@ -28,6 +29,7 @@ private:
|
||||
SpreaderChildOrderState& _quoter_child_order_state;
|
||||
std::vector<SingleFillHedger>& _hedger_data_vector;
|
||||
Book& _book;
|
||||
UnhedgedDelta& _unhedged_delta;
|
||||
public:
|
||||
SpreaderLegData (const LegPropertyData& leg_property_data,
|
||||
SingleLegQuoter& quoter,
|
||||
@@ -35,7 +37,8 @@ public:
|
||||
SpreaderOrderBuilder& hedger_order_builder,
|
||||
std::vector<SingleFillHedger>& hedger_data_vector,
|
||||
SpreaderChildOrderState& quoter_child_order_state,
|
||||
Book& book);
|
||||
Book& book,
|
||||
UnhedgedDelta& unhedged_delta);
|
||||
SpreaderLegData() = delete;
|
||||
SpreaderLegData(const SpreaderLegData&) = delete;
|
||||
// this cons is not deleted since for each leg,
|
||||
@@ -51,12 +54,13 @@ public:
|
||||
/////////////////////////////////
|
||||
const LegPropertyData& leg_property_data() const {return _leg_property_data;}
|
||||
SingleLegQuoter& quoter() {return _quoter;}
|
||||
const bts::orderman::Book& book () const {return _book;}
|
||||
const bts::orderman::Book& book () const {return _book;}
|
||||
SpreaderChildOrderState& order_update_message () {return _quoter_child_order_state;}
|
||||
bts::orderman::OrderKey key() {return _quoter_child_order_state.order_key();}
|
||||
bts::orderman::OrderKey order_key() {return _quoter_child_order_state.order_key();}
|
||||
SpreaderOrderBuilder& quoter_order_builder() {return _quoter_order_builder;}
|
||||
SpreaderOrderBuilder& hedger_order_builder() {return _hedger_order_builder;}
|
||||
UnhedgedDelta& unhedged_delta() {return _unhedged_delta;}
|
||||
// returns the number of active hedgers
|
||||
size_t n_hedgers() const { return _hedger_data_vector.size(); }
|
||||
// returns the hedger at a given index
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "server/sparkd/orderman/orderman_utils/Book.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/LegPropertyData.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/ISpreaderManager.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoterLegDriverData.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/quoter/QuoteLegDriver.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderBuilder.h"
|
||||
|
||||
@@ -423,17 +423,28 @@ public:
|
||||
// so if we get an N lot fill in l2, we send a hedge on l1
|
||||
// sized s1 * N / s2
|
||||
////////////////////////general functions
|
||||
static double get_leg_quantity_from_another_leg_quantity (const LegPropertyData& source_leg,
|
||||
double source_leg_quantity,
|
||||
const LegPropertyData& target_leg){
|
||||
return std::abs(target_leg.size_multiplier () * source_leg_quantity / source_leg.size_multiplier ());
|
||||
}
|
||||
static Quantity get_leg_quantity_from_another_leg_quantity (const LegPropertyData& source_leg,
|
||||
Quantity source_leg_quantity,
|
||||
const LegPropertyData& target_leg){
|
||||
double rv = std::abs(target_leg.size_multiplier () * source_leg_quantity.value () / source_leg.size_multiplier ());
|
||||
double rv = get_leg_quantity_from_another_leg_quantity(source_leg,
|
||||
static_cast<double>(source_leg_quantity.value()),
|
||||
target_leg);
|
||||
return Quantity(_round_leg_qty_computed_from_another_leg_qty(rv));
|
||||
}
|
||||
}
|
||||
// given the quantity of the spreader order, determines the quantity
|
||||
// of the leg
|
||||
static double get_leg_quantity_from_spreader_quantity(const LegPropertyData& leg,
|
||||
double spreader_order_quantity){
|
||||
return std::abs (leg.size_multiplier () * spreader_order_quantity);
|
||||
}
|
||||
static Quantity get_leg_quantity_from_spreader_quantity(const LegPropertyData& leg,
|
||||
Quantity spreader_order_quantity){
|
||||
double rv = std::abs (leg.size_multiplier () * spreader_order_quantity.value());
|
||||
double rv = get_leg_quantity_from_spreader_quantity(leg,static_cast<double>(spreader_order_quantity.value()));
|
||||
return Quantity (_round_leg_qty_computed_from_spreader_qty (rv));
|
||||
}
|
||||
// finds the average weighted price and total quantity
|
||||
@@ -450,53 +461,71 @@ public:
|
||||
out_avg_price = Price((base_price.value() * base_qty.value() + inc_price.value() * inc_qty.value())/out_total_qty.value());
|
||||
}
|
||||
////////////////////////2 legged spreads
|
||||
// given a quoting target qty, finds the "driver" price
|
||||
// and qty, thats needed to hedge the quote. It also
|
||||
// finds the ammended quoting target (since the hedge
|
||||
// book might not have enough to support the entire
|
||||
// quoting target qty)
|
||||
static void compute_driver(Quantity quote_target,
|
||||
const LegPropertyData& quote_leg_data,
|
||||
const LegPropertyData& hedge_leg_data,
|
||||
const bts::orderman::Book& hedge_leg_book,
|
||||
Price& out_hedge_price,
|
||||
Quantity& out_hedge_quantity,
|
||||
Quantity& out_quote_target){
|
||||
// effective_mult is the number of contracts needed to
|
||||
// hedge one quoting contract
|
||||
double effective_mult = std::abs(hedge_leg_data.leg_params().quote_params().lean_quantity_ratio() * hedge_leg_data.size_multiplier() / quote_leg_data.size_multiplier());
|
||||
uint32_t hedge_requirement = _round_hedge_target (effective_mult * quote_target.value()); // rounding hedging up
|
||||
SpreaderUtils::traverse_depth_for_quantity(hedge_leg_book,
|
||||
SpreaderUtils::invert_side (hedge_leg_data.trading_side()),
|
||||
Quantity(hedge_requirement),
|
||||
out_hedge_price,
|
||||
out_hedge_quantity);
|
||||
// when we floor the quoting target (ie _round_quoting_qty)
|
||||
// we are essentially demanding less in terms of hedging, ex :
|
||||
// consider the hedge book depth with qty's 5, 10, 5, 20, 10 (5 is the inside)
|
||||
// and hedge ratio 20 and target qty = 5, so effectively target = 100,
|
||||
// we get 50 at the last level, out_qty = _round_quoting_qty (50/20) = 2,
|
||||
// which means we need 2 * 20 and should re-evaluate
|
||||
uint32_t out_quote_target_uint = _round_quoting_qty (out_hedge_quantity.value() / effective_mult); // rounding quoting down
|
||||
out_quote_target_uint = std::min(out_quote_target_uint,quote_target.value());
|
||||
out_quote_target = Quantity (out_quote_target_uint);
|
||||
}
|
||||
// calculates the computed price and quantity for a quoting
|
||||
// leg given the data for the other leg
|
||||
static void compute_price_quantity_2_legs(Price target_spread_price,
|
||||
Quantity this_leg_target_quantity,
|
||||
const LegPropertyData& this_leg_data,
|
||||
const LegPropertyData& other_leg_data,
|
||||
const bts::orderman::Book& other_leg_book,
|
||||
Price& price, Quantity& quantity){
|
||||
// other_leg_price and other_leg_quantity are the price and quantity calculated
|
||||
// from traversing the other book, for the requires size
|
||||
Price other_leg_price; Quantity other_leg_quantity;
|
||||
// this is the size ratio of the other leg vs this leg
|
||||
double effective_mult = std::abs(other_leg_data.leg_params().quote_params().lean_quantity_ratio() * other_leg_data.size_multiplier() / this_leg_data.size_multiplier());
|
||||
// to be conservative round the required size up
|
||||
uint32_t other_leg_target_quantity = _round_hedge_target (effective_mult * this_leg_target_quantity.value());
|
||||
SpreaderUtils::traverse_depth_for_quantity(other_leg_book, SpreaderUtils::invert_side (other_leg_data.trading_side()), Quantity(other_leg_target_quantity), other_leg_price, other_leg_quantity);
|
||||
// when we floor we essentially go back up the hedging ladder
|
||||
// so we should re-evaluate
|
||||
// ex, consider the hedge book depth with qty's 5, 10, 5, 20, 10 (5 is the inside)
|
||||
// and hedge ratio 20 and target qty = 5, so effectively target = 100,
|
||||
// we get 50 at the last level, out_qty = floor (50/20) = 2, so the
|
||||
// price should not be the last level but the second last level
|
||||
uint32_t out_quantity = _round_quoting_qty (other_leg_quantity.value() / effective_mult);
|
||||
quantity = Quantity (out_quantity);
|
||||
double out_price = (target_spread_price.value() - other_leg_price.value() * other_leg_data.price_multiplier())/this_leg_data.price_multiplier();
|
||||
out_price = bts::securities::raw_round_to_quote_price (out_price, this_leg_data.tick_size(), this_leg_data.trading_side());
|
||||
price = Price(out_price);
|
||||
Quantity quote_target,
|
||||
const LegPropertyData& quote_leg_data,
|
||||
const LegPropertyData& hedge_leg_data,
|
||||
const bts::orderman::Book& hedge_leg_book,
|
||||
Price& quote_price,
|
||||
Quantity& quote_qty){
|
||||
Price hedge_price;
|
||||
Quantity hedge_qty;
|
||||
compute_driver(quote_target,quote_leg_data,hedge_leg_data,hedge_leg_book,
|
||||
hedge_price,hedge_qty,quote_qty);
|
||||
double out_price = (target_spread_price.value() - hedge_price.value() * hedge_leg_data.price_multiplier())/quote_leg_data.price_multiplier();
|
||||
out_price = bts::securities::raw_round_to_quote_price (out_price, quote_leg_data.tick_size(), quote_leg_data.trading_side());
|
||||
quote_price = Price(out_price);
|
||||
}
|
||||
static void compute_hedge_price_size_2_legs (Price spread_price,
|
||||
const LegPropertyData& quote_filled_leg,
|
||||
Price quote_fill_price,
|
||||
Quantity quote_fill_quantity,
|
||||
double quote_fill_quantity,
|
||||
const LegPropertyData& hedge_leg,
|
||||
Price& out_hedge_price,
|
||||
Quantity& out_hedge_quantity){
|
||||
// for ratio spreads hedge sizes are simply rounded off (the fraction can be a setting)
|
||||
double& out_hedge_quantity){
|
||||
out_hedge_quantity = get_leg_quantity_from_another_leg_quantity (quote_filled_leg, quote_fill_quantity, hedge_leg);
|
||||
double out_hedge_price_d = (spread_price.value() - quote_fill_price.value() * quote_filled_leg.price_multiplier())/hedge_leg.price_multiplier();
|
||||
out_hedge_price_d = bts::securities::raw_round_to_quote_price (out_hedge_price_d, hedge_leg.tick_size(), hedge_leg.trading_side());
|
||||
out_hedge_price = Price(out_hedge_price_d);
|
||||
out_hedge_price = Price(bts::securities::raw_round_to_quote_price (out_hedge_price_d, hedge_leg.tick_size(), hedge_leg.trading_side()));
|
||||
}
|
||||
////////////////////////N legged case
|
||||
static void compute_quoting_price_size_n_legs (Price target_spread_price,
|
||||
Quantity quoting_leg_target_quantity,
|
||||
size_t quoting_leg_index,
|
||||
const ISpreaderManager& spreader,
|
||||
std::vector<QuoterLegDriverData>& driver_vector,
|
||||
std::vector<QuoteLegDriver>& driver_vector,
|
||||
Price& out_price,
|
||||
Quantity& out_quantity){
|
||||
compute_drivers (quoting_leg_target_quantity, quoting_leg_index, spreader, driver_vector);
|
||||
@@ -514,19 +543,23 @@ public:
|
||||
static void compute_drivers(Quantity quoting_leg_target_quantity,
|
||||
size_t quoting_leg_index,
|
||||
const ISpreaderManager& spreader,
|
||||
std::vector<QuoterLegDriverData>& out_driver_vector){
|
||||
const LegPropertyData& quoting_leg_data = spreader.get_leg_data (quoting_leg_index);
|
||||
std::vector<QuoteLegDriver>& out_driver_vector){
|
||||
const LegPropertyData& quote_leg_data = spreader.get_leg_data (quoting_leg_index);
|
||||
size_t n_legs = out_driver_vector.size();
|
||||
for (size_t idx = 0; idx < n_legs; idx++)
|
||||
if (idx != quoting_leg_index){
|
||||
const bts::orderman::Book& driver_leg_book = spreader.get_book(idx);
|
||||
const LegPropertyData& driver_leg_data = spreader.get_leg_data(idx);
|
||||
double driver_effective_mult = std::abs(driver_leg_data.leg_params().quote_params().lean_quantity_ratio() * driver_leg_data.size_multiplier() / quoting_leg_data.size_multiplier());
|
||||
uint32_t driver_leg_target_quantity = _round_hedge_target (driver_effective_mult * quoting_leg_target_quantity.value());
|
||||
Price driver_price; Quantity driver_quantity;
|
||||
SpreaderUtils::traverse_depth_for_quantity(driver_leg_book, SpreaderUtils::invert_side (driver_leg_data.trading_side()), Quantity(driver_leg_target_quantity), driver_price, driver_quantity);
|
||||
Quantity quoting_leg_calc_quantity = Quantity (_round_quoting_qty (driver_quantity.value() / driver_effective_mult));
|
||||
out_driver_vector[idx].set (driver_price, driver_quantity, quoting_leg_calc_quantity);
|
||||
const bts::orderman::Book& hedge_leg_book = spreader.get_book(idx);
|
||||
const LegPropertyData& hedge_leg_data = spreader.get_leg_data(idx);
|
||||
Price hedge_price;
|
||||
Quantity hedge_qty, quote_qty;
|
||||
compute_driver(quoting_leg_target_quantity,
|
||||
quote_leg_data,
|
||||
hedge_leg_data,
|
||||
hedge_leg_book,
|
||||
hedge_price,
|
||||
hedge_qty,
|
||||
quote_qty);
|
||||
out_driver_vector[idx].set (hedge_price, hedge_qty, quote_qty);
|
||||
}
|
||||
}
|
||||
// computes the quoting price from the leg drivers
|
||||
@@ -534,13 +567,13 @@ public:
|
||||
static Price compute_quoting_price_from_drivers(Price target_spread_price,
|
||||
size_t quoting_leg_index,
|
||||
const ISpreaderManager& spreader,
|
||||
const std::vector<QuoterLegDriverData>& driver_vector){
|
||||
const std::vector<QuoteLegDriver>& driver_vector){
|
||||
const LegPropertyData& quoting_leg_data = spreader.get_leg_data (quoting_leg_index);
|
||||
size_t n_legs = driver_vector.size();
|
||||
double sum = 0;
|
||||
for (size_t idx = 0; idx < n_legs; idx++)
|
||||
if (idx != quoting_leg_index)
|
||||
sum += driver_vector[idx].driver_price.value() * spreader.get_leg_data (idx).price_multiplier();
|
||||
sum += driver_vector[idx].driver_price().value() * spreader.get_leg_data (idx).price_multiplier();
|
||||
double rv = (target_spread_price.value() - sum)/quoting_leg_data.price_multiplier();
|
||||
rv = bts::securities::raw_round_to_quote_price (rv, quoting_leg_data.tick_size(), quoting_leg_data.trading_side());
|
||||
return Price(rv);
|
||||
@@ -549,26 +582,40 @@ public:
|
||||
// what each hedging leg can support. this function
|
||||
// simply finds that minimum
|
||||
static Quantity compute_quoting_quantity_from_drivers (size_t quoting_leg_index,
|
||||
const std::vector<QuoterLegDriverData>& driver_vector){
|
||||
const std::vector<QuoteLegDriver>& driver_vector){
|
||||
uint32_t rv = std::numeric_limits<uint32_t>::max();
|
||||
for (size_t idx = 0; idx < driver_vector.size(); idx++)
|
||||
if (idx != quoting_leg_index)
|
||||
if (driver_vector[idx].quoter_leg_quantity.value() < rv)
|
||||
rv = driver_vector[idx].quoter_leg_quantity.value();
|
||||
if (driver_vector[idx].quoter_leg_quantity().value() < rv)
|
||||
rv = driver_vector[idx].quoter_leg_quantity().value();
|
||||
return Quantity(rv);
|
||||
}
|
||||
static void compute_hedge_price_size_n_legs (const LegPropertyData& quote_filled_leg,
|
||||
Price,
|
||||
Quantity quote_fill_quantity,
|
||||
double quote_fill_quantity,
|
||||
const LegPropertyData& hedge_leg,
|
||||
const QuoterLegDriverData& leg_driver_data,
|
||||
Price& out_hedge_price, Quantity& out_hedge_quantity){
|
||||
out_hedge_price = leg_driver_data.driver_price;
|
||||
out_hedge_quantity = get_leg_quantity_from_another_leg_quantity (quote_filled_leg, quote_fill_quantity, hedge_leg);
|
||||
const QuoteLegDriver& leg_driver_data,
|
||||
Price& out_hedge_price,
|
||||
double& out_hedge_quantity){
|
||||
out_hedge_price = Price(leg_driver_data.driver_price().value());
|
||||
out_hedge_quantity = get_leg_quantity_from_another_leg_quantity (quote_filled_leg,
|
||||
quote_fill_quantity,
|
||||
hedge_leg);
|
||||
}
|
||||
////////////////////////////////////////////////////
|
||||
// Rounding functions //////////////////////////////
|
||||
////////////////////////////////////////////////////
|
||||
// rounds a double to a whole number if its too close
|
||||
// to it (this is ued in the case of rounding errors
|
||||
// when 0.9999999 or 1.0000001 is really 1)
|
||||
static double round_if_near_whole(double qty,
|
||||
double vicinity=0.000000001){
|
||||
double round_qty = std::round(qty);
|
||||
if (std::abs(round_qty - qty) <= vicinity)
|
||||
return round_qty;
|
||||
else
|
||||
return qty;
|
||||
}
|
||||
static uint32_t _round_quantity(double qty, bool is_up){
|
||||
double round_err = 0.000000001;
|
||||
double round_qty = std::round(qty);
|
||||
|
||||
+1
-2
@@ -10,8 +10,7 @@ namespace orderman {
|
||||
class SpreaderChildOrderState;
|
||||
class Book;
|
||||
class SingleFillHedger;
|
||||
class IHedgerStateWorker
|
||||
{
|
||||
class IHedgerStateWorker{
|
||||
public:
|
||||
// called once when transition to this state happens
|
||||
virtual void on_transition () = 0;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "UnhedgedDelta.h"
|
||||
#include <cmath>
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "server/sparkd/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
|
||||
|
||||
namespace bts{
|
||||
namespace orderman{
|
||||
|
||||
UnhedgedDelta::UnhedgedDelta()
|
||||
:
|
||||
_qty(0),
|
||||
_avg_price(0){
|
||||
}
|
||||
void UnhedgedDelta::add_unhedged(double price,double qty){
|
||||
bts_assert(qty >= 0);
|
||||
auto total_qty=_qty+qty;
|
||||
_avg_price = (_qty*_avg_price + qty*price)/total_qty;
|
||||
_qty=total_qty;
|
||||
}
|
||||
uint32_t UnhedgedDelta::subtract_hedge_if_available(){
|
||||
uint32_t rv = 0;
|
||||
double qty = SpreaderUtils::round_if_near_whole(_qty);
|
||||
if (qty >= 1){
|
||||
rv = std::floor(qty);
|
||||
_qty = qty-rv;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace bts{
|
||||
namespace orderman{
|
||||
// in the case when the hedge quantity is not a full contract
|
||||
// (ex if we get a 1 lot fill on a 1 X 3 spreader, where 3 is
|
||||
// a quoting leg), this class is used to accumulate the unhedged
|
||||
// delta
|
||||
class UnhedgedDelta{
|
||||
private:
|
||||
double _qty;
|
||||
double _avg_price;
|
||||
public:
|
||||
UnhedgedDelta();
|
||||
// adds to the unhedged qty and updates the
|
||||
// running average price
|
||||
void add_unhedged (double price, double qty);
|
||||
// if >= 1 unhedged delta is available, it
|
||||
// subtracts the integer part and returns it.
|
||||
// else returns 0
|
||||
uint32_t subtract_hedge_if_available();
|
||||
};
|
||||
}}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "QuoteDriverSet.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
QuoteDriverSet::QuoteDriverSet(size_t n_legs)
|
||||
:
|
||||
_quote_seq_no(0),
|
||||
_quote_price(0.0),
|
||||
_drivers(){
|
||||
_drivers.reserve(n_legs);
|
||||
for (size_t i=0; i<n_legs; i++)
|
||||
_drivers.emplace_back();
|
||||
}
|
||||
void QuoteDriverSet::update(uint16_t quote_seq_no,
|
||||
Price quote_price,
|
||||
const std::vector<QuoteLegDriver>& drivers){
|
||||
_quote_seq_no=quote_seq_no;
|
||||
_quote_price=quote_price;
|
||||
bts_assert(_drivers.size()==drivers.size());
|
||||
int i=0;
|
||||
for(const auto& d:drivers)
|
||||
_drivers[i++]=d;
|
||||
}
|
||||
const QuoteLegDriver& QuoteDriverSet::leg_driver(size_t leg_index) const{
|
||||
bts_assert(leg_index<_drivers.size());
|
||||
return _drivers[leg_index];
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "QuoteLegDriver.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
// this is the list of all drivers for a
|
||||
// single quoting leg
|
||||
class QuoteDriverSet{
|
||||
private:
|
||||
uint16_t _quote_seq_no;
|
||||
Price _quote_price;
|
||||
std::vector<QuoteLegDriver> _drivers;
|
||||
public:
|
||||
QuoteDriverSet (size_t n_legs);
|
||||
uint16_t quote_seq_no() const{
|
||||
return _quote_seq_no;
|
||||
}
|
||||
Price quote_price() const{
|
||||
return _quote_price;
|
||||
}
|
||||
void update(uint16_t quote_seq_no,
|
||||
Price quote_price,
|
||||
const std::vector<QuoteLegDriver>& drivers);
|
||||
const QuoteLegDriver& leg_driver (size_t leg_index) const;
|
||||
};
|
||||
}}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
#include "QuoteDriverSetHistory.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
QuoteDriverSetHistory::QuoteDriverSetHistory(size_t n_legs,
|
||||
size_t cache_size,
|
||||
double tick_size)
|
||||
:
|
||||
_n_legs(n_legs),
|
||||
_cache_size(cache_size),
|
||||
_tick_size(tick_size),
|
||||
_quote_seq_no(0),
|
||||
_driver_sets(),
|
||||
_h_ptr(0){
|
||||
_driver_sets.reserve(cache_size);
|
||||
for(size_t i=0;i<cache_size;i++)
|
||||
_driver_sets.emplace_back(n_legs);
|
||||
}
|
||||
void QuoteDriverSetHistory::add(const std::vector<QuoteLegDriver>& drivers,
|
||||
Price quote_price){
|
||||
bts_assert(_h_ptr<_cache_size);
|
||||
bts_assert(_cache_size>0);
|
||||
_quote_seq_no++;
|
||||
_driver_sets[_h_ptr].update(_quote_seq_no,quote_price,drivers);
|
||||
// move it to the next writeable place
|
||||
if(_h_ptr == _cache_size-1)
|
||||
_h_ptr = 0;
|
||||
else
|
||||
_h_ptr++;
|
||||
}
|
||||
const QuoteDriverSet* QuoteDriverSetHistory::_latest() const{
|
||||
if (_quote_seq_no>0)
|
||||
return &(_driver_sets[_h_ptr==0 ? _cache_size-1 : _h_ptr-1]);
|
||||
return
|
||||
nullptr;
|
||||
}
|
||||
const QuoteDriverSet* QuoteDriverSetHistory::_find_driver_set(Price quote_price) const{
|
||||
const QuoteDriverSet* rv=nullptr;
|
||||
for(const auto& d : _driver_sets)
|
||||
if (bts::securities::test_price_equality(d.quote_price().value(),
|
||||
quote_price.value(),
|
||||
_tick_size))
|
||||
if (rv==nullptr||d.quote_seq_no()>rv->quote_seq_no())
|
||||
rv=&d;
|
||||
return rv!=nullptr ? rv:_latest();
|
||||
}
|
||||
const QuoteLegDriver* QuoteDriverSetHistory::find_driver(Price quote_price,
|
||||
size_t leg_index) const{
|
||||
const QuoteDriverSet* driver_set_ptr=_find_driver_set(quote_price);
|
||||
return driver_set_ptr==nullptr ? nullptr : &(driver_set_ptr->leg_driver(leg_index));
|
||||
}
|
||||
}}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "QuoteDriverSet.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class QuoteDriverSetHistory{
|
||||
private:
|
||||
const size_t _n_legs, _cache_size;
|
||||
const double _tick_size;
|
||||
uint16_t _quote_seq_no;
|
||||
std::vector<QuoteDriverSet> _driver_sets;
|
||||
uint16_t _h_ptr;
|
||||
const QuoteDriverSet* _latest() const;
|
||||
const QuoteDriverSet* _find_driver_set(Price quote_price) const;
|
||||
public:
|
||||
QuoteDriverSetHistory(size_t n_legs,
|
||||
size_t cache_size,
|
||||
double tick_size);
|
||||
void add(const std::vector<QuoteLegDriver>& drivers,
|
||||
Price quote_price);
|
||||
const QuoteLegDriver* find_driver(Price quote_price, size_t leg_index) const;
|
||||
};
|
||||
|
||||
}}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "QuoteLegDriver.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
QuoteLegDriver::QuoteLegDriver()
|
||||
:
|
||||
_driver_price(0.0),
|
||||
_driver_quantity(0),
|
||||
_quoter_leg_quantity(0){
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
// Should be read as Quote_LegDriver, this is the
|
||||
// price and qty in a hedging leg that can sustain
|
||||
// a "_quoter_leg_quantity" sized quote in the
|
||||
// quoting leg
|
||||
class QuoteLegDriver
|
||||
{
|
||||
private:
|
||||
Price _driver_price;
|
||||
Quantity _driver_quantity;
|
||||
Quantity _quoter_leg_quantity;
|
||||
public:
|
||||
// driver_price and driver_quantity are the price and quantity
|
||||
// in the hedging (or driver) leg that can drive a quoter_leg_quantity
|
||||
// sized order in the quoting leg
|
||||
QuoteLegDriver();
|
||||
QuoteLegDriver& operator=(const QuoteLegDriver& s){
|
||||
set(s.driver_price(),s.driver_quantity(), s.quoter_leg_quantity());
|
||||
return *this;
|
||||
}
|
||||
void set(Price driver_price,
|
||||
Quantity driver_quantity,
|
||||
Quantity quoter_leg_quantity){
|
||||
_driver_price = driver_price;
|
||||
_driver_quantity = driver_quantity;
|
||||
_quoter_leg_quantity = quoter_leg_quantity;
|
||||
}
|
||||
Price driver_price() const{
|
||||
return _driver_price;
|
||||
}
|
||||
Quantity driver_quantity() const{
|
||||
return _driver_quantity;
|
||||
}
|
||||
Quantity quoter_leg_quantity() const{
|
||||
return _quoter_leg_quantity;
|
||||
}
|
||||
};
|
||||
}}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
// the concept of a "driver" is the size and price of a leg that
|
||||
// can sustain a quote in the quoted leg
|
||||
class QuoterLegDriverData
|
||||
{
|
||||
public:
|
||||
// driver_price and driver_quantity are the price and quantity
|
||||
// in the hedging (or driver) leg that can drive a quoter_leg_quantity
|
||||
// sized order in the quoting leg
|
||||
Price driver_price;
|
||||
Quantity driver_quantity;
|
||||
Quantity quoter_leg_quantity;
|
||||
|
||||
QuoterLegDriverData()
|
||||
:
|
||||
driver_price(0.0),
|
||||
driver_quantity(0),
|
||||
quoter_leg_quantity(0)
|
||||
{}
|
||||
void set(Price new_driver_price, Quantity new_driver_quantity, Quantity new_quoter_leg_quantity)
|
||||
{
|
||||
driver_price = new_driver_price;
|
||||
driver_quantity = new_driver_quantity;
|
||||
quoter_leg_quantity = new_quoter_leg_quantity;
|
||||
}
|
||||
};
|
||||
}}
|
||||
+1
-1
@@ -51,7 +51,7 @@ void QuoterWaitingForSizeStateWorker::_longpath_analyze_and_act (orderman::TickT
|
||||
Price out_price; Quantity out_quantity;
|
||||
_quoter->_compute_price_quantity (out_price, out_quantity);
|
||||
if (out_quantity.value() > 0){
|
||||
_quoter->_commit_drivers ();
|
||||
_quoter->_commit_drivers (out_price);
|
||||
LOG(OrderMan, Info, "QuoterWaitingForSizeStateWorker sending order key: {} price: {} quantity: {}", _quoter->_key(), out_price, out_quantity);
|
||||
// this is the only place where a quoter order is entered
|
||||
_quoter->_place_order(out_price, out_quantity, cid);
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ void QuoterWorkingStateWorker::_modify_order (orderman::TickToTradeCorID cid, Pr
|
||||
bts::securities::test_price_equality(_quoter->_price_quantity_state.out_price().value(),
|
||||
price.value(), _quoter->_this_leg_data.tick_size());
|
||||
if (!same_price_qty){
|
||||
_quoter->_commit_drivers ();
|
||||
_quoter->_commit_drivers (price);
|
||||
_quoter->_modify_order(price, quantity, cid);
|
||||
}
|
||||
}
|
||||
|
||||
+24
-12
@@ -97,19 +97,31 @@ void SpreaderWorkingStateWorker::on_quoter_order_fill(SpreaderLegData& leg_data)
|
||||
order_update_message.last_update_filled_quantity(),
|
||||
order_update_message.last_update_filled_price());
|
||||
bts_assert (order_update_message.last_update_filled_quantity().value() > 0);
|
||||
for (size_t q_idx = 0; q_idx < _spreader->_bookkeeping_data().n_legs(); q_idx++){
|
||||
SpreaderLegData& other_leg_data = _spreader->_bookkeeping_data().get_leg_data (q_idx);
|
||||
for (size_t q_idx = 0; q_idx < _spreader->_bookkeeping_data().n_legs(); q_idx++){
|
||||
if (q_idx != leg_data.leg_property_data().leg_index()){
|
||||
// if it is an actively quoting leg, inform the quoter about it
|
||||
// so it can decrease the proportional size
|
||||
if (other_leg_data.leg_property_data().is_active_quoting())
|
||||
other_leg_data.quoter().update_other_leg_fills(leg_data.leg_property_data().leg_index(),
|
||||
order_update_message.last_update_filled_quantity());
|
||||
// a hedge is sent regardless
|
||||
_spreader->_send_hedge (leg_data.leg_property_data().leg_index(),
|
||||
order_update_message.last_update_filled_price(),
|
||||
order_update_message.last_update_filled_quantity(),
|
||||
q_idx);
|
||||
SpreaderLegData& other_leg_data = _spreader->_bookkeeping_data().get_leg_data (q_idx);
|
||||
Price hedge_price;
|
||||
double hedge_qty;
|
||||
_spreader->_compute_hedge_price_quantity(leg_data.leg_property_data().leg_index(),
|
||||
order_update_message.last_update_filled_price(),
|
||||
order_update_message.last_update_filled_quantity().value(),
|
||||
q_idx,
|
||||
hedge_price,
|
||||
hedge_qty);
|
||||
UnhedgedDelta& leg_unhedged_delta = other_leg_data.unhedged_delta();
|
||||
leg_unhedged_delta.add_unhedged(hedge_price.value(), hedge_qty);
|
||||
uint32_t total_unhedged_qty=leg_unhedged_delta.subtract_hedge_if_available();
|
||||
if (total_unhedged_qty != 0){
|
||||
// if it is an actively quoting leg, inform the quoter about it
|
||||
// so it can decrease the proportional size
|
||||
if (other_leg_data.leg_property_data().is_active_quoting())
|
||||
other_leg_data.quoter().update_other_leg_fills(leg_data.leg_property_data().leg_index(),
|
||||
Quantity(total_unhedged_qty));
|
||||
// a hedge is sent regardless
|
||||
_spreader->_send_hedge(other_leg_data.leg_property_data().leg_index(),
|
||||
hedge_price,
|
||||
Quantity(total_unhedged_qty));
|
||||
}
|
||||
}
|
||||
}
|
||||
// this is done at the end (i dont like ordering though) because
|
||||
|
||||
@@ -483,7 +483,7 @@ TEST_CASE ("compute quoting price quantity 3 legs", "[spreader]")
|
||||
std::vector<LegSpreaderParams> params; params.push_back(leg_params_1);params.push_back(leg_params_2);params.push_back(leg_params_3);
|
||||
|
||||
SpreaderManagerForTest* sm = create_spreader_manager_n_legs (OrderKey(), legs, params);
|
||||
std::vector<QuoterLegDriverData> driver_vector;
|
||||
std::vector<QuoteLegDriver> driver_vector;
|
||||
for(size_t i = 0; i < legs.size(); i++) driver_vector.emplace_back();
|
||||
|
||||
SECTION("basic joining")
|
||||
@@ -570,8 +570,9 @@ TEST_CASE ("compute quoting price quantity 3 legs", "[spreader]")
|
||||
TEST_CASE ("compute hedge price quantity 2 legs", "[spreader]")
|
||||
{
|
||||
ContractLeg leg_1, leg_2;
|
||||
Price out_hedge_price; Quantity out_hedge_quantity;
|
||||
Price spreader_price = Price (0.35);
|
||||
Price out_hedge_price;
|
||||
double out_hedge_quantity;
|
||||
Price spreader_price = Price(0.35);
|
||||
double size_mult_1 = 0.25, size_mult_2 = -1.1, // same used for price and qty
|
||||
price_mult_1 = 0.35, price_mult_2 = -1.5;
|
||||
double lean_mult = 1.0;
|
||||
@@ -580,23 +581,23 @@ TEST_CASE ("compute hedge price quantity 2 legs", "[spreader]")
|
||||
LegPropertyData leg_prop_data_1 = create_leg_property_data (leg_1, 0, price_mult_1, true, lean_mult);
|
||||
LegPropertyData leg_prop_data_2 = create_leg_property_data (leg_2, 1, price_mult_2, true, lean_mult);
|
||||
// md
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price (2500.5), Quantity (3),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price(2500.5), 3,
|
||||
leg_prop_data_1, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 1);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 0.6818181818181818181) < 0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 10717.25);
|
||||
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price (2500.5), Quantity (2),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_2, Price(2500.5), 2,
|
||||
leg_prop_data_1, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 0);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 0.45454545454545454545) < 0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 10717.25);
|
||||
// reversing the side of the quote fill
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price (10000.5), Quantity (2),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price(10000.5), 2,
|
||||
leg_prop_data_2, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 9);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 8.8)<0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 2333.25);
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price (9997), Quantity (6),
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs (spreader_price, leg_prop_data_1, Price(9997), 6,
|
||||
leg_prop_data_2, out_hedge_price, out_hedge_quantity);
|
||||
REQUIRE (out_hedge_quantity.value() == 26);
|
||||
REQUIRE (std::abs(out_hedge_quantity - 26.40) < 0.000000001);
|
||||
REQUIRE (out_hedge_price.value() == 2332.5);
|
||||
}
|
||||
/************************************************************************************************************/
|
||||
@@ -2341,6 +2342,201 @@ TEST_CASE ("prehedge_payup", "[spreader]"){
|
||||
REQUIRE(bts::securities::test_price_equality(out_price.value(),21.10,0.05));
|
||||
}
|
||||
}
|
||||
// n legged spreader price evaluation /////////////////////
|
||||
///////////////////////////////////////////////////////////
|
||||
TEST_CASE ("nlegs_compute_driver", "[spreader]"){
|
||||
bts::orderman::OrderKey order_key(1);
|
||||
ContractLeg leg_1, leg_2, leg_3, leg_4;
|
||||
fill_future_contract_leg (leg_1, 1, "CL", bts::api::Venue::VENUE_GLBX, 2018, 1, 15, 1.0, 1.0, api::Side::SIDE_BUY, 1.0, 0.01);
|
||||
fill_future_contract_leg (leg_2, 2, "CL", bts::api::Venue::VENUE_GLBX, 2018, 2, 15, 1.0, 1.0, api::Side::SIDE_BUY, 1.0, 0.01);
|
||||
fill_future_contract_leg (leg_3, 3, "CL", bts::api::Venue::VENUE_GLBX, 2018, 3, 15, 1.0, 1.0, api::Side::SIDE_BUY, 1.0, 0.01);
|
||||
fill_future_contract_leg (leg_4, 4, "CL", bts::api::Venue::VENUE_GLBX, 2018, 4, 15, -3.0, -3.0, api::Side::SIDE_SELL, 1.0, 0.01);
|
||||
std::vector<ContractLeg> legs;
|
||||
legs.push_back(leg_1);legs.push_back(leg_2);legs.push_back(leg_3);legs.push_back(leg_4);
|
||||
Security sec;
|
||||
fill_synthetic_security (sec, 2000, "4legs", legs);
|
||||
// execution params/order def
|
||||
LegSpreaderParams lsp_1, lsp_2, lsp_3, lsp_4;
|
||||
fill_leg_spreader_params(lsp_1, 1.0, false, 1.0);fill_leg_spreader_params(lsp_2, 1.0, false, 1.0);
|
||||
fill_leg_spreader_params(lsp_3, 1.0, false, 1.0);fill_leg_spreader_params(lsp_4, -4.0, true, 1.0);
|
||||
// // post hedge time out here
|
||||
std::vector<LegSpreaderParams> params;
|
||||
params.push_back(lsp_1);params.push_back(lsp_2);
|
||||
params.push_back(lsp_3);params.push_back(lsp_4);
|
||||
bts::api::orderman::OrderDefinition order_def;
|
||||
fill_spreader_order_definition(order_def, Side::SIDE_BUY, sec, params);
|
||||
// putting it together in SpreaderBookkeepingData
|
||||
SpreaderPreActivationOrderServices::TestOnlySpreaderPreActivationOrderServices
|
||||
testonly_spreader_pao_services = _create_spreader_pao_services(sec);
|
||||
SpreaderPreActivationOrderServices pao_services (&testonly_spreader_pao_services);
|
||||
std::vector<LegPropertyData> leg_props;
|
||||
SpreaderUtils::resolve_synthetic_spread(pao_services, order_def, leg_props);
|
||||
SpreaderManagerForTest spreader_manager (order_key, order_def, pao_services, leg_props);
|
||||
SpreaderBookkeepingData& bookkeeping_data = spreader_manager.bookkeeping_data();
|
||||
SECTION("compute_1"){
|
||||
Quantity quoting_leg_target (300);
|
||||
Price spread_price(0.25);
|
||||
fill_book(spreader_manager, 0, new double[5]{65.31,65.32,65.34,65.36, 65.40},
|
||||
new uint16_t[5]{12,18,20,19,23},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 1, new double[5]{64.40,64.41,64.43,64.45,64.50},
|
||||
new uint16_t[5]{1,5,20,12,15},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 2, new double[5]{64.10,64.18,64.19,64.20,64.21},
|
||||
new uint16_t[5]{10,43,17,50,15},bts::api::Side::SIDE_SELL,true);
|
||||
std::vector<QuoteLegDriver> out_driver_vector;
|
||||
for (int i=0; i<4; i++)
|
||||
out_driver_vector.emplace_back();
|
||||
SpreaderUtils::compute_drivers(quoting_leg_target, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],92,65.40,276);
|
||||
_verify_driver (out_driver_vector[1],53,64.50,159);
|
||||
_verify_driver (out_driver_vector[2],100,64.20,300);
|
||||
// size of the quoting leg
|
||||
Quantity computed_quoted_qty=SpreaderUtils::compute_quoting_quantity_from_drivers(3,out_driver_vector);
|
||||
REQUIRE(computed_quoted_qty.value()==159);
|
||||
// 2nd pass drivers
|
||||
SpreaderUtils::compute_drivers(computed_quoted_qty, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],53,65.36,159);
|
||||
_verify_driver (out_driver_vector[1],53,64.50,159);
|
||||
_verify_driver (out_driver_vector[2],53,64.18,159);
|
||||
// price of the quoting leg
|
||||
Price computed_price=SpreaderUtils::compute_quoting_price_from_drivers(spread_price,3,spreader_manager,out_driver_vector);
|
||||
REQUIRE(computed_price.value()==64.60);
|
||||
//hedging
|
||||
Price hedge_price; double hedge_quantity;
|
||||
SpreaderUtils::compute_hedge_price_size_n_legs(spreader_manager.get_leg_data(3),
|
||||
Price(0),
|
||||
18,
|
||||
spreader_manager.get_leg_data(0),
|
||||
out_driver_vector[0],
|
||||
hedge_price,
|
||||
hedge_quantity);
|
||||
REQUIRE(hedge_price.value()==65.36);
|
||||
REQUIRE(hedge_quantity==6);
|
||||
}
|
||||
SECTION("compute_2_fractional"){
|
||||
Quantity quoting_leg_target (100);
|
||||
Price spread_price(0.25);
|
||||
fill_book(spreader_manager, 0, new double[5]{65.31,65.32,65.34,65.36, 65.40},
|
||||
new uint16_t[5]{12,18,20,19,23},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 1, new double[5]{64.40,64.41,64.43,64.45,64.50},
|
||||
new uint16_t[5]{1,5,20,12,15},bts::api::Side::SIDE_SELL,true);
|
||||
fill_book(spreader_manager, 2, new double[5]{64.10,64.18,64.19,64.20,64.21},
|
||||
new uint16_t[5]{11,1,1,5,1},bts::api::Side::SIDE_SELL,true);
|
||||
std::vector<QuoteLegDriver> out_driver_vector;
|
||||
for (int i=0; i<4; i++)
|
||||
out_driver_vector.emplace_back();
|
||||
SpreaderUtils::compute_drivers(quoting_leg_target, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],34,65.34,100);
|
||||
_verify_driver (out_driver_vector[1],34,64.45,100);
|
||||
_verify_driver (out_driver_vector[2],19,64.21,57);
|
||||
// size of the quoting leg
|
||||
Quantity computed_quoted_qty=SpreaderUtils::compute_quoting_quantity_from_drivers(3,out_driver_vector);
|
||||
REQUIRE(computed_quoted_qty.value()==57);
|
||||
// // 2nd pass drivers
|
||||
SpreaderUtils::compute_drivers(computed_quoted_qty, 3, spreader_manager, out_driver_vector);
|
||||
_verify_driver (out_driver_vector[0],19,65.32,57);
|
||||
_verify_driver (out_driver_vector[1],19,64.43,57);
|
||||
_verify_driver (out_driver_vector[2],19,64.21,57);
|
||||
// // price of the quoting leg
|
||||
Price computed_price=SpreaderUtils::compute_quoting_price_from_drivers(spread_price,3,spreader_manager,out_driver_vector);
|
||||
REQUIRE(computed_price.value()==64.57);
|
||||
// //hedging
|
||||
Price hedge_price; double hedge_quantity;
|
||||
SpreaderUtils::compute_hedge_price_size_n_legs(spreader_manager.get_leg_data(3),
|
||||
Price(0),
|
||||
10.0,
|
||||
spreader_manager.get_leg_data(0),
|
||||
out_driver_vector[0],
|
||||
hedge_price,
|
||||
hedge_quantity);
|
||||
REQUIRE(hedge_price.value()==65.32);
|
||||
REQUIRE(std::abs(hedge_quantity - 3.333333) < 0.000001);
|
||||
}
|
||||
}
|
||||
TEST_CASE ("quote driver caching", "[spreader]"){
|
||||
SECTION("zero_search"){
|
||||
QuoteDriverSetHistory driver_cache(5,10,0.05);
|
||||
const QuoteLegDriver* driver_ptr=driver_cache.find_driver(Price(100),3);
|
||||
REQUIRE(driver_ptr==nullptr);
|
||||
}
|
||||
SECTION("simple_search"){
|
||||
size_t n_legs=5, cache_sz=10;
|
||||
QuoteDriverSetHistory driver_cache(n_legs,cache_sz,0.05);
|
||||
Price p1(100); std::vector<QuoteLegDriver> d1;
|
||||
generate_driver_set(d1,n_legs);
|
||||
driver_cache.add(d1,p1);
|
||||
Price p2(100.05); std::vector<QuoteLegDriver> d2;
|
||||
generate_driver_set(d2,n_legs);
|
||||
driver_cache.add(d2,p2);
|
||||
Price p3(100.10); std::vector<QuoteLegDriver> d3;
|
||||
generate_driver_set(d3,n_legs);
|
||||
driver_cache.add(d3,p3);
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p1,i);
|
||||
verify_driver(*d,d1[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p2,i);
|
||||
verify_driver(*d,d2[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p3,i);
|
||||
verify_driver(*d,d3[i]);
|
||||
}
|
||||
// if not found, returns the latest
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(Price(100.01),i);
|
||||
verify_driver(*d,d3[i]);
|
||||
}
|
||||
}
|
||||
SECTION("collision_search and continous add"){
|
||||
// adding multiple updates to test circular nature
|
||||
size_t n_runs=10003, n_legs=4, cache_sz=10;
|
||||
QuoteDriverSetHistory driver_cache(n_legs,cache_sz,0.05);
|
||||
for(size_t i=0; i<n_runs; i++){
|
||||
Price p(gen_rand(1,100));
|
||||
std::vector<QuoteLegDriver> d;
|
||||
generate_driver_set(d,n_legs);
|
||||
driver_cache.add(d,p);
|
||||
}
|
||||
Price p1(100); std::vector<QuoteLegDriver> d1;
|
||||
generate_driver_set(d1,n_legs);
|
||||
driver_cache.add(d1,p1);
|
||||
Price p2(100.05); std::vector<QuoteLegDriver> d2;
|
||||
generate_driver_set(d2,n_legs);
|
||||
driver_cache.add(d2,p2);
|
||||
Price p3(100.10); std::vector<QuoteLegDriver> d3;
|
||||
generate_driver_set(d3,n_legs);
|
||||
driver_cache.add(d3,p3);
|
||||
Price p4(100); std::vector<QuoteLegDriver> d4;
|
||||
generate_driver_set(d4,n_legs);
|
||||
driver_cache.add(d4,p4);
|
||||
Price p5(100.15); std::vector<QuoteLegDriver> d5;
|
||||
generate_driver_set(d5,n_legs);
|
||||
driver_cache.add(d5,p5);
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p1,i);
|
||||
verify_driver(*d,d4[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p2,i);
|
||||
verify_driver(*d,d2[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p3,i);
|
||||
verify_driver(*d,d3[i]);
|
||||
}
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p4,i);
|
||||
verify_driver(*d,d4[i]);
|
||||
}
|
||||
// 100 is there twice, should returns the
|
||||
// most recently added one
|
||||
for(size_t i=0; i<n_legs; i++){
|
||||
const auto d=driver_cache.find_driver(p5,i);
|
||||
verify_driver(*d,d5[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
class RV
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -292,6 +292,20 @@ bts::orderman::Book get_book(SecurityKey key,
|
||||
{
|
||||
return get_book(key, n_levels, bid_prices, bid_sizes, n_levels, ask_prices, ask_sizes, delete_arrays);
|
||||
}
|
||||
void fill_book (SpreaderManagerForTest& sm,
|
||||
size_t index,
|
||||
double* prices,
|
||||
uint16_t* sizes,
|
||||
bts::api::Side side,
|
||||
bool delete_arrays){
|
||||
Book book;
|
||||
fill_book (book, prices, sizes, side);
|
||||
sm.set_book(book, index);
|
||||
if (delete_arrays){
|
||||
delete[] prices;
|
||||
delete[] sizes;
|
||||
}
|
||||
}
|
||||
/**********************************************************************/
|
||||
/******************************hedger**********************************/
|
||||
/**********************************************************************/
|
||||
@@ -464,14 +478,38 @@ void set_spreader_orders (SpreaderOrder& spreader, double* leg_1_bid_prices, uin
|
||||
/**********************************************************************/
|
||||
/******************************misc************************************/
|
||||
/**********************************************************************/
|
||||
void _verify_driver(const QuoteLegDriver& driver_data,
|
||||
uint16_t driver_qty,
|
||||
double driver_price,
|
||||
uint16_t quoter_leg_quantity){
|
||||
double err = 1e-9;
|
||||
REQUIRE(std::abs(driver_data.driver_price().value() - driver_price)< err);
|
||||
REQUIRE(driver_data.driver_quantity().value() == driver_qty);
|
||||
REQUIRE(driver_data.quoter_leg_quantity().value() == quoter_leg_quantity);
|
||||
}
|
||||
void verify_driver(const QuoteLegDriver& d1,
|
||||
const QuoteLegDriver& d2){
|
||||
_verify_driver(d1,
|
||||
d2.driver_quantity().value(),
|
||||
d2.driver_price().value(),
|
||||
d2.quoter_leg_quantity().value());
|
||||
}
|
||||
double gen_rand (double min, double max)
|
||||
{
|
||||
double f = (double)rand() / RAND_MAX;
|
||||
return min + f * (max - min);
|
||||
}
|
||||
// SpreaderChildOrderState create_order_update(const LegPropertyData& leg_data)
|
||||
// {
|
||||
// SpreaderChildOrderState rv(false, leg_data, true);
|
||||
// return rv;
|
||||
// }
|
||||
}}
|
||||
void fill_random_driver(QuoteLegDriver& driver){
|
||||
driver.set(Price(gen_rand(0,10)),
|
||||
Quantity(gen_rand(1,100)),
|
||||
Quantity(gen_rand(1,300)));
|
||||
}
|
||||
void generate_driver_set(std::vector<QuoteLegDriver>& drivers, size_t n_legs){
|
||||
drivers.clear();
|
||||
for(size_t i=0; i < n_legs; i++){
|
||||
QuoteLegDriver d;
|
||||
fill_random_driver(d);
|
||||
drivers.push_back(d);
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,44 @@
|
||||
This test verifies that fractional hedges work. That is, when a fill in a quoting leg does
|
||||
not equate to one full contract in the hedging leg, we accumulate the unhedged
|
||||
delta, until we can send one or more hedging contracts.
|
||||
|
||||
1. Create a new synthetic spread. For the legs choose SIF9 and SIH9 (if you dont
|
||||
have these contracts, other contracts can be chosen too) and for the quantities choose 1 and -1.5
|
||||
respectively (and same for the Price multiplier). Disable all quoting, pre-hedge and post-hedge
|
||||
parameters. Name the synthetic SIF9-1.5SIH9.
|
||||
|
||||
1. Bring up the ladder for the synthetic and for the 2 legs.
|
||||
|
||||
2. Enter a 10 lot sell order at 20.0 for SIF9 (If the ladder is showing blanks use the market view
|
||||
to enter orders). The quoting order will lean against this.
|
||||
|
||||
3. Enter a 10 lot buy order for the synthetic at -9.900.
|
||||
|
||||
4. Verify that a 15 lot quoting order is sent for SIH9 at 19.935.
|
||||
|
||||
5. Now we will start filling the quoting order and verify the hedges. A 1 lot fill in the
|
||||
quoting leg means a 2/3 contract in the hedging leg. Since we can not send 2/3 constracts,
|
||||
we will add it to the "unhedged delta" till the unhedged delta > 1, and then we will send one
|
||||
or more orders to get the unhedged delta < 1.
|
||||
|
||||
Force quoting fills based on the left most column (by lifting the quoting offer)
|
||||
and verify that hedge orders of size "hedge_qty" (3rd column) are sent as a result.
|
||||
The "unhedged_delta" column shows the unhedged delta immediatly after the quoting fill
|
||||
and the "remaining_unhedged_delta" column is the unhedged delta after the hedge order
|
||||
has been sent.
|
||||
You can check the hedge orders being sent easily by looking at the order reduction
|
||||
happening on the order sent in step 2 (or look at the hedges in the orderbook).
|
||||
------------------------------------------------------------------------------
|
||||
quoting fill unhedged_delta hedge_order_sent remaining_unhedged_delta
|
||||
------------------------------------------------------------------------------
|
||||
1 2/3 0 2/3
|
||||
1 4/3 1 1/3
|
||||
1 1 1 0
|
||||
4 8/3 2 2/3
|
||||
5 4 4 0
|
||||
2 4/3 1 1/3
|
||||
1 1 1 0
|
||||
|
||||
6. At the end of this, the whole order should be filled. Check the order book
|
||||
to make sure the fill price is -9.900, there are 10 total fills in SIF9 and
|
||||
15 fills in SIH9.
|
||||
@@ -0,0 +1,34 @@
|
||||
This test makes sure the basic spreader functionality works for 4 legged synthetic spreads
|
||||
with 1 quoting leg.
|
||||
|
||||
0. Run this test in sim, with no external markets. We will control the markets with limit orders.
|
||||
|
||||
1. Create a 4 legged synthetic spread with legs SIF9, SIH9, SIK9, SIN9, with leg quantities and
|
||||
price multipliers of 1,1,1 and -1 respectively. Leg SIN9 is the only quoting leg.
|
||||
Disable all Quote, Pre-Hedge and Post-Hedge paramaters (leg contracts are arbitrary, so if you
|
||||
dont have those contracts, choose any set of 4 contracts).
|
||||
|
||||
2. Enter a synthetic buy order of 10 lots at 40.00. The ladders will most likely be empty, so
|
||||
use the market view to enter orders.
|
||||
|
||||
3. You will see no quoting orders since all the 3 hedging legs have no markets. Now enter
|
||||
a 10 lot sell orders at 19.950 for SIH9, and 20.200 for SIK9. There should still be no quoting
|
||||
order for SIN9 since the SIF9 hedge leg still doesnt have a market. Enter a 1 lot sell order at
|
||||
20.00 for SIF9. This will result in the spreader sending a 1 lot quoting order at 20.150.
|
||||
|
||||
4. Now cancel the SIK9 order. This should result in the quoting order getting cancelled as well.
|
||||
Re-enter the SIK9 order, 10 lots at 20.200 and the quoting order should appear as well.
|
||||
|
||||
5. Now lift the quoting leg ask with a 1 lot buy order at the same price. Make sure hedges are sent (and filled)
|
||||
for all the 3 hedging legs. SIH9 should have no market now and hence we should have no quoting order.
|
||||
|
||||
6. Enter a 10 lot sell order at 20.000 for SIF9. This should result in a 9 lot quoting order.
|
||||
|
||||
7. Now randomly move the hedge orders as well as the spreader order a few times, and make sure the
|
||||
quoting order follows it and is correct
|
||||
quoting_order_price = (SIF9's ask + SIH9's ask + SIK9's ask - spreader_price)
|
||||
rounded up to the tick.
|
||||
|
||||
8. Now lift the complete quoter offer by crossing it with a 9 lot bid. Make sure all the hedges
|
||||
are sent and the spreader order reached a Completed state in the orderbook.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
@echo off
|
||||
@setlocal
|
||||
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"
|
||||
ENDLOCAL
|
||||
|
||||
goto :eof
|
||||
|
||||
:err
|
||||
echo usage git_switch_linux [branch]
|
||||
Reference in new issue
Block a user