Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f12b8c4b1 | ||
|
|
30b6719c2d |
No files matched your search
@@ -18,6 +18,7 @@ src/client/spark/Properties/launchSettings.json
|
||||
compile_commands.json
|
||||
.cquery_cached_index/
|
||||
src/client/sparkcmd/Properties/launchSettings.json
|
||||
src/sdk/examples/client_sdk/bts.api.csharp/src/Properties/launchSettings.json
|
||||
node_modules
|
||||
paket-files
|
||||
project.lock.json
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ else ()
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0 ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||
COMMENT "Creating server runtime layout"
|
||||
|
||||
+1
-1
Submodule docker/linux updated: 296f277d40...0df20ec20a.
+1
-1
Submodule externals updated: 2ea5e355a6...a558597a1a.
@@ -17,7 +17,7 @@ namespace bts.shell {
|
||||
class MarketShellModule : IShellModule {
|
||||
[ImportingConstructor]
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model) {
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub, orders_model.security_auxiliary_model);
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
|
||||
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -26,6 +24,7 @@ namespace mbts.spark {
|
||||
readonly AnimationManager _animation = new AnimationManager();
|
||||
readonly ObservableCollection<OrderRecordImmutable> _orders = new ObservableCollection<OrderRecordImmutable>();
|
||||
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<bool> _last_price_from_spread;
|
||||
readonly CellProperty<int> _position;
|
||||
@@ -34,51 +33,48 @@ namespace mbts.spark {
|
||||
readonly CellProperty<bool> _inside_bids, _inside_asks;
|
||||
readonly CellProperty<int> _working_bid_volume, _working_ask_volume;
|
||||
readonly CellProperty<double> _working_best_bid, _working_best_ask;
|
||||
readonly CellProperty<string> _working_bids_text, _working_asks_text;
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
PropertyAutoWrapper<uint, uint>
|
||||
_bid_direct_size,
|
||||
_bid_implied_size,
|
||||
_bid_merged_size,
|
||||
_ask_direct_size,
|
||||
_ask_implied_size,
|
||||
_ask_merged_size;
|
||||
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
double _raw_day_position;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
uint? _display_qty_divisor_backing;
|
||||
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_security_text = smd.security.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
string format_price(double price) => security.ContractSpec.format(price);
|
||||
_position = new CellProperty<int>(this, nameof(position), fmt_display_qty_int);
|
||||
_position = new CellProperty<int>(this, nameof(position), 0);
|
||||
_bid_price = backing(nameof(bid_price), double.NaN);
|
||||
_ask_price = backing(nameof(ask_price), double.NaN);
|
||||
_last_price = backing(nameof(last_price), double.NaN);
|
||||
_last_size = new CellProperty<uint>(this, nameof(last_size), fmt_display_qty_uint);
|
||||
_last_size = new CellProperty<uint>(this, nameof(last_size), 0);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), fmt_display_qty_ulong, 0);
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
|
||||
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
|
||||
_inside_asks = new CellProperty<bool>(this, nameof(inside_asks));
|
||||
_working_bid_volume = new CellProperty<int>(this, nameof(working_bid_volume));
|
||||
_working_ask_volume = new CellProperty<int>(this, nameof(working_ask_volume));
|
||||
//
|
||||
_bid_direct_size = backing(nameof(bid_direct_size), 0u, convert_display_qty_uint);
|
||||
_bid_implied_size = backing(nameof(bid_implied_size), 0u, convert_display_qty_uint);
|
||||
_bid_merged_size = backing(nameof(bid_merged_size), 0u, convert_display_qty_uint);
|
||||
_ask_direct_size = backing(nameof(ask_direct_size), 0u, convert_display_qty_uint);
|
||||
_ask_implied_size = backing(nameof(ask_implied_size), 0u, convert_display_qty_uint);
|
||||
_ask_merged_size = backing(nameof(ask_merged_size), 0u, convert_display_qty_uint);
|
||||
_bid_direct_size = backing(nameof(bid_direct_size), default(uint));
|
||||
_bid_merged_size = backing(nameof(bid_merged_size), default(uint));
|
||||
_ask_direct_size = backing(nameof(ask_direct_size), default(uint));
|
||||
_ask_merged_size = backing(nameof(ask_merged_size), default(uint));
|
||||
//
|
||||
_working_orders = new ObservableFilterCollection<OrderRecordImmutable>(_orders, o => o.is_active());
|
||||
_working_bids = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Buy);
|
||||
_working_asks = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Sell);
|
||||
_working_best_bid = new CellProperty<double>(this, nameof(working_best_bid), format_price, double.NaN);
|
||||
_working_best_ask = new CellProperty<double>(this, nameof(working_best_ask), format_price, double.NaN);
|
||||
_working_bids_text = new CellProperty<string>(this, nameof(working_bids_text), string.Empty);
|
||||
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
|
||||
//
|
||||
default_command = new DelegateCommand(_show_ladder);
|
||||
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
|
||||
@@ -94,45 +90,17 @@ namespace mbts.spark {
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
attach_prop_changed_cb(_bid_direct_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_direct_size_text))),
|
||||
attach_prop_changed_cb(_bid_implied_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_implied_size_text))),
|
||||
attach_prop_changed_cb(_bid_merged_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_merged_size_text))),
|
||||
attach_prop_changed_cb(_ask_direct_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_direct_size_text))),
|
||||
attach_prop_changed_cb(_ask_implied_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_implied_size_text))),
|
||||
attach_prop_changed_cb(_ask_merged_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_merged_size_text))),
|
||||
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
|
||||
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position))) {
|
||||
RaisePropertyChanged(nameof(raw_day_position_text));
|
||||
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
|
||||
raise_invalidated();
|
||||
}
|
||||
}),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
|
||||
);
|
||||
|
||||
uint convert_display_qty_uint(uint value) =>
|
||||
value.safe_divide(_display_qty_divisor);
|
||||
|
||||
string fmt_display_qty_int(int value) =>
|
||||
value.safe_divide(_display_qty_divisor).ToString();
|
||||
|
||||
string fmt_display_qty_uint(uint value) =>
|
||||
value.safe_divide(_display_qty_divisor).ToString();
|
||||
|
||||
string fmt_display_qty_ulong(ulong value) =>
|
||||
value.safe_divide(_display_qty_divisor).ToString();
|
||||
|
||||
PropertyChange<T> mk_echo_prop_changed_cb<T>(params string[] props) =>
|
||||
(oldval, newval) => RaisePropertyChanged(props);
|
||||
|
||||
IDisposable attach_prop_changed_cb<TView, TModel>(PropertyAutoWrapper<TModel, TView> prop, PropertyChange<(TModel, TView)> callback) {
|
||||
prop.changed += callback;
|
||||
return Disposable.Create(() => prop.changed -= callback);
|
||||
}
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
@@ -141,63 +109,32 @@ namespace mbts.spark {
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public string position_text => _position.text;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public string raw_day_position_text => SparkUtils.fmt_position(_raw_day_position.safe_divide(_display_qty_divisor));
|
||||
public double last_price => _last_price.Value;
|
||||
public bool last_price_from_spread => _last_price_from_spread.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public string last_size_text => _last_size.text;
|
||||
// bid price/size
|
||||
public double bid_price => _bid_price.Value;
|
||||
public uint bid_direct_size => _bid_direct_size.model;
|
||||
public uint bid_implied_size => _bid_merged_size.model - _bid_direct_size.model;
|
||||
public uint bid_merged_size => _bid_merged_size.model;
|
||||
// bid size text
|
||||
public string bid_direct_size_text => _bid_direct_size.view.ToString();
|
||||
public string bid_implied_size_text => _bid_implied_size.view.ToString();
|
||||
public string bid_merged_size_text => _bid_merged_size.view.ToString();
|
||||
// ask price/size
|
||||
public uint bid_direct_size => _bid_direct_size.Value;
|
||||
public uint bid_implied_size => _bid_merged_size.Value - _bid_direct_size.Value;
|
||||
public uint bid_merged_size => _bid_merged_size.Value;
|
||||
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size.Value, bid_implied_size));
|
||||
public double ask_price => _ask_price.Value;
|
||||
public uint ask_direct_size => _ask_direct_size.model;
|
||||
public uint ask_implied_size => _ask_implied_size.model;
|
||||
public uint ask_merged_size => _ask_merged_size.model;
|
||||
// ask size text
|
||||
public string ask_direct_size_text => _ask_direct_size.view.ToString();
|
||||
public string ask_implied_size_text => _ask_implied_size.view.ToString();
|
||||
public string ask_merged_size_text => _ask_merged_size.view.ToString();
|
||||
//
|
||||
public uint ask_direct_size => _ask_direct_size.Value;
|
||||
public uint ask_implied_size => _ask_merged_size.Value - _ask_direct_size.Value;
|
||||
public uint ask_merged_size => _ask_merged_size.Value;
|
||||
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size.Value, ask_implied_size));
|
||||
public ulong volume => _volume.value;
|
||||
public string volume_text => _volume.text;
|
||||
public double settlement => _settlement.Value;
|
||||
public double day_change => _day_change.Value;
|
||||
public double session_low => _session_low.Value;
|
||||
public double session_high => _session_high.Value;
|
||||
public int working_bid_volume => _working_bid_volume.value;
|
||||
public int working_ask_volume => _working_ask_volume.value;
|
||||
public string working_bids_text => OrderUtils.order_collection_summary(
|
||||
_order_summarizer.best_working_bid,
|
||||
_order_summarizer.best_working_bid_qty.safe_divide(_display_qty_divisor),
|
||||
_order_summarizer.working_bid_qty.safe_divide(_display_qty_divisor),
|
||||
security.ContractSpec);
|
||||
public string working_asks_text => OrderUtils.order_collection_summary(
|
||||
_order_summarizer.best_working_ask,
|
||||
_order_summarizer.best_working_ask_qty.safe_divide(_display_qty_divisor),
|
||||
_order_summarizer.working_ask_qty.safe_divide(_display_qty_divisor),
|
||||
security.ContractSpec);
|
||||
public string working_bids_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_bid, _order_summarizer.best_working_bid_qty, _order_summarizer.working_bid_qty, security.ContractSpec);
|
||||
public string working_asks_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_ask, _order_summarizer.best_working_ask_qty, _order_summarizer.working_ask_qty, security.ContractSpec);
|
||||
public double working_best_bid => _working_best_bid.value;
|
||||
public double working_best_ask => _working_best_ask.value;
|
||||
// ice props
|
||||
public string market_description => security.market_description();
|
||||
public string cleared_alias => security.cleared_alias();
|
||||
public string product_description => security.product_name();
|
||||
public string hub_alias => security.hub_alias();
|
||||
public string strip_name => security.strip_name();
|
||||
public string mic_code => security.mic_code();
|
||||
// animations
|
||||
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
// commands
|
||||
public ICommand bid_price_click { get; }
|
||||
public ICommand bid_merged_size_click { get; }
|
||||
public ICommand bid_direct_size_click { get; }
|
||||
@@ -209,30 +146,27 @@ namespace mbts.spark {
|
||||
public ICommand ask_implied_size_click { get; }
|
||||
public ICommand working_asks_click { get; }
|
||||
public ICommand default_command { get; }
|
||||
public string market_description {get;}
|
||||
public string cleared_alias { get; }
|
||||
public string product_description { get; }
|
||||
public string hub_alias { get; }
|
||||
public string strip_name{ get; }
|
||||
public string mic_code { get; }
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
#endregion
|
||||
|
||||
uint _display_qty_divisor => _display_qty_divisor_backing ??
|
||||
(_display_qty_divisor_backing = _orders_model.security_auxiliary_model.get_display_quantity_divisor(security)).Value;
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public void refresh() {
|
||||
bool dirty = false;
|
||||
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
_bid_price.set(b.price);
|
||||
// don't use the '||' operator - every 'set_new' call must be made
|
||||
// (the operation mustn't terminate the first time a set_new call returns true).
|
||||
dirty |= (
|
||||
_bid_direct_size.set_new(b.direct_size) |
|
||||
_bid_implied_size.set_new(b.implied_size) |
|
||||
_bid_merged_size.set_new(b.size) |
|
||||
_ask_direct_size.set_new(a.direct_size) |
|
||||
_ask_implied_size.set_new(a.implied_size) |
|
||||
_ask_merged_size.set_new(a.size));
|
||||
|
||||
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
_ask_price.set(a.price);
|
||||
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
|
||||
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
|
||||
}
|
||||
@@ -249,6 +183,16 @@ namespace mbts.spark {
|
||||
_volume.on_next_distinct(md.md.total_volume, ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
void refresh_size(ref NativePriceSize nps, Property<uint> direct, Property<uint> merged, ref string text, string text_prop) {
|
||||
if (nps.size != merged.Value || nps.direct_size != direct.Value) {
|
||||
merged.set(nps.size);
|
||||
direct.set(nps.direct_size);
|
||||
text = null;
|
||||
dirty = true;
|
||||
RaisePropertyChanged(text_prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(Row other) =>
|
||||
|
||||
@@ -9,7 +9,6 @@ using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
@@ -19,9 +18,6 @@ using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
|
||||
static IReadOnlyList<Func<MarketViewVM, IDataGridColumnFilter<Row>>> _filter_getters =
|
||||
ObjectSnapshot.property_delegates_of_type<MarketViewVM, IDataGridColumnFilter<Row>>();
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -41,21 +37,21 @@ namespace mbts.spark {
|
||||
const double working_orders_col_width = 105;
|
||||
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
|
||||
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume_text, nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size_text, nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position_text, nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => r.raw_day_position_text, nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size_text, nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size_text, nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size_text, nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size_text, nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
@@ -155,16 +151,14 @@ namespace mbts.spark {
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
market_description_filter.load_state(filters.market_description);
|
||||
cleared_alias_filter.load_state(filters.cleared_alias);
|
||||
product_description_filter.load_state(filters.product_description);
|
||||
hub_alias_filter.load_state(filters.hub_alias);
|
||||
strip_name_filter.load_state(filters.strip_name);
|
||||
mic_code_filter.load_state(filters.mic_code);
|
||||
}
|
||||
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
|
||||
}
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, _filters),
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
|
||||
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
@@ -239,15 +233,13 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.market_description, nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.cleared_alias, nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.product_description, nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.hub_alias, nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.strip_name, nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.mic_code, nameof(Row.mic_code));
|
||||
|
||||
IEnumerable<IDataGridColumnFilter<Row>> _filters =>
|
||||
_filter_getters.Select(fn => fn(this));
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
|
||||
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
|
||||
@@ -22,15 +22,12 @@ namespace bts.shell.time_and_sales {
|
||||
.to_safe();
|
||||
|
||||
readonly TradeMessageImmutable _trade;
|
||||
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
|
||||
uint? _display_size;
|
||||
|
||||
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
|
||||
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade) {
|
||||
this.row_key = row_key;
|
||||
this.security = security;
|
||||
_trade = trade;
|
||||
trade_key = trade.Key;
|
||||
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
@@ -42,8 +39,6 @@ namespace bts.shell.time_and_sales {
|
||||
public DateTime time => ((ulong)_trade.Time).lldatetime().ToLocalTime();
|
||||
public double price => _trade.price();
|
||||
public uint size => _trade.Size;
|
||||
public uint display_size => _display_size ??
|
||||
(_display_size = size.safe_divide(_get_display_qty_divisor(security))).Value;
|
||||
public AggressorSide aggressor_side => _trade.AggressorSide;
|
||||
|
||||
public void raise_invalidated() =>
|
||||
|
||||
@@ -40,7 +40,6 @@ namespace bts.shell.time_and_sales {
|
||||
_refresh_filter,
|
||||
_refresh_sort,
|
||||
_refresh_selected_rows;
|
||||
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
|
||||
IComparer<TimeAndSalesRowVM> _comparer_backing;
|
||||
Func<TimeAndSalesRowVM, bool> _filter_backing;
|
||||
ImmutableHashSet<TimeAndSalesRowVM> _selected_rows = ImmutableHashSet<TimeAndSalesRowVM>.Empty;
|
||||
@@ -59,9 +58,8 @@ namespace bts.shell.time_and_sales {
|
||||
_filter_change_count,
|
||||
_sort_change_count;
|
||||
|
||||
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
|
||||
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
_sort_manager = new ColumnSortManager(columns.WrapSelector(c => c as ISortableDIYGridColumn, null), TimeAndSalesView.Columns.RowKey.ToString());
|
||||
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
|
||||
_comparer_backing = _sort_manager.BuildComparer(_get_column_comparer);
|
||||
_log = new LoggerContext(Log.Ui, $"Time And Sales VM ({Interlocked.Increment(ref _id_counter)})", null);
|
||||
_settings_manager = settings_manager;
|
||||
@@ -97,7 +95,7 @@ namespace bts.shell.time_and_sales {
|
||||
System.Windows.HorizontalAlignment.Right);
|
||||
add_column(
|
||||
TimeAndSalesView.Columns.Size,
|
||||
r => r.display_size.ToString(),
|
||||
r => r.size.ToString(),
|
||||
DefaultColumnHeaders.Size,
|
||||
DefaultColumnWidths.Size,
|
||||
size_filter,
|
||||
@@ -306,7 +304,7 @@ namespace bts.shell.time_and_sales {
|
||||
}
|
||||
var all_rows_bd = _all_rows.ToBuilder();
|
||||
foreach (var t in trades) {
|
||||
var r = _row_factory.create(t.security, t.trade, _get_display_qty_divisor);
|
||||
var r = _row_factory.create(t.security, t.trade);
|
||||
all_rows_bd.Add(r);
|
||||
if (_filter_backing == null || _filter_backing(r))
|
||||
rows.InsertSorted(r, _comparer);
|
||||
@@ -402,11 +400,11 @@ namespace bts.shell.time_and_sales {
|
||||
// the row key must be unique to each row in the collection
|
||||
int _row_id_counter;
|
||||
|
||||
public TimeAndSalesRowVM create(TradeInfo trade_info, Func<SecurityImmutable?, uint> get_display_qty_divisor) =>
|
||||
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade, get_display_qty_divisor);
|
||||
public TimeAndSalesRowVM create(TradeInfo trade_info) =>
|
||||
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade);
|
||||
|
||||
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade, Func<SecurityImmutable?, uint> get_display_qty_divisor) =>
|
||||
new TimeAndSalesRowVM(_row_id_counter++, security, trade, get_display_qty_divisor);
|
||||
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade) =>
|
||||
new TimeAndSalesRowVM(_row_id_counter++, security, trade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ namespace bts.shell.time_and_sales {
|
||||
readonly TimeAndSalesWindowFactory _factory;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
readonly bool _can_pause_updates;
|
||||
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
|
||||
TimeAndSalesVM _vm;
|
||||
TimeAndSalesModel _model;
|
||||
SecurityCriteria _security_criteria_backing;
|
||||
@@ -36,21 +35,12 @@ namespace bts.shell.time_and_sales {
|
||||
SparkMenuDisplayVM _menu_display_vm;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public TimeAndSalesWindow(
|
||||
TimeAndSalesWindowFactory factory,
|
||||
IBus bus,
|
||||
MDConsumer consumer,
|
||||
TimeAndSalesSettings state,
|
||||
ImageSource icon,
|
||||
IWindowSettingsManager<GridSettings> settings_manager,
|
||||
bool can_pause_updates,
|
||||
Func<SecurityImmutable?, uint> get_display_qty_divisor)
|
||||
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, TimeAndSalesSettings state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager, bool can_pause_updates)
|
||||
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
|
||||
Log.assert(state != null);
|
||||
_consumer = consumer;
|
||||
_factory = factory;
|
||||
_state = state ?? TimeAndSalesSettings.Default(new SecurityCriteria());
|
||||
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
|
||||
if (_state.security_criteria == null)
|
||||
_state = _state.with(new SecurityCriteria());
|
||||
_security_criteria_backing = _state.security_criteria;
|
||||
@@ -96,7 +86,7 @@ namespace bts.shell.time_and_sales {
|
||||
_settings_manager.instance_settings = _state.grid;
|
||||
_menu_display_vm = new SparkMenuDisplayVM();
|
||||
_menu_display_vm.load_from(_state.menu_display);
|
||||
_vm = new TimeAndSalesVM(_settings_manager, _get_display_qty_divisor);
|
||||
_vm = new TimeAndSalesVM(_settings_manager);
|
||||
var selected_rows_src = Observable.Create<ImmutableHashSet<TimeAndSalesRowVM>>(o => {
|
||||
_vm.selected_rows_changed += o.OnNext;
|
||||
return Disposable.Create(() => _vm.selected_rows_changed -= o.OnNext);
|
||||
|
||||
@@ -20,15 +20,12 @@ namespace bts.shell {
|
||||
readonly MDConsumer _consumer;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly TimeAndSalesConfig _time_and_sales_conf;
|
||||
readonly ISecurityAuxiliaryDataModel _security_auxiliary_model;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
|
||||
|
||||
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, ISecurityAuxiliaryDataModel security_auxiliary_model)
|
||||
: base(module, "TimeAndSales") {
|
||||
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
|
||||
_bus = bus;
|
||||
_consumer = consumer;
|
||||
_settings_hub = settings_hub;
|
||||
_security_auxiliary_model = security_auxiliary_model;
|
||||
_time_and_sales_conf = new TimeAndSalesConfig(Configuration.get<bts.config.TimeAndSalesOptions>());
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
@@ -41,8 +38,7 @@ namespace bts.shell {
|
||||
public override Task<IShellWindow> load(TimeAndSalesSettings state) =>
|
||||
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
|
||||
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g)),
|
||||
_time_and_sales_conf.can_pause_updates,
|
||||
_security_auxiliary_model.get_display_quantity_divisor));
|
||||
_time_and_sales_conf.can_pause_updates));
|
||||
|
||||
public async void show(SecurityCriteria criteria) {
|
||||
var w = await load(new TimeAndSalesSettings(criteria));
|
||||
|
||||
@@ -236,20 +236,12 @@ namespace mbts.spark {
|
||||
get => algo_root;
|
||||
set => algo_root = value;
|
||||
}
|
||||
IAlgoVM IAlgoParamVM<IAlgoVM>.display_value {
|
||||
get => algo_root;
|
||||
set => algo_root = value;
|
||||
}
|
||||
bool IAlgoParamVM.has_value => _algo_root != null;
|
||||
bool IAlgoParamVM.is_enabled { get; set; } = true;
|
||||
object IAlgoParamVM.value {
|
||||
get => algo_root;
|
||||
set => algo_root = (IAlgoVM)value;
|
||||
}
|
||||
object IAlgoParamVM.display_value {
|
||||
get => algo_root;
|
||||
set => algo_root = (IAlgoVM)value;
|
||||
}
|
||||
AlgoParamPermissions _permissions = AlgoParamPermissions.ReadWrite;
|
||||
AlgoParamPermissions IAlgoParamVM.permissions { get => _permissions; set => _permissions = value; }
|
||||
|
||||
|
||||
+4
-40
@@ -199,30 +199,18 @@
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
Text="{Binding DataContext.bbo.bid_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
|
||||
ToolTip="Best Bid Size"
|
||||
>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
|
||||
<Binding Path="DataContext.bbo.bid_size" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
/>
|
||||
<TextBlock
|
||||
x:Name="ask_text"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="4,0"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
Text="{Binding DataContext.bbo.ask_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
|
||||
ToolTip="Best Ask Size"
|
||||
>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
|
||||
<Binding Path="DataContext.bbo.ask_size" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
/>
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
|
||||
@@ -284,14 +272,6 @@
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--TODO: replace all the param.value bindings with param.display_value.
|
||||
The reason this has not already been done is that the display_value property is new,
|
||||
and only necessary for the size param as of 6/8/2020 (slightly more uniform code doesn't warrant rocking the boat).-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.display_value}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
@@ -380,22 +360,6 @@
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--TODO: replace all the param.value bindings with param.display_value.
|
||||
The reason this has not already been done is that the display_value property is new,
|
||||
and only necessary for the size param as of 6/8/2020 (slightly more uniform code doesn't warrant rocking the boat).-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownNullableInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
|
||||
<c:TickUpDownNullableInt.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</c:TickUpDownNullableInt.CommandBindings>
|
||||
<c:TickUpDownNullableInt.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</c:TickUpDownNullableInt.InputBindings>
|
||||
</c:TickUpDownNullableInt>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace mbts.spark {
|
||||
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values.Ordered()), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
|
||||
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), dv_filter(EnumHelper.get_values<Venue>().ToArray(), r => r.security.exchange_or_default()), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
|
||||
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), dv_filter(EnumHelper.get_values<TimeInForce>().ToArray(), r => r.order?.time_in_force() ?? TimeInForce.Undefined), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
|
||||
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.display_size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
|
||||
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
|
||||
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), new DoubleMinMax<FillTuple>(r => r.fill.LastPrice), DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
|
||||
add_col(Columns.FillKind, DefaultColumnHeaders.FillKind, r => r.fill_kind.ToString(), dv_filter(EnumHelper.get_values<FillKind>().ToArray(), r => r.fill.FillKind), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
|
||||
add_col(Columns.FillStatus, DefaultColumnHeaders.FillStatus, r => r.fill_status.ToString(), dv_filter(EnumHelper.get_values<FillStatus>().ToArray(), r => r.fill.FillStatus), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
|
||||
@@ -286,7 +286,6 @@ namespace mbts.spark {
|
||||
Color? _root_algo_badge_color;
|
||||
OrderMetadata _order_metadata;
|
||||
bool _is_selected;
|
||||
double? _display_size;
|
||||
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) :
|
||||
base(fill_tuple.security ?? SecurityDataHelper.Defaults.SecurityImmutable) {
|
||||
@@ -307,8 +306,6 @@ namespace mbts.spark {
|
||||
public Side side => _fill.Side;
|
||||
public double price => _fill.LastPrice;
|
||||
public double size => _fill.LastShares;
|
||||
public double display_size => _display_size ??
|
||||
(_display_size = size.safe_divide(_model.security_auxiliary_model.get_display_quantity_divisor(security))).Value;
|
||||
public FillKind fill_kind => _fill.FillKind;
|
||||
public FillStatus fill_status => _fill.FillStatus;
|
||||
public string fill_key => _fill.FillKey;
|
||||
|
||||
@@ -87,15 +87,6 @@ namespace mbts.spark {
|
||||
public static bool operator !=(LadderColumnSettings x, LadderColumnSettings y) => !(x == y);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class LadderOrderLimits : IEquatable<LadderOrderLimits> {
|
||||
[ProtoMember(1)]
|
||||
public ToggleInt32Proto max_settlement_cross_ticks { get; set; }
|
||||
|
||||
public bool Equals(LadderOrderLimits other) =>
|
||||
other != null && max_settlement_cross_ticks == other.max_settlement_cross_ticks;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class HistogramBarAnimation : IEquatable<HistogramBarAnimation> {
|
||||
// this class is 100% mutable (there are literally 0 readonly members),
|
||||
@@ -323,8 +314,6 @@ namespace mbts.spark {
|
||||
// Settings are liable to change in the near future, so an internal IEqualityComparer simply won't do as it risks becoming out of date.
|
||||
[ProtoContract]
|
||||
class LadderSettings : IEquatable<LadderSettings> {
|
||||
// RESERVED ProtoMember indices
|
||||
// 900-999: dj settings
|
||||
const double _default_order_entry_panel_width = 110.0;
|
||||
const double _default_zoom_factor = 1.0;
|
||||
const bool _default_show_warn_on_cross = true;
|
||||
@@ -603,9 +592,6 @@ namespace mbts.spark {
|
||||
[ProtoMember(61), DefaultValue(true)]
|
||||
public bool status_message_visible { get; set; }
|
||||
|
||||
[ProtoMember(900)]
|
||||
public LadderOrderLimits ladder_order_limits { get; set; }
|
||||
|
||||
public void sanitize() =>
|
||||
sanitize(this);
|
||||
|
||||
@@ -711,8 +697,7 @@ namespace mbts.spark {
|
||||
a.enforce_max_clip == b.enforce_max_clip &&
|
||||
a.max_clip == b.max_clip &&
|
||||
a.price_display_format == b.price_display_format &&
|
||||
a.status_message_visible == b.status_message_visible &&
|
||||
a.ladder_order_limits.safe_equals(b.ladder_order_limits);
|
||||
a.status_message_visible == b.status_message_visible;
|
||||
|
||||
public bool Equals(LadderSettings other) =>
|
||||
profile_equals(this, other) &&
|
||||
|
||||
@@ -4,7 +4,6 @@ using bts.shell.orderbook;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@@ -170,8 +169,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
private void _set_badge_text() {
|
||||
var fmt = MDDisplayHelper.get_value_formatter(_owner.display_quantity_divisor);
|
||||
text_lines = BadgeRender.format(side, quantity, filled, _current_badge_style, fmt);
|
||||
text_lines = BadgeRender.format(side, quantity, filled, _current_badge_style);
|
||||
RaisePropertyChanged(nameof(text_lines));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.persist.algo;
|
||||
using bts.proto;
|
||||
using bts.shell.orderbook;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
@@ -45,8 +44,7 @@ namespace mbts.spark {
|
||||
LadderSettings.NetChangeTerms _price_format;
|
||||
LadderSettings.PriceDisplayFormat _price_display_format;
|
||||
double _order_entry_panel_width, _zoom_factor;
|
||||
bool _auto_clear, _show_warn_on_cross, _enforce_max_clip, _enforce_max_settlement_cross_ticks;
|
||||
int _max_settlement_cross_ticks;
|
||||
bool _auto_clear, _show_warn_on_cross, _enforce_max_clip;
|
||||
LadderSettings _current;
|
||||
LadderColumnSettingsVM
|
||||
_orders = new OrdersColumnSettingsVM(),
|
||||
@@ -192,8 +190,6 @@ namespace mbts.spark {
|
||||
public bool show_warn_on_cross { get => _show_warn_on_cross; set => Set(ref _show_warn_on_cross, value); }
|
||||
public bool enforce_max_clip { get => _enforce_max_clip; set => Set(ref _enforce_max_clip, value); }
|
||||
public uint max_clip { get => _max_clip.Value; set => _max_clip.Value = value; }
|
||||
public bool enforce_max_settlement_cross_ticks { get => _enforce_max_settlement_cross_ticks; set => Set(ref _enforce_max_settlement_cross_ticks, value); }
|
||||
public int max_settlement_cross_ticks { get => _max_settlement_cross_ticks; set => Set(ref _max_settlement_cross_ticks, value); }
|
||||
|
||||
// favorites
|
||||
public FavoriteAlgosVM favorite_algos_vm { get; }
|
||||
@@ -374,10 +370,6 @@ namespace mbts.spark {
|
||||
show_warn_on_cross = settings.show_warn_on_cross;
|
||||
enforce_max_clip = settings.enforce_max_clip;
|
||||
max_clip = settings.max_clip;
|
||||
if (settings.ladder_order_limits != null) {
|
||||
enforce_max_settlement_cross_ticks = settings.ladder_order_limits.max_settlement_cross_ticks.is_enabled;
|
||||
max_settlement_cross_ticks = settings.ladder_order_limits.max_settlement_cross_ticks.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,10 +448,6 @@ namespace mbts.spark {
|
||||
clone.show_warn_on_cross = show_warn_on_cross;
|
||||
clone.enforce_max_clip = enforce_max_clip;
|
||||
clone.max_clip = max_clip;
|
||||
if (clone.ladder_order_limits == null)
|
||||
clone.ladder_order_limits = new LadderOrderLimits();
|
||||
clone.ladder_order_limits.max_settlement_cross_ticks =
|
||||
new ToggleInt32Proto(max_settlement_cross_ticks, enforce_max_settlement_cross_ticks);
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.proto;
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
@@ -8,7 +8,6 @@ using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -67,8 +66,6 @@ namespace mbts.spark {
|
||||
current_settings = init;
|
||||
this.settings = settings;
|
||||
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
|
||||
display_quantity_divisor = model.order_gateway.security_auxiliary_model.get_display_quantity_divisor(model.security);
|
||||
size_calculator.display_divisor = display_quantity_divisor;
|
||||
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
|
||||
this.order_properties_vm = order_properties_vm;
|
||||
_high = backing(nameof(high), double.NaN);
|
||||
@@ -295,7 +292,6 @@ namespace mbts.spark {
|
||||
remove => _on_disposed_impl -= value;
|
||||
}
|
||||
public SecurityImmutable security => _model.security;
|
||||
public uint display_quantity_divisor { get; }
|
||||
public LadderSettings current_settings { get; private set; }
|
||||
public double high => _high.Value;
|
||||
public double low => _low.Value;
|
||||
@@ -373,14 +369,14 @@ namespace mbts.spark {
|
||||
get {
|
||||
string rv = null;
|
||||
if (show_day_position)
|
||||
rv = SparkUtils.fmt_position(day_position / display_quantity_divisor);
|
||||
rv = SparkUtils.fmt_position(day_position);
|
||||
if (show_raw_day_position) {
|
||||
var raw_day_text = SparkUtils.fmt_position(raw_day_position / display_quantity_divisor);
|
||||
var raw_day_text = SparkUtils.fmt_position(raw_day_position);
|
||||
rv = rv == null ? raw_day_text : $"{rv} / {raw_day_text}";
|
||||
}
|
||||
if (show_account_position) {
|
||||
var acc_text = SparkUtils.fmt_position(position / display_quantity_divisor);
|
||||
rv = rv == null ? acc_text : $"{rv} ({acc_text})";
|
||||
var acc_text = SparkUtils.fmt_position(position);
|
||||
rv = rv == null ? SparkUtils.fmt_position(position) : $"{rv} ({acc_text})";
|
||||
}
|
||||
return rv ?? "";
|
||||
}
|
||||
@@ -490,10 +486,6 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public async Task send_order_async(Side side, uint size, double price) {
|
||||
if (!_validate_price(side, price, out var invalid_price_msg)) {
|
||||
status_update(new Activity { security = security, message = invalid_price_msg, category = ActivityLogCategory.order });
|
||||
return;
|
||||
}
|
||||
var order = order_properties_vm.create(side);
|
||||
if (order.CustomDef != null) {
|
||||
if (order.CustomDef.Stop != null) {
|
||||
@@ -525,23 +517,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
bool _validate_price(Side side, double price, out string msg) {
|
||||
msg = null;
|
||||
if (!price.is_real()) {
|
||||
msg = "not a real number";
|
||||
} else if (settlement.is_real()) {
|
||||
var ticks_from_settle = security.ContractSpec.tickdiff(price, settlement);
|
||||
if (ticks_from_settle.HasValue) {
|
||||
var max_cross = (current_settings?.ladder_order_limits?.max_settlement_cross_ticks)?.as_nullable();
|
||||
if (max_cross.HasValue) {
|
||||
if (side == Side.Sell ? ticks_from_settle < (-1 * max_cross.Value) : ticks_from_settle > max_cross.Value)
|
||||
msg = $"exceeds settle cross ticks limit ({ticks_from_settle.Value})";
|
||||
}
|
||||
}
|
||||
}
|
||||
return msg == null;
|
||||
}
|
||||
|
||||
void _update_header() {
|
||||
bool last_updated = md.get_last_trade(ref _last_trade_time, out NativePriceSize last, out DateTime time, out ulong volume);
|
||||
if (_misc_time != _model.md.misc_timestamp || last_updated) {
|
||||
|
||||
@@ -46,12 +46,12 @@ namespace mbts.spark {
|
||||
void _reset_ladder_vm() {
|
||||
if (_value == null && !_security_md.HasValue)
|
||||
return;
|
||||
_value?.Dispose();
|
||||
_value = _security_md.HasValue ?
|
||||
LadderVM.create_readonly(_bus,
|
||||
new LadderModel(security_md.Value, _model, _model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
|
||||
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null;
|
||||
Disposable.replace(ref _value,
|
||||
_security_md.HasValue ?
|
||||
LadderVM.create_readonly(_bus,
|
||||
new LadderModel(security_md.Value, _model, _model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
|
||||
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null);
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +66,7 @@ namespace mbts.spark {
|
||||
double _last_origin_price;
|
||||
MarketBreakLines _mkt_break_lines;
|
||||
NativePriceSize _last_trade;
|
||||
Func<NativePriceSize, string> _fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
|
||||
Func<uint, string> _fmt_mkt_quantity_impl = v => v.ToString();
|
||||
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
|
||||
int _old_tooltip_row = int.MaxValue;
|
||||
NativePriceSize? _old_last_trade;
|
||||
string _direction_arrow = string.Empty;
|
||||
@@ -469,7 +468,7 @@ namespace mbts.spark {
|
||||
security_id: _vm.model.security.Id,
|
||||
price_level: price_for_point(point)
|
||||
);
|
||||
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query, fmt_mbo_qty);
|
||||
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query);
|
||||
// Load the first set of orders so we know if there are any before showing the popup
|
||||
var result = await model.load_orders();
|
||||
if (result.success && result.value.orders.Count > 0) {
|
||||
@@ -490,8 +489,6 @@ namespace mbts.spark {
|
||||
};
|
||||
view.RequestClose += (o, _) => popup.IsOpen = false;
|
||||
}
|
||||
string fmt_mbo_qty(int value) =>
|
||||
value < 0 ? $"-{_fmt_mkt_quantity((uint)value)}" : _fmt_mkt_quantity((uint)value);
|
||||
}
|
||||
|
||||
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
|
||||
@@ -659,7 +656,7 @@ namespace mbts.spark {
|
||||
}
|
||||
void render_market_col(NativePriceSize[] depth) {
|
||||
var mid = LadderRender.index_of(_price_list, _last_origin_price, _price_comparer);
|
||||
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, security.ContractSpec, column.settings, _fmt_mkt_depth_item);
|
||||
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, security.ContractSpec, column.settings, _fmt_size);
|
||||
}
|
||||
}
|
||||
_apply_column_visual_offset();
|
||||
@@ -737,7 +734,7 @@ namespace mbts.spark {
|
||||
_hide_tool_tip();
|
||||
}
|
||||
|
||||
tooltip_view.Content = _fmt_mkt_quantity(volume);
|
||||
tooltip_view.Content = $"{volume}";
|
||||
tooltip_view.IsOpen = true;
|
||||
this.ToolTip = tooltip_view;
|
||||
} else {
|
||||
@@ -853,7 +850,7 @@ namespace mbts.spark {
|
||||
if (last_trade_changed) {
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
if (last_index > 0)
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column);
|
||||
}
|
||||
if (depth_changed)
|
||||
_refresh_sizes();
|
||||
@@ -895,21 +892,21 @@ namespace mbts.spark {
|
||||
void _refresh_last_trade() {
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
if (last_index > 0)
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column);
|
||||
}
|
||||
|
||||
void _refresh_piq() {
|
||||
if (!_settings.piq_column.show)
|
||||
return;
|
||||
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
|
||||
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, security.ContractSpec, _settings.piq_column, _fmt_mkt_quantity);
|
||||
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, security.ContractSpec, _settings.piq_column);
|
||||
}
|
||||
|
||||
void _refresh_sizes() {
|
||||
Log.assert_d(_price_list != null);
|
||||
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
|
||||
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, security.ContractSpec, _settings.bid_column, _fmt_mkt_depth_item);
|
||||
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, security.ContractSpec, _settings.ask_column, _fmt_mkt_depth_item);
|
||||
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, security.ContractSpec, _settings.bid_column, _fmt_size);
|
||||
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, security.ContractSpec, _settings.ask_column, _fmt_size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -931,17 +928,7 @@ namespace mbts.spark {
|
||||
if (vapls != null) {
|
||||
Log.assert_d(_price_list != null);
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
LadderRender.render_volumes(
|
||||
_volume_vis_tup.visual,
|
||||
_volume_vis_tup.glyph_context,
|
||||
_price_list,
|
||||
vapls,
|
||||
_settings.row_height,
|
||||
security.ContractSpec,
|
||||
_settings.volume_column,
|
||||
last_index,
|
||||
_get_last_arrow_string(),
|
||||
_fmt_mkt_quantity);
|
||||
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _get_last_arrow_string());
|
||||
_old_last_trade = _last_trade;
|
||||
}
|
||||
|
||||
@@ -964,7 +951,7 @@ namespace mbts.spark {
|
||||
}
|
||||
// regardless of if we've arrived here via a new Last (and new direction arrow)
|
||||
// or we're reusing the old one, this format bit is the same
|
||||
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size)} ";
|
||||
return $"{_direction_arrow} {_last_trade.size} ";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1069,7 +1056,7 @@ namespace mbts.spark {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return double.IsNaN(best_bid) ?
|
||||
double.IsNaN(best_ask) ? _vm.settlement : security.ContractSpec.tickdown(best_ask) :
|
||||
double.IsNaN(best_ask) ? _vm.low : security.ContractSpec.tickdown(best_ask) :
|
||||
best_bid;
|
||||
}
|
||||
|
||||
@@ -1077,7 +1064,7 @@ namespace mbts.spark {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return double.IsNaN(best_ask) ?
|
||||
double.IsNaN(best_bid) ? _vm.settlement : best_bid :
|
||||
double.IsNaN(best_bid) ? security.ContractSpec.tickdown(_vm.high) : best_bid :
|
||||
security.ContractSpec.tickdown(best_ask);
|
||||
}
|
||||
|
||||
@@ -1085,7 +1072,7 @@ namespace mbts.spark {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return double.IsNaN(best_bid) ?
|
||||
double.IsNaN(best_ask) ? _vm.settlement : security.ContractSpec.tickdown(best_ask) :
|
||||
double.IsNaN(best_ask) ? 0.5 * (_vm.low + _vm.high) : security.ContractSpec.tickdown(best_ask) :
|
||||
double.IsNaN(best_ask) ? best_bid : TickUtils.mid_tick(security.ContractSpec, best_bid, best_ask);
|
||||
}
|
||||
|
||||
@@ -1122,9 +1109,7 @@ namespace mbts.spark {
|
||||
GlyphContext glyph_context(LadderColumnSettings cs) =>
|
||||
GlyphContext.create(cs.font_family, cs.font_weight);
|
||||
|
||||
var mkt_qty_display_divisor = _vm?.display_quantity_divisor ?? 1;
|
||||
_fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(_settings.size_display, mkt_qty_display_divisor);
|
||||
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(mkt_qty_display_divisor);
|
||||
_fmt_size = MDDisplayHelper.get_formatter_nps(_settings.size_display);
|
||||
_set_columns();
|
||||
_apply_column_visual_offset();
|
||||
if (prev_row_height != _settings.row_height)
|
||||
@@ -1173,22 +1158,6 @@ namespace mbts.spark {
|
||||
}
|
||||
visible_columns_changed?.Invoke();
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
|
||||
string _fmt_mkt_depth_item(NativePriceSize value) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_mkt_depth_item_impl;
|
||||
return impl is null ? MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default())?.ToString() ?? "Error" : impl(value);
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
|
||||
string _fmt_mkt_quantity(uint value) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_mkt_quantity_impl;
|
||||
return impl is null ? value.ToString() : impl(value);
|
||||
}
|
||||
|
||||
void _init_tactile_adorner() {
|
||||
Disposable.dispose(ref _tactile_adorner);
|
||||
|
||||
@@ -12,24 +12,20 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using bts.utils;
|
||||
|
||||
namespace mbts.spark {
|
||||
static class BadgeRender {
|
||||
// IMPORTANT: the return values for this method MUST BE IN SYNC with the "format" method, else "measure_height" and "LadderRender.measure_row_height" will be broken.
|
||||
static int _line_count_for_style(LadderSettings.BadgeStyle badge_style) => badge_style == LadderSettings.BadgeStyle.Alternate ? 2 : 1;
|
||||
// IMPORTANT: at the risk of being redundant, see above comment.
|
||||
public static IReadOnlyList<string> format(Side side, uint quantity, uint filled, LadderSettings.BadgeStyle style, Func<uint, string> fmt_value = null) {
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<uint>();
|
||||
public static IReadOnlyList<string> format(Side side, uint quantity, uint filled, LadderSettings.BadgeStyle style) {
|
||||
switch (style) {
|
||||
case LadderSettings.BadgeStyle.Alternate:
|
||||
var working_qty = quantity > filled ? quantity - filled : 0;
|
||||
return new[] {
|
||||
$"{side.abbreviate()}:{fmt_value(filled)}",
|
||||
$"W:{fmt_value(working_qty)}"
|
||||
$"{side.abbreviate()}:{filled}",
|
||||
$"W:{quantity - filled}"
|
||||
};
|
||||
default: return new[] { $"{fmt_value(filled)}/{fmt_value(quantity)}" };
|
||||
default: return new[] { $"{filled}/{quantity}" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,17 +193,16 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public static void render_volumes(
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyph_context,
|
||||
double[] prices,
|
||||
IEnumerable<VolumeAtPriceLevelImmutable> data,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
string last_size_and_direction,
|
||||
Func<uint, string> fmt_value) {
|
||||
//
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyph_context,
|
||||
double[] prices,
|
||||
IEnumerable<VolumeAtPriceLevelImmutable> data,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
string last_size_and_direction
|
||||
) {
|
||||
if (!data.Any()) {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
@@ -229,8 +224,6 @@ namespace mbts.spark {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
}
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<uint>();
|
||||
// inflate the max so that the graph never fills the available space
|
||||
double max = data.Max(v => v.Volume) * 1.1;
|
||||
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
|
||||
@@ -262,11 +255,11 @@ namespace mbts.spark {
|
||||
}
|
||||
x_bar += width;
|
||||
if (show_numbers)
|
||||
numbers_text.Add(fmt_value(v));
|
||||
numbers_text.Add(v.ToString());
|
||||
}
|
||||
|
||||
if (settings.total.show_numbers) {
|
||||
numbers_text.Add(fmt_value(vap.Volume));
|
||||
numbers_text.Add(vap.Volume.ToString());
|
||||
}
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
numbers_text.Clear();
|
||||
@@ -316,33 +309,19 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
internal static void render_last(DrawingVisual dv, GlyphContext glyph_context, NativePriceSize last, int index, double row_height, LadderColumnSettings settings, Func<uint, string> fmt_value) {
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<uint>();
|
||||
internal static void render_last(DrawingVisual dv, GlyphContext glyph_context, NativePriceSize last, int index, double row_height, LadderColumnSettings settings) {
|
||||
using (var dc = dv.RenderOpen()) {
|
||||
var gr = format_text(fmt_value(last.size), index, row_height, settings, glyph_context);
|
||||
var gr = format_text(last.size.ToString(), index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(settings.foreground.rgb_brush(), gr);
|
||||
}
|
||||
}
|
||||
|
||||
// this is basically a copy of redner_sizes up above
|
||||
internal static void render_piqs(
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyph_context,
|
||||
int origin_index,
|
||||
double[] prices,
|
||||
IEnumerable<(double price, uint piq)> pvp,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<uint, string> fmt_value) {
|
||||
//
|
||||
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, IEnumerable<(double price, uint piq)> pvp, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
|
||||
using (var dc = dv.RenderOpen()) {
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
|
||||
if (!prices.within_range(origin_index) || pvp.is_null_or_empty())
|
||||
return;
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<uint>();
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
int curr_index = origin_index;
|
||||
double curr_price = prices[curr_index];
|
||||
@@ -353,7 +332,7 @@ namespace mbts.spark {
|
||||
curr_index += tick_diff;
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Length) {
|
||||
var gr = format_text(fmt_value(entry.piq), curr_index, row_height, settings, glyph_context);
|
||||
var gr = format_text(entry.piq.ToString(), curr_index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(foreground, gr);
|
||||
} else break;
|
||||
}
|
||||
@@ -392,4 +371,4 @@ namespace mbts.spark {
|
||||
using (dv.RenderOpen()) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1019,47 +1019,45 @@
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- market cross -->
|
||||
<CheckBox Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding show_warn_on_cross}">
|
||||
<TextBlock
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding show_warn_on_cross}"
|
||||
Text="Warn on Market Cross"
|
||||
/>
|
||||
<CheckBox Name="warn" Grid.Column="0" FlowDirection="RightToLeft" IsChecked="{Binding show_warn_on_cross}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight"/>
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
<TextBlock Grid.Column="1"
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding ElementName=warn, Path=IsChecked}"
|
||||
Text="Warn on Market Cross" />
|
||||
<!-- max order qty -->
|
||||
<CheckBox Grid.Row="2" Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding enforce_max_clip}">
|
||||
<TextBlock
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding enforce_max_clip}"
|
||||
Text="Max Order Quantity"
|
||||
/>
|
||||
<CheckBox Name="enforce_max_clip" Grid.Row="1" Grid.Column="0" FlowDirection="RightToLeft" IsChecked="{Binding enforce_max_clip}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight"/>
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
<c:TickUpDownUInt
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
IsEnabled="{Binding enforce_max_clip}"
|
||||
Value="{Binding max_clip}"
|
||||
Width="60"
|
||||
/>
|
||||
<CheckBox Grid.Row="4" VerticalContentAlignment="Center" IsChecked="{Binding enforce_max_settlement_cross_ticks}">
|
||||
<TextBlock
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding enforce_max_settlement_cross_ticks}"
|
||||
Text="Max Ticks from Settlement"
|
||||
/>
|
||||
</CheckBox>
|
||||
<c:TickUpDownInt Grid.Row="4" Grid.Column="2" IsEnabled="{Binding enforce_max_settlement_cross_ticks}" Value="{Binding max_settlement_cross_ticks}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1"
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding ElementName=enforce_max_clip, Path=IsChecked}"
|
||||
MinWidth="{Binding ElementName=warn, Path=ActualWidth}"
|
||||
Text="Max Order Quantity"/>
|
||||
<c:TickUpDownUInt Grid.Row="1" Grid.Column="2"
|
||||
IsEnabled="{Binding ElementName=enforce_max_clip, Path=IsChecked}"
|
||||
Value="{Binding max_clip}" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Mini Order Book">
|
||||
|
||||
@@ -426,26 +426,9 @@
|
||||
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Grid.Row="5"
|
||||
FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<converters:MultiValueConverterPipe>
|
||||
<x:Static MemberType="{x:Type converters:DynamicDoubleDivisorConverter}" Member="Instance"/>
|
||||
<x:Static MemberType="{x:Type converters:ValueConverters}" Member="SimpleString"/>
|
||||
</converters:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="volume" Mode="OneWay"/>
|
||||
<Binding Path="display_quantity_divisor" Mode="OneTime"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="2" Grid.Row="5" Text="{Binding volume, Mode=OneWay}" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</Grid>
|
||||
<!-- Account combobox -->
|
||||
<ComboBox DataContext="{Binding order_properties_vm}" Margin="2,10,2,2"
|
||||
@@ -524,7 +507,7 @@
|
||||
mbts:TextBoxBehavior.IncrementDecrementOnMouseClick="True"
|
||||
HorizontalContentAlignment="Right"
|
||||
Margin="2"
|
||||
Text="{Binding size_calculator.display_value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Text="{Binding size_calculator.value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ContextMenu="{x:Null}"
|
||||
/>
|
||||
<!-- Qty quick buttons -->
|
||||
@@ -539,14 +522,7 @@
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Margin="2" Command="{Binding Path=DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}" Content="{Binding}" Focusable="False">
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:DynamicDoubleMultiplierConverter.Instance}">
|
||||
<Binding/>
|
||||
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=spark:LadderView}"/>
|
||||
</MultiBinding>
|
||||
</Button.CommandParameter>
|
||||
</Button>
|
||||
<Button Margin="2" Command="{Binding Path=DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Content="{Binding}" Focusable="False"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
@@ -3,7 +3,6 @@ using bts.api.position;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -26,27 +25,24 @@ namespace bts.shell.manual_fills {
|
||||
readonly Property<bool> _is_dirty;
|
||||
readonly Property<EditableItemState> _editable_item_state;
|
||||
readonly DelegateProperty<bool> _is_valid;
|
||||
readonly DisplayQuantityMultiplier _size = new DisplayQuantityMultiplier();
|
||||
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
|
||||
SecurityImmutable? _security;
|
||||
AccountTuple? _account;
|
||||
UserTuple? _user;
|
||||
Side _side;
|
||||
uint _size;
|
||||
double _price;
|
||||
bool _is_selected;
|
||||
ManualFillData? _data;
|
||||
IDisposable _committed_snapshot;
|
||||
|
||||
public ManualFillRow(ManualFillData data, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
|
||||
public ManualFillRow(ManualFillData data) {
|
||||
_data = data;
|
||||
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
|
||||
key = data.key;
|
||||
_security = data.value.security;
|
||||
_account = data.value.account;
|
||||
_user = data.value.user;
|
||||
_side = data.value.manual_fill.Side;
|
||||
_size.actual_value = data.value.manual_fill.Quantity;
|
||||
_size.conversion_data = new QuantityConversionData(_get_display_qty_divisor(data.value.security), QuantityConversionData.OpType.DivideActual);
|
||||
_size = data.value.manual_fill.Quantity;
|
||||
_price = data.value.manual_fill.Price;
|
||||
if (placeholder_key.HasValue) {
|
||||
_is_dirty = backing(nameof(is_dirty), false);
|
||||
@@ -57,7 +53,6 @@ namespace bts.shell.manual_fills {
|
||||
_editable_item_state = backing(nameof(editable_item_state), _is_dirty, ch => ch.newval ? EditableItemState.Modified : EditableItemState.None);
|
||||
}
|
||||
_is_valid = backing(nameof(is_valid), () => _validate(this));
|
||||
_dispose = _size.connect_owner(this, nameof(size), nameof(display_size));
|
||||
}
|
||||
|
||||
public event Action<bool> is_selected_changed;
|
||||
@@ -70,10 +65,8 @@ namespace bts.shell.manual_fills {
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
set {
|
||||
if (_set(ref _security, value)) {
|
||||
_size.conversion_data = new QuantityConversionData(_get_display_qty_divisor(value), QuantityConversionData.OpType.DivideActual);
|
||||
if (_set(ref _security, value))
|
||||
RaisePropertyChanged(nameof(spec));
|
||||
}
|
||||
}
|
||||
}
|
||||
public ContractSpecificationImmutable? spec => security.spec_or_default();
|
||||
@@ -82,8 +75,7 @@ namespace bts.shell.manual_fills {
|
||||
public UserTuple? user { get => _user; set => _set(ref _user, value); }
|
||||
// side/size/price
|
||||
public Side side { get => _side; set => _set(ref _side, value); }
|
||||
public uint size { get => _size.actual_value; set => _size.actual_value = value; }
|
||||
public uint display_size { get => _size.display_value; set => _size.display_value = value; }
|
||||
public uint size { get => _size; set => _set(ref _size, value); }
|
||||
public double price { get => _price; set => _set(ref _price, value); }
|
||||
// is selected
|
||||
public bool is_selected {
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Accounts}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Users}}" SelectedItem="{Binding user}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Sides}}" SelectedItem="{Binding side}" Width="48"/>
|
||||
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding display_size}"/>
|
||||
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
|
||||
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
|
||||
</Grid>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="0,6,0,0">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
@@ -104,8 +103,5 @@ namespace bts.shell.manual_fills {
|
||||
|
||||
public async Task<bool> update_fills(IEnumerable<ManualFill> fills) =>
|
||||
await _model.update_manual_fills(fills);
|
||||
|
||||
public uint get_display_qty_divisor(SecurityImmutable? security) =>
|
||||
_model.security_auxiliary_model.get_display_quantity_divisor(security);
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ namespace bts.shell.manual_fills {
|
||||
RXCollection.connect(_persisted_rows,
|
||||
_model.manual_fills.OnDispatcher(),
|
||||
Observable.Return(Comparer<ManualFillData>.Create((a, b) => a.key.CompareTo(b.key))),
|
||||
mfd => new ManualFillRow(mfd, model.get_display_qty_divisor),
|
||||
mfd => new ManualFillRow(mfd),
|
||||
mfr => mfr.data),
|
||||
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
|
||||
security_map = new FilteredDictionary<uint, SecurityMD>(m, smd => smd.security.ContractCase == Security.ContractOneofCase.Future);
|
||||
@@ -126,8 +126,7 @@ namespace bts.shell.manual_fills {
|
||||
new ManualFillKey(_placeholder_id_counter++),
|
||||
new ManualFillTuple(new ManualFillImmutable(new ManualFill {
|
||||
Side = Side.Buy
|
||||
}), null, accounts.FirstOrDefault(), users.FirstOrDefault())),
|
||||
_model.get_display_qty_divisor);
|
||||
}), null, accounts.FirstOrDefault(), users.FirstOrDefault())));
|
||||
|
||||
void _add_fill(ManualFillRow new_row) =>
|
||||
_placeholder_rows.InsertSorted((new_row ?? (new_row = row_factory())).placeholder_key.HasValue ?
|
||||
@@ -211,20 +210,11 @@ namespace bts.shell.manual_fills {
|
||||
} else {
|
||||
add_imported_fills();
|
||||
}
|
||||
void add_imported_fills() {
|
||||
_placeholder_rows.InsertSorted(parsed_fills.Select(mk_row), _row_key_comparer);
|
||||
|
||||
ManualFillRow mk_row(ParsedManualFill pmf) {
|
||||
var data = new ManualFillData(
|
||||
new ManualFillKey(_placeholder_id_counter++),
|
||||
new ManualFillTuple(
|
||||
new ManualFillImmutable(pmf.result),
|
||||
pmf.result.SecurityKey != 0 && security_map.TryGetValue(pmf.result.SecurityKey, out var smd) ? smd.security : (SecurityImmutable?)null,
|
||||
accounts.FirstOrDefault(a => a?.id == pmf.result.AccountId),
|
||||
users.FirstOrDefault(a => a?.id == pmf.result.UserId)));
|
||||
return new ManualFillRow(data, _model.get_display_qty_divisor);
|
||||
}
|
||||
}
|
||||
void add_imported_fills() =>
|
||||
_placeholder_rows.InsertSorted(parsed_fills.Select(pmf => new ManualFillRow(new ManualFillData(new ManualFillKey(_placeholder_id_counter++), new ManualFillTuple(new ManualFillImmutable(pmf.result),
|
||||
pmf.result.SecurityKey != 0 && security_map.TryGetValue(pmf.result.SecurityKey, out var smd) ? smd.security : (SecurityImmutable?)null,
|
||||
accounts.FirstOrDefault(a => a?.id == pmf.result.AccountId),
|
||||
users.FirstOrDefault(a => a?.id == pmf.result.UserId))))), _row_key_comparer);
|
||||
} catch (Exception ex) {
|
||||
BTSMessageBox.Error($"Error importing fills: {ex}.", ManualFillsWindowFactory.window_name);
|
||||
}
|
||||
|
||||
@@ -212,12 +212,12 @@
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Size, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Qty" SortMemberPath="side">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding display_size}" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding size}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownUInt MinWidth="40" Value="{Binding display_size, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<c:TickUpDownUInt MinWidth="40" Value="{Binding size, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
@@ -597,19 +597,19 @@ namespace mbts.spark {
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
: base(Columns.Size, "Qty", r => r.display_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.display_quantity.ToString(), position, row_height,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.display_filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.display_filled_quantity.ToString(), position, row_height,
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
@@ -1163,7 +1163,7 @@ namespace mbts.spark {
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var tick_up_down = new TickUpDownNullableUIntCommit();
|
||||
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.SourceValueProperty, nameof(OrderBookRowVM.prospective_display_quantity));
|
||||
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.SourceValueProperty, nameof(OrderBookRowVM.prospective_quantity));
|
||||
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.OnCommitProperty, nameof(OrderBookRowVM.modify_order_quantity_andor_price));
|
||||
tick_up_down.InputBindings.Add(_create_keyinput_binding(Key.Enter, nameof(OrderBookRowVM.modify_order_quantity_andor_price)));
|
||||
tick_up_down.InputBindings.Add(_create_keyinput_binding(Key.Escape, nameof(OrderBookRowVM.revert_prospective_order_quantity_andor_price)));
|
||||
|
||||
@@ -29,9 +29,7 @@ namespace mbts.spark {
|
||||
int _depth_level;
|
||||
SecurityImmutable? _security => order_tuple.order.security;
|
||||
// for mini-ob
|
||||
DisplayQuantityMultiplier _prospective_qty_translator_backing;
|
||||
uint _display_qty_divisor_backing;
|
||||
bool _display_qty_divisor_dirty = true;
|
||||
uint _prospective_quantity;
|
||||
double _prospective_price;
|
||||
Action<bool> _on_selection_changed;
|
||||
|
||||
@@ -39,6 +37,7 @@ namespace mbts.spark {
|
||||
_owner = owner;
|
||||
this.order_tuple = order_tuple;
|
||||
_is_expanded = is_expanded;
|
||||
prospective_quantity = this.quantity;
|
||||
prospective_price = this.price;
|
||||
_on_selection_changed = on_selection_changed;
|
||||
}
|
||||
@@ -95,18 +94,14 @@ namespace mbts.spark {
|
||||
public string last_price_text => _fmt_price(last_price);
|
||||
public string avg_price_text => _fmt_price(avg_price);
|
||||
public uint quantity => order.quantity();
|
||||
public uint display_quantity => quantity.safe_divide(_display_qty_divisor);
|
||||
public uint filled_quantity => order.filled();
|
||||
public uint display_filled_quantity => filled_quantity.safe_divide(_display_qty_divisor);
|
||||
public string last_msg => order.last_msg();
|
||||
// piq
|
||||
public uint piq => order_tuple.order.piq ?? uint.MaxValue;
|
||||
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.safe_divide(_display_qty_divisor).ToString();
|
||||
|
||||
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.ToString();
|
||||
public bool is_non_terminal => order.is_non_terminal();
|
||||
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
|
||||
public bool is_detachable { get => _is_detachable; set => SetVisual(ref _is_detachable, value); }
|
||||
public string filled_status_desc => order.filled_status_description(security.spec_or_default(), _display_qty_divisor);
|
||||
public string filled_status_desc => order.filled_status_description(security.spec_or_default());
|
||||
|
||||
// Root Algo
|
||||
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root.algo.value.CustomCase;
|
||||
@@ -134,12 +129,12 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
public uint prospective_quantity {
|
||||
get => _prospective_qty_translator.actual_value;
|
||||
set => _prospective_qty_translator.actual_value = value;
|
||||
}
|
||||
public uint prospective_display_quantity {
|
||||
get => _prospective_qty_translator.display_value;
|
||||
set => _prospective_qty_translator.display_value = value;
|
||||
get => _prospective_quantity;
|
||||
set {
|
||||
if (Set(ref _prospective_quantity, value)) {
|
||||
RaisePropertyChanged(nameof(is_qty_dirty));
|
||||
}
|
||||
}
|
||||
}
|
||||
public double prospective_price {
|
||||
get => _prospective_price;
|
||||
@@ -229,29 +224,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
uint _display_qty_divisor {
|
||||
get {
|
||||
_sync_display_qty_if_dirty();
|
||||
return _display_qty_divisor_backing;
|
||||
}
|
||||
}
|
||||
|
||||
IDisplayTranslator<uint> _prospective_qty_translator {
|
||||
get {
|
||||
if (_prospective_qty_translator_backing == null) {
|
||||
_prospective_qty_translator_backing = new DisplayQuantityMultiplier {
|
||||
conversion_data = new QuantityConversionData(_display_qty_divisor, QuantityConversionData.OpType.DivideActual),
|
||||
actual_value = quantity
|
||||
};
|
||||
_prospective_qty_translator.value_changed += () =>
|
||||
RaisePropertyChanged(nameof(is_qty_dirty));
|
||||
} else {
|
||||
_sync_display_qty_if_dirty();
|
||||
}
|
||||
return _prospective_qty_translator_backing;
|
||||
}
|
||||
}
|
||||
|
||||
#region IHierarchicalRow
|
||||
bool IHierarchicalRow.is_parent => is_parent;
|
||||
bool IHierarchicalRow.is_expanded => is_expanded;
|
||||
@@ -295,35 +267,22 @@ namespace mbts.spark {
|
||||
if ((Columns.OrderType & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(order_type), nameof(order_metadata));
|
||||
// quantity
|
||||
if ((Columns.Size & dirty) != 0) {
|
||||
RaisePropertyChanged(nameof(quantity), nameof(is_qty_dirty));
|
||||
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
|
||||
// via the Mini OB update column mechanism (vs the EditableQty column)
|
||||
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
|
||||
// row will no longer match the filter
|
||||
prospective_quantity = quantity;
|
||||
}
|
||||
// price
|
||||
if ((Columns.Price & dirty) != 0) {
|
||||
RaisePropertyChanged(nameof(price));
|
||||
RaisePropertyChanged(nameof(is_price_dirty));
|
||||
}
|
||||
// security
|
||||
if ((Columns.Security & dirty) != 0) {
|
||||
_display_qty_divisor_dirty = true;
|
||||
RaisePropertyChanged(
|
||||
// security props
|
||||
nameof(security),
|
||||
nameof(security_text),
|
||||
// qty props
|
||||
nameof(display_quantity),
|
||||
nameof(prospective_quantity),
|
||||
nameof(prospective_display_quantity),
|
||||
nameof(is_qty_dirty),
|
||||
//
|
||||
nameof(piq_text),
|
||||
nameof(display_filled_quantity));
|
||||
}
|
||||
if ((Columns.Size & dirty) != 0) {
|
||||
RaisePropertyChanged(nameof(quantity), nameof(display_quantity));
|
||||
if (_prospective_qty_translator_backing != null)
|
||||
_prospective_qty_translator_backing.actual_value = quantity;
|
||||
}
|
||||
if ((Columns.Filled & dirty) != 0) {
|
||||
RaisePropertyChanged(nameof(filled_quantity), nameof(display_filled_quantity));
|
||||
}
|
||||
if ((Columns.Security & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(security), nameof(security_text));
|
||||
// badge fill
|
||||
if (old_order_tuple.order.meta?.badge_color != order_tuple.order.meta?.badge_color) {
|
||||
// invalidate by setting to null (will be re-instantiated in the public getter)
|
||||
@@ -400,15 +359,6 @@ namespace mbts.spark {
|
||||
security.Value.ContractSpec.format(v) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
|
||||
void _sync_display_qty_if_dirty() {
|
||||
if (_display_qty_divisor_dirty) {
|
||||
_display_qty_divisor_dirty = false;
|
||||
_display_qty_divisor_backing = _owner.get_display_qty_divisor(security);
|
||||
if (_prospective_qty_translator_backing != null)
|
||||
_prospective_qty_translator_backing.conversion_data = new QuantityConversionData(_display_qty_divisor_backing, QuantityConversionData.OpType.DivideActual);
|
||||
}
|
||||
}
|
||||
|
||||
void RaisePropertyChanged(params string[] props) {
|
||||
var pc = PropertyChanged;
|
||||
if (pc == null)
|
||||
@@ -447,7 +397,6 @@ namespace mbts.spark {
|
||||
Columns.LastMessage |
|
||||
Columns.Piq,
|
||||
filled_status_desc_mask =
|
||||
Columns.Security |
|
||||
Columns.Filled |
|
||||
Columns.AvgFilled |
|
||||
Columns.Status;
|
||||
|
||||
@@ -639,9 +639,6 @@ namespace mbts.spark {
|
||||
|
||||
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order.actual_order_type());
|
||||
|
||||
internal uint get_display_qty_divisor(SecurityImmutable? security) =>
|
||||
_model.security_auxiliary_model.get_display_quantity_divisor(security);
|
||||
|
||||
internal void show_order_modify(OrderBookRowVM row) =>
|
||||
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
|
||||
|
||||
@@ -801,7 +798,7 @@ namespace mbts.spark {
|
||||
readonly Property<bool> _is_dirty;
|
||||
IRaiseCanExecuteChangedCommand _payup_bid_cmd, _payup_ask_cmd;
|
||||
IAlgoParamVM<double> _price_param;
|
||||
AlgoParamVM<uint?> _size_param;
|
||||
IAlgoParamVM<uint?> _size_param;
|
||||
AlgoParamPermissions
|
||||
_price_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write,
|
||||
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
|
||||
@@ -838,9 +835,7 @@ namespace mbts.spark {
|
||||
_price_param = null;
|
||||
_size_param = null;
|
||||
} else {
|
||||
_size_param = new AlgoParamVM<uint?>("size", "Size", owner._selected_params_mon.size, EqualityComparer<uint?>.Default,
|
||||
to_display_value: v => v.HasValue ? v.Value / display_quantity_divisor : v,
|
||||
from_display_value: v => v.HasValue ? v.Value * display_quantity_divisor : v) {
|
||||
_size_param = new AlgoParamVM<uint?>("size", "Size", owner._selected_params_mon.size) {
|
||||
permissions = _size_permissions & _param_permissions
|
||||
};
|
||||
var price_param = new AlgoParamVM<double>("price", "Price", owner._selected_params_mon.price, spec?.tick_equality_comparer() ?? MathUtils.epsilon_eq_comparer) {
|
||||
@@ -883,11 +878,10 @@ namespace mbts.spark {
|
||||
get => _security_md;
|
||||
set {
|
||||
if (Set(ref _security_md, value)) {
|
||||
RaisePropertyChanged(nameof(security), nameof(spec), nameof(display_quantity_divisor));
|
||||
RaisePropertyChanged(nameof(security), nameof(spec));
|
||||
bbo.security_md = value;
|
||||
_payup_bid_cmd.RaiseCanExecuteChanged();
|
||||
_payup_ask_cmd.RaiseCanExecuteChanged();
|
||||
_size_param?.refresh_display_value();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -909,7 +903,6 @@ namespace mbts.spark {
|
||||
public AlgoDefinitionVM algo_vm { get; }
|
||||
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows { get; }
|
||||
public BBOVM bbo { get; }
|
||||
public uint display_quantity_divisor => _owner.get_display_qty_divisor(security);
|
||||
public ICommand send_order { get; }
|
||||
public ICommand payup_bid_cmd => _payup_bid_cmd;
|
||||
public ICommand payup_ask_cmd => _payup_ask_cmd;
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_actual_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding BestBidSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
@@ -82,7 +82,7 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_actual_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding BestAskSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
@@ -101,7 +101,7 @@
|
||||
<TextBlock.Inlines>
|
||||
<Run Text="{Binding security, Mode=OneWay}" FontWeight="Bold"/>
|
||||
<Run Text="{Binding side, Mode=OneWay}" Foreground="{Binding side, Converter={StaticResource SideToBrushConverter}}"/>
|
||||
<Run Text="{Binding display_quantity, Mode=OneWay}"/>
|
||||
<Run Text="{Binding quantity, Mode=OneWay}"/>
|
||||
<Run>@</Run>
|
||||
<Run>
|
||||
<Run.Text>
|
||||
|
||||
@@ -5,7 +5,6 @@ using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -141,18 +140,15 @@ namespace bts.shell.pnl {
|
||||
public PositionPriceSize summary =>
|
||||
new PositionPriceSize(long_data.working_total, long_data.avg_price, short_data.working_total, short_data.avg_price);
|
||||
|
||||
public static WorkingOrderData Create(IEnumerable<OrderRecordImmutable> orders, QuantityConversionData qty_conversion) =>
|
||||
Create(orders.Select(o => (o, qty_conversion)));
|
||||
public static WorkingOrderData Create(IEnumerable<OrderRecordImmutable> orders) =>
|
||||
orders.Aggregate(Empty, (seed, order) => seed.Add(order));
|
||||
|
||||
public static WorkingOrderData Create(IEnumerable<(OrderRecordImmutable order, QuantityConversionData qty_conversion)> orders) =>
|
||||
orders.Aggregate(Empty, (seed, tup) => seed.Add(tup.order, tup.qty_conversion));
|
||||
|
||||
public WorkingOrderData Add(OrderRecordImmutable order, QuantityConversionData qty_conversion) {
|
||||
public WorkingOrderData Add(OrderRecordImmutable order) {
|
||||
if (order.working_quantity() <= 0)
|
||||
return new WorkingOrderData(long_data, short_data);
|
||||
switch (order.side()) {
|
||||
case Side.Buy: return new WorkingOrderData(long_data.Add(order, qty_conversion), short_data);
|
||||
case Side.Sell: return new WorkingOrderData(long_data, short_data.Add(order, qty_conversion));
|
||||
case Side.Buy: return new WorkingOrderData(long_data.Add(order), short_data);
|
||||
case Side.Sell: return new WorkingOrderData(long_data, short_data.Add(order));
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side");
|
||||
}
|
||||
}
|
||||
@@ -169,21 +165,18 @@ namespace bts.shell.pnl {
|
||||
}
|
||||
|
||||
readonly struct WorkingOrderSideData {
|
||||
public readonly double avg_price, display_avg_price;
|
||||
public readonly ImmutableSortedDictionary<double, ImmutableDictionary<uint, (OrderRecordImmutable order, QuantityConversionData qty_conversion)>> data;
|
||||
public readonly double avg_price;
|
||||
public readonly ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordImmutable>> data;
|
||||
public readonly int working_total;
|
||||
public readonly int display_working_total;
|
||||
|
||||
WorkingOrderSideData(double avg_price, int working_total, double display_avg_price, int display_working_total, ImmutableSortedDictionary<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>> data) {
|
||||
WorkingOrderSideData(double avg_price, int working_total, ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordImmutable>> data) {
|
||||
this.avg_price = avg_price;
|
||||
this.working_total = working_total;
|
||||
this.display_avg_price = display_avg_price;
|
||||
this.data = data;
|
||||
this.display_working_total = display_working_total;
|
||||
}
|
||||
|
||||
public static WorkingOrderSideData EmptyLong => new WorkingOrderSideData(double.NaN, 0, double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>>(new ReverseComparer<double>(OrderDataHelper.PriceComparer)));
|
||||
public static WorkingOrderSideData EmptyShort => new WorkingOrderSideData(double.NaN, 0, double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>>(OrderDataHelper.PriceComparer));
|
||||
public static WorkingOrderSideData EmptyLong => new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(new ReverseComparer<double>(OrderDataHelper.PriceComparer)));
|
||||
public static WorkingOrderSideData EmptyShort => new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(OrderDataHelper.PriceComparer));
|
||||
|
||||
public double top_price =>
|
||||
data.Count > 0 ?
|
||||
@@ -192,35 +185,23 @@ namespace bts.shell.pnl {
|
||||
|
||||
public int top_volume =>
|
||||
data.Count > 0 ?
|
||||
data.Values.First().Values.Sum(o => o.order.working_quantity()) :
|
||||
data.Values.First().Values.Sum(o => o.working_quantity()) :
|
||||
0;
|
||||
|
||||
public int display_top_volume =>
|
||||
data.Count > 0 ?
|
||||
data.Values.First().Values.Sum(o => o.qty_conversion.to_display(o.order.working_quantity())) :
|
||||
0;
|
||||
|
||||
public WorkingOrderSideData Add(OrderRecordImmutable order_record) =>
|
||||
Add(order_record, new QuantityConversionData());
|
||||
|
||||
public WorkingOrderSideData Add(OrderRecordImmutable order_record, QuantityConversionData qty_conversion) {
|
||||
public WorkingOrderSideData Add(OrderRecordImmutable order_record) {
|
||||
uint order = order_record.Key;
|
||||
int working_qty = order_record.working_quantity();
|
||||
double price = order_record.price();
|
||||
if (working_qty <= 0)
|
||||
throw new ArgumentException(nameof(working_qty));
|
||||
uint order = order_record.Key;
|
||||
int dworking_qty = qty_conversion.to_display(working_qty);
|
||||
double price = order_record.price();
|
||||
if (working_total == 0) {
|
||||
Log.assert(display_working_total == 0);
|
||||
return new WorkingOrderSideData(price, working_qty, display_avg_price, dworking_qty, data.Add(price, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>.Empty.Add(order, (order_record, qty_conversion))));
|
||||
return new WorkingOrderSideData(price, working_qty, data.Add(price, ImmutableDictionary<uint, OrderRecordImmutable>.Empty.Add(order, order_record)));
|
||||
} else return new WorkingOrderSideData(
|
||||
avg_price + ((price - avg_price) * ((double)working_qty / (working_total + working_qty))),
|
||||
working_total + working_qty,
|
||||
display_avg_price + ((price - display_avg_price) * ((double)dworking_qty / (display_working_total + dworking_qty))),
|
||||
display_working_total + dworking_qty,
|
||||
data.SetItem(price, data.TryGetValue(price, out var curr) ?
|
||||
curr.Add(order, (order_record, qty_conversion)) :
|
||||
ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>.Empty.Add(order, (order_record, qty_conversion))));
|
||||
curr.Add(order, order_record) :
|
||||
ImmutableDictionary<uint, OrderRecordImmutable>.Empty.Add(order, order_record)));
|
||||
}
|
||||
|
||||
public WorkingOrderSideData Remove(uint order, double price) {
|
||||
@@ -229,30 +210,22 @@ namespace bts.shell.pnl {
|
||||
throw new KeyNotFoundException(nameof(price));
|
||||
if (!wdata.TryGetValue(order, out var worder))
|
||||
throw new KeyNotFoundException(nameof(order));
|
||||
var wqty = worder.order.working_quantity();
|
||||
var dwqty = worder.qty_conversion.to_display(wqty);
|
||||
var wqty = worder.working_quantity();
|
||||
wdata = wdata.Remove(order);
|
||||
if (wdata.Count == 0)
|
||||
tmp_data = tmp_data.Remove(price);
|
||||
if (tmp_data.Count == 0)
|
||||
return new WorkingOrderSideData(double.NaN, 0, double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>>(data.KeyComparer));
|
||||
return new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(data.KeyComparer));
|
||||
return new WorkingOrderSideData(
|
||||
avg_price - ((price - avg_price) * (wqty / (working_total - wqty))),
|
||||
working_total - wqty,
|
||||
display_avg_price - ((price - display_avg_price) * (dwqty / (display_working_total - dwqty))),
|
||||
display_working_total - dwqty,
|
||||
tmp_data);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
if (data.Count > 0) {
|
||||
var best_order = data.Values.First().Values.First();
|
||||
return OrderUtils.order_collection_summary(
|
||||
best_order.order.price(),
|
||||
display_avg_price,
|
||||
best_order.qty_conversion.to_display(best_order.order.working_quantity()),
|
||||
display_working_total,
|
||||
SecurityDataHelper.Defaults.ContractSpecificationImmutable);
|
||||
return OrderUtils.order_collection_summary(best_order.price(), avg_price, best_order.working_quantity(), working_total, SecurityDataHelper.Defaults.ContractSpecificationImmutable);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
@@ -275,7 +248,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
public readonly PNLKey key;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly QuantityConversionData qty_conversion;
|
||||
public readonly string account_name;
|
||||
public readonly PNLCalculationType calc_type;
|
||||
public readonly PositionPriceSize day, raw_day, sod;
|
||||
@@ -301,8 +273,7 @@ namespace bts.shell.pnl {
|
||||
WorkingOrderData? working = null,
|
||||
SecurityImmutable? security = null,
|
||||
string account_name = null,
|
||||
PNLCalculationType calc_type = PNLCalculationType.Mid,
|
||||
QuantityConversionData? qty_conversion = null) {
|
||||
PNLCalculationType calc_type = PNLCalculationType.Mid) {
|
||||
//
|
||||
this.key = key;
|
||||
this.cpp = cpp;
|
||||
@@ -317,7 +288,6 @@ namespace bts.shell.pnl {
|
||||
this.security = security;
|
||||
this.account_name = account_name;
|
||||
this.calc_type = calc_type;
|
||||
this.qty_conversion = qty_conversion ?? new QuantityConversionData();
|
||||
}
|
||||
|
||||
[Flags]
|
||||
@@ -398,10 +368,6 @@ namespace bts.shell.pnl {
|
||||
MathUtils.weighted_average(
|
||||
sod_long_avg_price_delta_raw, sod.long_pos,
|
||||
sod_short_avg_price_delta_raw, sod.short_pos);
|
||||
// display
|
||||
public double display_sod_net_pos => qty_conversion.to_display(sod_net_pos);
|
||||
public double display_sod_long_pos => qty_conversion.to_display(sod_long_pos);
|
||||
public double display_sod_short_pos => qty_conversion.to_display(sod_short_pos);
|
||||
|
||||
/// Day
|
||||
public double day_pnl => cpp *
|
||||
@@ -429,19 +395,11 @@ namespace bts.shell.pnl {
|
||||
MathUtils.weighted_average(
|
||||
day_long_avg_price_delta_raw, day.long_pos,
|
||||
day_short_avg_price_delta_raw, day.short_pos);
|
||||
// display
|
||||
public double display_day_net_pos => qty_conversion.to_display(day_net_pos);
|
||||
public double display_day_long_pos => qty_conversion.to_display(day_long_pos);
|
||||
public double display_day_short_pos => qty_conversion.to_display(day_short_pos);
|
||||
|
||||
/// Raw Day
|
||||
public double raw_day_long_pos => raw_day.long_pos;
|
||||
public double raw_day_short_pos => raw_day.short_pos;
|
||||
public double raw_day_net_pos => raw_day.net_pos;
|
||||
// display
|
||||
public double display_raw_day_net_pos => qty_conversion.to_display(raw_day_net_pos);
|
||||
public double display_raw_day_long_pos => qty_conversion.to_display(raw_day_long_pos);
|
||||
public double display_raw_day_short_pos => qty_conversion.to_display(raw_day_short_pos);
|
||||
|
||||
/// Total
|
||||
public double total_pnl => DoubleUtil.nan_to_zero_sum(sod_pnl, day_pnl);
|
||||
@@ -470,10 +428,6 @@ namespace bts.shell.pnl {
|
||||
MathUtils.weighted_average(
|
||||
total_long_avg_price_delta_raw, total_long_pos,
|
||||
total_short_avg_price_delta_raw, total_short_pos);
|
||||
// display
|
||||
public double display_total_net_pos => qty_conversion.to_display(total_net_pos);
|
||||
public double display_total_long_pos => qty_conversion.to_display(total_long_pos);
|
||||
public double display_total_short_pos => qty_conversion.to_display(total_short_pos);
|
||||
|
||||
#region Working
|
||||
// long
|
||||
@@ -499,10 +453,6 @@ namespace bts.shell.pnl {
|
||||
MathUtils.weighted_average(
|
||||
working_long_avg_price_delta_raw, working_long_pos,
|
||||
working_short_avg_price_delta_raw, working_short_pos);
|
||||
// display
|
||||
public double display_working_net_pos => qty_conversion.to_display(working_net_pos);
|
||||
public double display_working_long_pos => qty_conversion.to_display(working_long_pos);
|
||||
public double display_working_short_pos => qty_conversion.to_display(working_short_pos);
|
||||
#endregion
|
||||
|
||||
WaterfallPriceFlags _waterfall_flags() {
|
||||
@@ -558,8 +508,7 @@ namespace bts.shell.pnl {
|
||||
settlement_price.Equals(other.settlement_price) &&
|
||||
calc_type.Equals(other.calc_type) &&
|
||||
raw_day == other.raw_day &&
|
||||
working.summary == other.working.summary &&
|
||||
qty_conversion == other.qty_conversion;
|
||||
working.summary == other.working.summary;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is PNLRowData other && Equals(other);
|
||||
@@ -574,7 +523,7 @@ namespace bts.shell.pnl {
|
||||
}
|
||||
|
||||
static class PNLRowDataFactory {
|
||||
public static PNLRowData create((SecurityImmutable? sec, PNLUnitImmutable value, QuantityConversionData qty_conversion) t) =>
|
||||
public static PNLRowData create((SecurityImmutable? sec, PNLUnitImmutable value) t) =>
|
||||
new PNLRowData(new PNLKey(t.value.Pos.AccountId, t.value.Pos.SecurityId),
|
||||
t.value.CashValue,
|
||||
t.value.BidPrice,
|
||||
@@ -586,8 +535,7 @@ namespace bts.shell.pnl {
|
||||
t.value.Pos.raw_day_pps(),
|
||||
WorkingOrderData.Empty,
|
||||
t.sec,
|
||||
t.value.Pos.Account,
|
||||
qty_conversion: t.qty_conversion);
|
||||
t.value.Pos.Account);
|
||||
}
|
||||
|
||||
static class PNLDataExtensions {
|
||||
@@ -606,7 +554,6 @@ namespace bts.shell.pnl {
|
||||
working ?? pnl_data.working,
|
||||
security ?? pnl_data.security,
|
||||
account_name ?? pnl_data.account_name,
|
||||
calc_type ?? pnl_data.calc_type,
|
||||
qty_conversion: pnl_data.qty_conversion);
|
||||
calc_type ?? pnl_data.calc_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,7 @@ namespace bts.shell.pnl {
|
||||
decimal sod_long_pos,
|
||||
decimal sod_short_pos,
|
||||
decimal working_long_pos = 0,
|
||||
decimal working_short_pos = 0,
|
||||
decimal? display_day_long_pos = null,
|
||||
decimal? display_day_short_pos = null,
|
||||
decimal? display_raw_day_long_pos = null,
|
||||
decimal? display_raw_day_short_pos = null,
|
||||
decimal? display_sod_long_pos = null,
|
||||
decimal? display_sod_short_pos = null,
|
||||
decimal? display_working_long_pos = null,
|
||||
decimal? display_working_short_pos = null) {
|
||||
decimal working_short_pos = 0) {
|
||||
//
|
||||
this.day_pnl = day_pnl;
|
||||
this.day_long_pos = day_long_pos;
|
||||
@@ -36,15 +28,6 @@ namespace bts.shell.pnl {
|
||||
this.sod_short_pos = sod_short_pos;
|
||||
this.working_long_pos = working_long_pos;
|
||||
this.working_short_pos = working_short_pos;
|
||||
// display
|
||||
this.display_day_long_pos = display_day_long_pos ?? day_long_pos;
|
||||
this.display_day_short_pos = display_day_short_pos ?? day_short_pos;
|
||||
this.display_raw_day_long_pos = display_raw_day_long_pos ?? raw_day_long_pos;
|
||||
this.display_raw_day_short_pos = display_raw_day_short_pos ?? raw_day_short_pos;
|
||||
this.display_sod_long_pos = display_sod_long_pos ?? sod_long_pos;
|
||||
this.display_sod_short_pos = display_sod_short_pos ?? sod_short_pos;
|
||||
this.display_working_long_pos = display_working_long_pos ?? working_long_pos;
|
||||
this.display_working_short_pos = display_working_short_pos ?? working_short_pos;
|
||||
}
|
||||
|
||||
// sod
|
||||
@@ -52,48 +35,24 @@ namespace bts.shell.pnl {
|
||||
public decimal sod_long_pos { get; }
|
||||
public decimal sod_short_pos { get; }
|
||||
public decimal sod_net_pos => sod_long_pos - sod_short_pos;
|
||||
// sod display
|
||||
public decimal display_sod_long_pos { get; }
|
||||
public decimal display_sod_short_pos { get; }
|
||||
public decimal display_sod_net_pos => display_sod_long_pos - display_sod_short_pos;
|
||||
|
||||
// day
|
||||
public decimal day_pnl { get; }
|
||||
public decimal day_long_pos { get; }
|
||||
public decimal day_short_pos { get; }
|
||||
public decimal day_net_pos => day_long_pos - day_short_pos;
|
||||
// display
|
||||
public decimal display_day_long_pos { get; }
|
||||
public decimal display_day_short_pos { get; }
|
||||
public decimal display_day_net_pos => display_day_long_pos - display_day_short_pos;
|
||||
|
||||
// raw day
|
||||
public decimal raw_day_long_pos { get; }
|
||||
public decimal raw_day_short_pos { get; }
|
||||
public decimal raw_day_net_pos => raw_day_long_pos - raw_day_short_pos;
|
||||
// display
|
||||
public decimal display_raw_day_long_pos { get; }
|
||||
public decimal display_raw_day_short_pos { get; }
|
||||
public decimal display_raw_day_net_pos => display_raw_day_long_pos - display_raw_day_short_pos;
|
||||
|
||||
// total
|
||||
public decimal total_pnl => sod_pnl + day_pnl;
|
||||
public decimal total_net_pos => total_long_pos - total_short_pos;
|
||||
public decimal total_long_pos => sod_long_pos + day_long_pos;
|
||||
public decimal total_short_pos => sod_short_pos + day_short_pos;
|
||||
// display
|
||||
public decimal display_total_long_pos => display_sod_long_pos + display_day_long_pos;
|
||||
public decimal display_total_short_pos => display_sod_short_pos + display_day_short_pos;
|
||||
public decimal display_total_net_pos => display_total_long_pos - display_total_short_pos;
|
||||
|
||||
// working
|
||||
public decimal working_long_pos { get; }
|
||||
public decimal working_short_pos { get; }
|
||||
public decimal working_net_pos => working_long_pos - working_short_pos;
|
||||
// display
|
||||
public decimal display_working_long_pos { get; }
|
||||
public decimal display_working_short_pos { get; }
|
||||
public decimal display_working_net_pos => display_working_long_pos - display_working_short_pos;
|
||||
public decimal working_short_pos { get; }
|
||||
|
||||
public bool Equals(PNLNormalizedData other) =>
|
||||
day_pnl == other.day_pnl &&
|
||||
@@ -105,17 +64,6 @@ namespace bts.shell.pnl {
|
||||
sod_long_pos == other.sod_long_pos &&
|
||||
sod_short_pos == other.sod_short_pos &&
|
||||
working_long_pos == other.working_long_pos &&
|
||||
working_short_pos == other.working_short_pos &&
|
||||
// display
|
||||
sod_long_pos == other.sod_long_pos &&
|
||||
sod_short_pos == other.sod_short_pos &&
|
||||
day_long_pos == other.day_long_pos &&
|
||||
day_short_pos == other.day_short_pos &&
|
||||
raw_day_long_pos == other.raw_day_long_pos &&
|
||||
raw_day_short_pos == other.raw_day_short_pos &&
|
||||
total_long_pos == other.total_long_pos &&
|
||||
total_short_pos == other.total_short_pos &&
|
||||
working_long_pos == other.working_long_pos &&
|
||||
working_short_pos == other.working_short_pos;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
@@ -154,16 +102,7 @@ namespace bts.shell.pnl {
|
||||
a.sod_long_pos + b.sod_long_pos,
|
||||
a.sod_short_pos + b.sod_short_pos,
|
||||
a.working_long_pos + b.working_long_pos,
|
||||
a.working_short_pos + b.working_short_pos,
|
||||
// display
|
||||
a.display_day_long_pos + b.display_day_long_pos,
|
||||
a.display_day_short_pos + b.display_day_short_pos,
|
||||
a.display_raw_day_long_pos + b.display_raw_day_long_pos,
|
||||
a.display_raw_day_short_pos + b.display_raw_day_short_pos,
|
||||
a.display_sod_long_pos + b.display_sod_long_pos,
|
||||
a.display_sod_short_pos + b.display_sod_short_pos,
|
||||
a.display_working_long_pos + b.display_working_long_pos,
|
||||
a.display_working_short_pos + b.display_working_short_pos);
|
||||
a.working_short_pos + b.working_short_pos);
|
||||
|
||||
public static PNLNormalizedData operator -(PNLNormalizedData a, PNLNormalizedData b) =>
|
||||
new PNLNormalizedData(
|
||||
@@ -176,15 +115,6 @@ namespace bts.shell.pnl {
|
||||
a.sod_long_pos - b.sod_long_pos,
|
||||
a.sod_short_pos - b.sod_short_pos,
|
||||
a.working_long_pos - b.working_long_pos,
|
||||
a.working_short_pos - b.working_short_pos,
|
||||
// display
|
||||
a.display_day_long_pos - b.display_day_long_pos,
|
||||
a.display_day_short_pos - b.display_day_short_pos,
|
||||
a.display_raw_day_long_pos - b.display_raw_day_long_pos,
|
||||
a.display_raw_day_short_pos - b.display_raw_day_short_pos,
|
||||
a.display_sod_long_pos - b.display_sod_long_pos,
|
||||
a.display_sod_short_pos - b.display_sod_short_pos,
|
||||
a.display_working_long_pos - b.display_working_long_pos,
|
||||
a.display_working_short_pos - b.display_working_short_pos);
|
||||
a.working_short_pos - b.working_short_pos);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace bts.shell.pnl {
|
||||
public readonly DIYGridAnimationModel<PNLView.Columns, TRow> animation_model = new DIYGridAnimationModel<PNLView.Columns, TRow>(TimeSpan.FromSeconds(0.3));
|
||||
|
||||
static readonly Func<WorkingOrderSideData, string> _fmt_working_order_data =
|
||||
d => OrderUtils.order_collection_summary(d.top_price, d.display_top_volume, d.display_working_total);
|
||||
d => OrderUtils.order_collection_summary(d.top_price, d.top_volume, d.working_total);
|
||||
|
||||
static readonly IComparer<WorkingOrderSideData> _working_total_comparer =
|
||||
Comparer<int>.Default.wrap<int, WorkingOrderSideData>(d => d.working_total);
|
||||
|
||||
@@ -31,40 +31,40 @@ namespace bts.shell.pnl {
|
||||
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
|
||||
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
|
||||
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.security?.Symbol ?? string.Empty);
|
||||
security_cell = _cell_map.add(ColumnKeys.Security, d => (d.security), null, EqualityComparer<SecurityImmutable?>.Default, Comparer<SecurityImmutable>.Default.to_nullable());
|
||||
security_cell = _cell_map.add(ColumnKeys.Security, d => d.security, null, EqualityComparer<SecurityImmutable?>.Default, Comparer<SecurityImmutable>.Default.to_nullable());
|
||||
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
|
||||
cpp = _cell_map.add(ColumnKeys.CPP, d => d.cpp);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.display_sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.display_sod_long_pos);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
|
||||
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price_raw);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.display_sod_short_pos);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
|
||||
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price_raw);
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.display_day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.display_day_long_pos);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
|
||||
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price_raw);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.display_day_short_pos);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
|
||||
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price_raw);
|
||||
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
|
||||
// raw day
|
||||
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.display_raw_day_net_pos);
|
||||
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.display_raw_day_long_pos);
|
||||
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.display_raw_day_short_pos);
|
||||
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.raw_day_net_pos);
|
||||
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.raw_day_long_pos);
|
||||
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.raw_day_short_pos);
|
||||
// working
|
||||
working_net_pos = _cell_map.add_net_pos(ColumnKeys.WorkingNetPos, d => d.display_working_net_pos);
|
||||
working_net_pos = _cell_map.add_net_pos(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
|
||||
working_long_pos = _cell_map.add_working_long(ColumnKeys.WorkingLongPos, d => d.working.long_data, d => d.bid_price);
|
||||
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price_raw);
|
||||
working_short_pos = _cell_map.add_working_short(ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price);
|
||||
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price_raw);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(ColumnKeys.TotalNetPos, d => d.display_total_net_pos);
|
||||
total_long_pos = _cell_map.add_long_pos(ColumnKeys.TotalLongPos, d => d.display_total_long_pos);
|
||||
total_net_pos = _cell_map.add_net_pos(ColumnKeys.TotalNetPos, d => d.total_net_pos);
|
||||
total_long_pos = _cell_map.add_long_pos(ColumnKeys.TotalLongPos, d => d.total_long_pos);
|
||||
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price_raw);
|
||||
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.display_total_short_pos);
|
||||
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.total_short_pos);
|
||||
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price_raw);
|
||||
// market
|
||||
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
|
||||
@@ -112,16 +112,7 @@ namespace bts.shell.pnl {
|
||||
sod_long,
|
||||
sod_short,
|
||||
DoubleUtil.round_to_decimal(data.working_long_pos),
|
||||
DoubleUtil.round_to_decimal(data.working_short_pos),
|
||||
// display
|
||||
DoubleUtil.round_to_decimal(data.display_day_long_pos),
|
||||
DoubleUtil.round_to_decimal(data.display_day_short_pos),
|
||||
DoubleUtil.round_to_decimal(data.display_raw_day_long_pos),
|
||||
DoubleUtil.round_to_decimal(data.display_raw_day_short_pos),
|
||||
DoubleUtil.round_to_decimal(data.display_sod_long_pos),
|
||||
DoubleUtil.round_to_decimal(data.display_sod_short_pos),
|
||||
DoubleUtil.round_to_decimal(data.display_working_long_pos),
|
||||
DoubleUtil.round_to_decimal(data.display_working_short_pos));
|
||||
DoubleUtil.round_to_decimal(data.working_short_pos));
|
||||
if (_normalized_data != nd) {
|
||||
_normalized_data = nd;
|
||||
updated?.Invoke(nd);
|
||||
@@ -215,4 +206,4 @@ namespace bts.shell.pnl {
|
||||
public override string ToString() =>
|
||||
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,27 +112,27 @@ namespace bts.shell.pnl {
|
||||
});
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.display_sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.display_sod_long_pos);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.display_sod_short_pos);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.display_day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.display_day_long_pos);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.display_day_short_pos);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
|
||||
// raw day
|
||||
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.display_raw_day_net_pos);
|
||||
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.display_raw_day_long_pos);
|
||||
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.display_raw_day_short_pos);
|
||||
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.raw_day_net_pos);
|
||||
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.raw_day_long_pos);
|
||||
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.raw_day_short_pos);
|
||||
// total
|
||||
total_pnl = _cell_map.add(ColumnKeys.TotalPNL, d => d.total_pnl, FormattingDelegates.Price<decimal>.Value);
|
||||
total_net_pos = _cell_map.add(ColumnKeys.TotalNetPos, d => d.display_total_net_pos);
|
||||
total_long_pos = _cell_map.add(ColumnKeys.TotalLongPos, d => d.display_total_long_pos);
|
||||
total_short_pos = _cell_map.add(ColumnKeys.TotalShortPos, d => d.display_total_short_pos);
|
||||
total_net_pos = _cell_map.add(ColumnKeys.TotalNetPos, d => d.total_net_pos);
|
||||
total_long_pos = _cell_map.add(ColumnKeys.TotalLongPos, d => d.total_long_pos);
|
||||
total_short_pos = _cell_map.add(ColumnKeys.TotalShortPos, d => d.total_short_pos);
|
||||
// working
|
||||
working_net_pos = _cell_map.add(ColumnKeys.WorkingNetPos, d => d.display_working_net_pos);
|
||||
working_long_pos = _cell_map.add(ColumnKeys.WorkingLongPos, d => d.display_working_long_pos);
|
||||
working_short_pos = _cell_map.add(ColumnKeys.WorkingShortPos, d => d.display_working_short_pos);
|
||||
working_net_pos = _cell_map.add(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
|
||||
working_long_pos = _cell_map.add(ColumnKeys.WorkingLongPos, d => d.working_long_pos);
|
||||
working_short_pos = _cell_map.add(ColumnKeys.WorkingShortPos, d => d.working_short_pos);
|
||||
|
||||
_cell_map.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace mbts.spark {
|
||||
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged, ITemplateSelectorKey {
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
readonly string _template_key;
|
||||
readonly Func<T, T> _to_display_value, _from_display_value;
|
||||
AlgoParamPermissions _permissions;
|
||||
bool _is_enabled, _has_value;
|
||||
T _committed_value, _value;
|
||||
@@ -21,15 +20,13 @@ namespace mbts.spark {
|
||||
public AlgoParamVM(string id, string display_name, T value, string template_key = null)
|
||||
: this(id, display_name, value, EqualityComparer<T>.Default, template_key) { }
|
||||
|
||||
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer, string template_key = null, Func<T, T> to_display_value = null, Func<T, T> from_display_value = null) {
|
||||
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer, string template_key = null) {
|
||||
this.id = id;
|
||||
this.display_name = display_name;
|
||||
_value = value;
|
||||
_committed_value = value;
|
||||
_comparer = comparer;
|
||||
_template_key = template_key;
|
||||
_to_display_value = to_display_value;
|
||||
_from_display_value = from_display_value;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
@@ -46,10 +43,6 @@ namespace mbts.spark {
|
||||
_toggle_is_dirty();
|
||||
}
|
||||
}
|
||||
public T display_value {
|
||||
get => _to_display_value == null ? value : _to_display_value(value);
|
||||
set => this.value = _from_display_value == null ? value : _from_display_value(value);
|
||||
}
|
||||
public AlgoParamPermissions permissions {
|
||||
get => _permissions;
|
||||
set {
|
||||
@@ -61,7 +54,6 @@ namespace mbts.spark {
|
||||
public bool has_value { get => _has_value; set => _set(ref _has_value, value); }
|
||||
public bool is_dirty { get; private set; }
|
||||
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
|
||||
object IAlgoParamVM.display_value { get => display_value; set => display_value = (T)value; }
|
||||
|
||||
string ITemplateSelectorKey.key => _template_key;
|
||||
|
||||
@@ -72,9 +64,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void refresh_display_value() =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(display_value)));
|
||||
|
||||
public IObservable<T> subscribe_value() {
|
||||
return Observable.Create<T>(o => {
|
||||
o.OnNext(_value);
|
||||
@@ -124,7 +113,6 @@ namespace mbts.spark {
|
||||
value_changed(_value, _value = new_value);
|
||||
}
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(value)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(display_value)));
|
||||
}
|
||||
|
||||
void _toggle_is_dirty() {
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace mbts.spark {
|
||||
readonly Property<TimeInForce> _current_time_in_force;
|
||||
readonly Property<CustomOrderDefinition.CustomOneofCase> _current_order_type;
|
||||
readonly Property<bool> _price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty;
|
||||
uint _display_quantity_divisor = 1;
|
||||
|
||||
public ExistingOrderTicketVM(OrdersModel model, IObservable<(SecurityMD? security_md, OrderRecordImmutable order)> order_source, IObservable<int[]> calculator_buttons) {
|
||||
uint? key = null;
|
||||
@@ -41,7 +40,7 @@ namespace mbts.spark {
|
||||
(val, curr) => !double.IsNaN(val) && val != curr));
|
||||
_size_dirty = backing(
|
||||
nameof(size_dirty),
|
||||
size_calculator.observe_actual_value()
|
||||
size_calculator.observe_current(true)
|
||||
.CombineLatest(_current_size.observe_current(true),
|
||||
(val, curr) => val.HasValue && val.Value != curr));
|
||||
_account_dirty = backing(
|
||||
@@ -84,17 +83,14 @@ namespace mbts.spark {
|
||||
_dispose = Disposable.Create(
|
||||
size_calculator,
|
||||
properties_vm,
|
||||
security_md_source.OnDispatcher().Subscribe(smd => {
|
||||
bbo.security_md = smd;
|
||||
display_quantity_divisor = model.security_auxiliary_model.get_display_quantity_divisor(smd.security);
|
||||
}),
|
||||
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
|
||||
bbo,
|
||||
order_source.OnDispatcher().Subscribe(ort => {
|
||||
key = ort.order.Key;
|
||||
if (DoubleHelper.IsNaNOrInfinity(price))
|
||||
price = ort.order.State.Price;
|
||||
if (!size_calculator.value.HasValue || !size_calculator.value_setter_called)
|
||||
size_calculator.bypass_value_setter(ort.order.State.Quantity, true);
|
||||
size_calculator.bypass_value_setter(ort.order.State.Quantity);
|
||||
}),
|
||||
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).OnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
|
||||
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().OnDispatcher().Subscribe(modify_order));
|
||||
@@ -108,13 +104,6 @@ namespace mbts.spark {
|
||||
public OrderPropertiesEditorVM properties_vm { get; }
|
||||
public double price { get => _price.Value; set => _price.Value = value; }
|
||||
public CalculatorLogic size_calculator { get; }
|
||||
public uint display_quantity_divisor {
|
||||
get => _display_quantity_divisor;
|
||||
private set {
|
||||
Set(ref _display_quantity_divisor, value);
|
||||
size_calculator.display_divisor = value;
|
||||
}
|
||||
}
|
||||
public double current_price => _current_price.Value;
|
||||
public uint current_size => _current_size.Value;
|
||||
public AccountTuple current_account => _current_account.Value;
|
||||
@@ -148,4 +137,4 @@ namespace mbts.spark {
|
||||
if (_order_type_dirty.Value) yield return "type";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ namespace mbts.spark {
|
||||
|
||||
class NewOrderTicketVM : DisposableViewModelBase, IRequestViewClose {
|
||||
readonly Property<SecurityImmutable> _security;
|
||||
uint _display_quantity_divisor = 1;
|
||||
|
||||
public NewOrderTicketVM(OrdersModel model, SecurityMD security_md, OrderDefinitionImmutable def, double price, uint size, IObservable<int[]> calculator_buttons)
|
||||
: this(model, Observable.Return(security_md), Observable.Return(new InlineOrderTicketProperties(model, def, price, size)), calculator_buttons) { }
|
||||
@@ -62,10 +61,7 @@ namespace mbts.spark {
|
||||
set_price_command = new DelegateCommand<double>(set_price);
|
||||
_dispose = Disposable.Create(
|
||||
bbo,
|
||||
security_md_source.OnDispatcher().Subscribe(smd => {
|
||||
bbo.security_md = smd;
|
||||
display_quantity_divisor = model.security_auxiliary_model.get_display_quantity_divisor(smd.security);
|
||||
}),
|
||||
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
|
||||
size_calculator = new CalculatorLogic(0, calculator_buttons),
|
||||
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
|
||||
);
|
||||
@@ -87,13 +83,6 @@ namespace mbts.spark {
|
||||
public SecurityImmutable security => _security.Value;
|
||||
public double price { get; set; }
|
||||
public CalculatorLogic size_calculator { get; }
|
||||
public uint display_quantity_divisor {
|
||||
get => _display_quantity_divisor;
|
||||
private set {
|
||||
Set(ref _display_quantity_divisor, value);
|
||||
size_calculator.display_divisor = value;
|
||||
}
|
||||
}
|
||||
public OrderPropertiesEditorVM properties_vm { get; }
|
||||
public BBOVM bbo { get; }
|
||||
public bool close_on_send { get; set; }
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
usp:OrderModificationControl.IsDirty="{Binding size_dirty, FallbackValue={x:Null}}"
|
||||
CommitOnEnter="True"
|
||||
Focusable="False"
|
||||
Value="{Binding size_calculator.display_value}"
|
||||
Value="{Binding size_calculator.value}"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid Grid.Column="2">
|
||||
@@ -114,26 +114,15 @@
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Command="{Binding size_calculator.set_actual_value_command}"
|
||||
CommandParameter="{Binding bbo.bid_size}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
>
|
||||
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
|
||||
<Binding Path="bbo.bid_size"/>
|
||||
<Binding Path="display_quantity_divisor"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
Content="{Binding bbo.bid_size}" Command="{Binding size_calculator.set_value_command}"
|
||||
CommandParameter="{Binding bbo.bid_size}" Foreground="{u:BTSColor MarketData.bid}"
|
||||
/>
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
Command="{Binding size_calculator.set_actual_value_command}"
|
||||
Content="{Binding bbo.ask_size}" Command="{Binding size_calculator.set_value_command}"
|
||||
CommandParameter="{Binding bbo.ask_size}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
>
|
||||
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
|
||||
<Binding Path="bbo.ask_size"/>
|
||||
<Binding Path="display_quantity_divisor"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" Grid.RowSpan="3">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -150,18 +139,9 @@
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
Margin="2,4,2,0"
|
||||
Content="{Binding}"
|
||||
Focusable="False"
|
||||
Margin="2,4,2,0" Content="{Binding}" Focusable="False" CommandParameter="{Binding}"
|
||||
Command="{Binding Path=DataContext.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
>
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static cv:DynamicDoubleMultiplierConverter.Instance}">
|
||||
<Binding/>
|
||||
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:OrderModificationControl}"/>
|
||||
</MultiBinding>
|
||||
</Button.CommandParameter>
|
||||
</Button>
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.OrderTicket"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
|
||||
Style="{StaticResource BTSWindowTitleTemplate}"
|
||||
>
|
||||
<Window x:Class="mbts.ui.spark.OrderTicket"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
|
||||
Style="{StaticResource BTSWindowTitleTemplate}">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@@ -126,12 +123,7 @@
|
||||
<TextBlock>
|
||||
<TextBlock.Inlines>
|
||||
<Run Text="{Binding security, Mode=OneWay}" FontWeight="Bold"/>
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10">
|
||||
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}" Mode="OneWay" StringFormat="({0})">
|
||||
<Binding Path="bid_size" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
|
||||
</MultiBinding>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10" Text="{Binding bid_size, Mode=OneWay, StringFormat='{}({0})'}"/>
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
@@ -148,12 +140,7 @@
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10">
|
||||
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}" Mode="OneWay" StringFormat="({0})">
|
||||
<Binding Path="ask_size" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
|
||||
</MultiBinding>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10" Text="{Binding ask_size, Mode=OneWay, StringFormat='{}({0})'}"/>
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -70,7 +70,6 @@ namespace mbts.spark {
|
||||
string id { get; }
|
||||
string display_name { get; }
|
||||
object value { get; set; }
|
||||
object display_value { get; set; }
|
||||
bool is_enabled { get; set; }
|
||||
bool is_dirty { get; }
|
||||
bool has_value { get; }
|
||||
@@ -82,7 +81,6 @@ namespace mbts.spark {
|
||||
interface IAlgoParamVM<T> : IAlgoParamVM {
|
||||
event PropertyChange<T> value_changed;
|
||||
new T value { get; set; }
|
||||
new T display_value { get; set; }
|
||||
}
|
||||
|
||||
interface IAlgoSelector : IAlgoParamVM<IAlgoVM>, IObserver<(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo)> {
|
||||
|
||||
@@ -985,10 +985,8 @@ namespace mbts.spark {
|
||||
|
||||
#region IAlgoParamVM
|
||||
IAlgoVM IAlgoParamVM<IAlgoVM>.value { get => selected_algo; set => selected_algo = value; }
|
||||
IAlgoVM IAlgoParamVM<IAlgoVM>.display_value { get => selected_algo; set => selected_algo = value; }
|
||||
event PropertyChange<IAlgoVM> IAlgoParamVM<IAlgoVM>.value_changed { add => selected_algo_changed += value; remove => selected_algo_changed -= value; }
|
||||
object IAlgoParamVM.value { get => selected_algo; set => selected_algo = (IAlgoVM)value; }
|
||||
object IAlgoParamVM.display_value { get => selected_algo; set => selected_algo = (IAlgoVM)value; }
|
||||
bool IAlgoParamVM.has_value => _algo != null;
|
||||
// changing child algos is not allowed, so an algo selector is never dirty
|
||||
bool IAlgoParamVM.is_dirty => false;
|
||||
|
||||
@@ -240,8 +240,8 @@ namespace mbts.spark {
|
||||
public static string price_quantity_description(double price, uint quantity, ContractSpecificationImmutable spec) =>
|
||||
new StringBuilderEx(quantity.ToString(), " @ ", spec.format(price) ?? price.ToString("#0.00##")).ToString();
|
||||
|
||||
public static string filled_status_description(this OrderRecordImmutable order, ContractSpecificationImmutable spec, uint display_qty_divisor = 1) {
|
||||
var sb = new StringBuilderEx("filled ", order.filled().safe_divide(display_qty_divisor).ToString());
|
||||
public static string filled_status_description(this OrderRecordImmutable order, ContractSpecificationImmutable spec) {
|
||||
var sb = new StringBuilderEx("filled ", order.filled().ToString());
|
||||
if (order.filled() > 0)
|
||||
sb.Add(" @ ", spec.format(order.avg_price()));
|
||||
sb.Add(", ", camel_to_title_case(order.status().ToString(), TextCapitalizationFormat.NoCaps));
|
||||
|
||||
@@ -10,7 +10,6 @@ using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.config;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -27,9 +26,8 @@ using OrderUpdatePiq = bts.api.core.TableUpdate<bts.api.orderman.OrderSecurity,
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
class OrdersModel : IDisposable {
|
||||
public static readonly bool piq_enabled = Configuration.get<bts.config.toggles.PlacementInQueue>().enabled;
|
||||
public readonly MarketDataAuxiliaryModel md_auxiliary_model;
|
||||
public readonly ISecurityAuxiliaryDataModel security_auxiliary_model;
|
||||
public static readonly bool piq_enabled = Configuration.get<bts.config.toggles.PlacementInQueue>().enabled;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrderFactory _factory;
|
||||
@@ -46,20 +44,8 @@ namespace mbts.spark {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
OrdersModel(IBus bus,
|
||||
OrderFactory factory,
|
||||
OrderService service,
|
||||
PositionService position_service,
|
||||
MBOService mbo_service,
|
||||
SpreaderModel spreader,
|
||||
DynamicOrderHelper dynamic_helper,
|
||||
AdminModel admin_model,
|
||||
IAlgoStudioModel algo_model,
|
||||
ISecurityAuxiliaryDataModel security_auxiliary_model,
|
||||
MarketDataAuxiliaryModel md_auxiliary_model,
|
||||
SecurityStore sstore,
|
||||
ErrorReporter error) {
|
||||
//
|
||||
OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, MBOService mbo_service,
|
||||
SpreaderModel spreader, DynamicOrderHelper dynamic_helper, AdminModel admin_model, IAlgoStudioModel algo_model, MarketDataAuxiliaryModel md_auxiliary_model, SecurityStore sstore, ErrorReporter error) {
|
||||
_bus = bus;
|
||||
_factory = factory;
|
||||
_service = service;
|
||||
@@ -71,7 +57,6 @@ namespace mbts.spark {
|
||||
this.admin_model = admin_model;
|
||||
this.algo_model = algo_model;
|
||||
this.md_auxiliary_model = md_auxiliary_model;
|
||||
this.security_auxiliary_model = security_auxiliary_model;
|
||||
var scheduler = ThreadPoolScheduler.Instance;
|
||||
var interval = TimeSpan.FromMilliseconds(100);
|
||||
on_orders = admin_model
|
||||
@@ -95,10 +80,7 @@ namespace mbts.spark {
|
||||
_position_service.subscribei_pnl_data().Naggle(interval, scheduler),
|
||||
p => p.Pos.AccountId)
|
||||
.join(sstore, p => p.Pos.SecurityId)
|
||||
.Select(
|
||||
tup => (tup.sec, tup.val, new QuantityConversionData(security_auxiliary_model.get_display_quantity_divisor(tup.sec), QuantityConversionData.OpType.DivideActual)),
|
||||
tup => tup.val.RecordId)
|
||||
.TableCheckedAutoCorrect(EqualityComparer<(SecurityImmutable?, PNLUnitImmutable, QuantityConversionData)>.Default)
|
||||
.TableCheckedAutoCorrect(EqualityComparer<(SecurityImmutable? sec, PNLUnitImmutable data)>.Default)
|
||||
.Where(t => t.has_changes())
|
||||
.Cache();
|
||||
var working_orders = on_orders.Filter(tup => tup.order.is_active());
|
||||
@@ -185,8 +167,8 @@ namespace mbts.spark {
|
||||
working_account_order_groups,
|
||||
PNLRowData.key_fn,
|
||||
acc_orders => acc_orders.group_key,
|
||||
(row, acc_orders, pnl_key) => row.with(working: WorkingOrderData.Create(acc_orders.table.state.Values.Select(t => t.order), row.qty_conversion)),
|
||||
(row, pnl_id) => row,
|
||||
(pnl_row_data, acc_orders, pnl_key) => pnl_row_data.with(working: WorkingOrderData.Create(acc_orders.table.state.Values.Select(t => t.order))),
|
||||
(pnl_row_data, pnl_id) => pnl_row_data,
|
||||
(acc_orders, pnl_id) => {
|
||||
string account;
|
||||
SecurityImmutable? security;
|
||||
@@ -200,9 +182,7 @@ namespace mbts.spark {
|
||||
return new PNLRowData(pnl_id,
|
||||
security: security,
|
||||
account_name: account,
|
||||
working: WorkingOrderData.Create(
|
||||
acc_orders.table.state.Values.Select(t => t.order),
|
||||
new QuantityConversionData(security_auxiliary_model.get_display_quantity_divisor(security), QuantityConversionData.OpType.DivideActual)));
|
||||
working: WorkingOrderData.Create(acc_orders.table.state.Values.Select(t => t.order)));
|
||||
},
|
||||
PNLRowData.key_fn)
|
||||
.Where(t => t.has_changes())
|
||||
@@ -302,7 +282,7 @@ namespace mbts.spark {
|
||||
public IObservable<TableUpdate<FillTuple, string>> on_fill_tuples { get; }
|
||||
public IObservable<TableUpdate<FillTuple, string>> on_fill_tuples_with_root { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint, OrderSecurity>> working_orders { get; }
|
||||
public IObservable<TableUpdate<(SecurityImmutable?, PNLUnitImmutable, QuantityConversionData), int>> on_pnl { get; }
|
||||
public IObservable<TableUpdate<(SecurityImmutable?, PNLUnitImmutable), int>> on_pnl { get; }
|
||||
public IObservable<TableUpdate<PNLRowData, PNLKey>> security_account_pnl { get; }
|
||||
public IObservable<TableUpdate<(SecurityImmutable?, PositionUnitImmutable), int>> on_positions { get; }
|
||||
public IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> studio_algos_with_meta { get; }
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts.config;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
|
||||
namespace mbts.spark {
|
||||
interface ISecurityAuxiliaryDataModel {
|
||||
SecurityAuxiliaryData? get_auxiliary_data(SecurityImmutable security);
|
||||
}
|
||||
|
||||
readonly struct SecurityAuxiliaryData {
|
||||
public SecurityAuxiliaryData(uint quantity_divisor) =>
|
||||
this.quantity_divisor = quantity_divisor;
|
||||
|
||||
public static SecurityAuxiliaryData Default => new SecurityAuxiliaryData(1);
|
||||
|
||||
public uint quantity_divisor { get; }
|
||||
}
|
||||
|
||||
[Export(typeof(ISecurityAuxiliaryDataModel))]
|
||||
class SecurityAuxiliaryDataModel : ISecurityAuxiliaryDataModel {
|
||||
readonly Dictionary<string, SecurityAuxiliaryData> _auxiliary_data = new Dictionary<string, SecurityAuxiliaryData>();
|
||||
|
||||
public SecurityAuxiliaryDataModel() {
|
||||
var sym_config = Configuration.get<bts.config.SymbolUIConfiguration>();
|
||||
var sym_config_items = Configuration.get<bts.config.SymbolUIConfiguration>().items;
|
||||
foreach (var item in sym_config_items)
|
||||
_auxiliary_data[item.symbol] = new SecurityAuxiliaryData(item.display_quantity_divisor);
|
||||
}
|
||||
|
||||
public SecurityAuxiliaryData? get_auxiliary_data(SecurityImmutable security) =>
|
||||
security.ToString().SafeContains("ICE Lots") ?
|
||||
new SecurityAuxiliaryData(1) :
|
||||
_auxiliary_data.TryGetValue(security.Symbol, out var d) ? d : (SecurityAuxiliaryData?)null;
|
||||
}
|
||||
|
||||
static class SecurityAuxiliaryDataModelExtensions {
|
||||
public static uint get_display_quantity(this ISecurityAuxiliaryDataModel model, SecurityImmutable? security, uint actual_qty) =>
|
||||
actual_qty.safe_divide(model.get_display_quantity_divisor(security));
|
||||
|
||||
public static uint get_display_quantity_divisor(this ISecurityAuxiliaryDataModel model, SecurityImmutable? security) =>
|
||||
security.HasValue ? model.get_auxiliary_data(security.Value)?.quantity_divisor ?? 1 : 1;
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,9 @@ namespace mbts {
|
||||
public static bool IsNonZero(this double value) =>
|
||||
value != 0 && !double.IsNaN(value) && !double.IsInfinity(value);
|
||||
|
||||
public static bool IsRealNonZero(this double value) =>
|
||||
value > 0 && value != double.PositiveInfinity;
|
||||
|
||||
public static double CoerceNaN(double value) => CoerceNaN(value, 0.0);
|
||||
public static double CoerceNaN(double value, double if_nan) => double.IsNaN(value) ? if_nan : value;
|
||||
public static double CoerceNaNOrInfinity(double value) => CoerceNaNOrInfinity(value, 0.0);
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
namespace bts.proto {
|
||||
[ProtoContract]
|
||||
readonly struct ToggleInt32Proto : IEquatable<ToggleInt32Proto> {
|
||||
public ToggleInt32Proto(int value, bool is_enabled) {
|
||||
this.value = value;
|
||||
this.is_enabled = is_enabled;
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public int value { get; }
|
||||
[ProtoMember(2)]
|
||||
public bool is_enabled { get; }
|
||||
|
||||
public bool Equals(ToggleInt32Proto other) =>
|
||||
value == other.value && is_enabled == other.is_enabled;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is ToggleInt32Proto other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
(value % (1 << 31)) | (is_enabled ? 1 << 31 : 0);
|
||||
|
||||
public static bool operator ==(ToggleInt32Proto a, ToggleInt32Proto b) => a.Equals(b);
|
||||
public static bool operator !=(ToggleInt32Proto a, ToggleInt32Proto b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static class ToggleInt32ProtoExtensions {
|
||||
public static int? as_nullable(this ToggleInt32Proto toggle) =>
|
||||
toggle.is_enabled ? toggle.value : (int?)null;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,39 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
static class RegexFilterUtils {
|
||||
public static Regex build_regex(string str) {
|
||||
if (str == null)
|
||||
return null;
|
||||
str = str.Trim();
|
||||
if (str == string.Empty)
|
||||
return null;
|
||||
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
}
|
||||
}
|
||||
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
|
||||
string _text = string.Empty;
|
||||
Regex _regex;
|
||||
|
||||
public RegexFilterVM(Func<TRow, string> get_row_val, string prop_name = null) : base(get_row_val, prop_name) { }
|
||||
|
||||
public string text {
|
||||
get => _text;
|
||||
set => _regex = RegexFilterUtils.build_regex(_text = (value ?? string.Empty));
|
||||
}
|
||||
|
||||
public override string get_state() => _text ?? string.Empty;
|
||||
|
||||
protected override bool _get_active() => _regex != null;
|
||||
protected override bool _filter(string value) =>
|
||||
_regex != null && (
|
||||
_regex.IsMatch(value) ||
|
||||
(!string.IsNullOrEmpty(value) && _regex.IsMatch(value.Replace(" ", ""))));
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
protected override void _load_state_internal(string state) => text = state;
|
||||
protected override void _clear() {
|
||||
_close();
|
||||
_text = string.Empty;
|
||||
_regex = null;
|
||||
RaisePropertyChanged(nameof(text));
|
||||
_raise_filter_changed();
|
||||
}
|
||||
}
|
||||
namespace mbts.spark.filters {
|
||||
static class RegexFilterUtils {
|
||||
public static Regex build_regex(string str) {
|
||||
if (str == null)
|
||||
return null;
|
||||
str = str.Trim();
|
||||
if (str == string.Empty)
|
||||
return null;
|
||||
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
}
|
||||
}
|
||||
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
|
||||
string _text = string.Empty;
|
||||
Regex _regex;
|
||||
|
||||
public RegexFilterVM(Func<TRow, string> get_row_val, string prop_name = null) : base(get_row_val, prop_name) { }
|
||||
|
||||
public string text {
|
||||
get => _text;
|
||||
set => _regex = RegexFilterUtils.build_regex(_text = (value ?? string.Empty));
|
||||
}
|
||||
|
||||
public override string get_state() => _text ?? string.Empty;
|
||||
|
||||
protected override bool _get_active() => _regex != null;
|
||||
protected override bool _filter(string value) => _regex != null && _regex.IsMatch(value);
|
||||
protected override void _load_state_internal(string state) => text = state;
|
||||
protected override void _clear() {
|
||||
_close();
|
||||
_text = string.Empty;
|
||||
_regex = null;
|
||||
RaisePropertyChanged(nameof(text));
|
||||
_raise_filter_changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
[ValueConversion(typeof(double), typeof(string))]
|
||||
class DoubleDivisorConverter : IValueConverter {
|
||||
public double Divisor { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
value is double d ? d.safe_divide(Divisor) :
|
||||
value is int i ? i.safe_divide(Divisor) :
|
||||
value is uint u ? u.safe_divide(Divisor) :
|
||||
double.NaN;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
value is double d ? d * Divisor :
|
||||
value is int i ? (double)i * Divisor :
|
||||
value is uint u ? (double)u * Divisor :
|
||||
double.NaN;
|
||||
}
|
||||
|
||||
class DynamicDoubleDivisorConverter : IMultiValueConverter {
|
||||
public static DynamicDoubleDivisorConverter Instance { get; } = new DynamicDoubleDivisorConverter();
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (ConvertUtils.try_convert_args(values, out double value, out double div)) {
|
||||
return value.safe_divide(div);
|
||||
}
|
||||
return new ValidationResult(false, "unable to convert values to doubles.");
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
@@ -20,18 +19,4 @@ namespace mbts.ui.converters {
|
||||
value is uint u ? (double)u / Multiplier :
|
||||
double.NaN;
|
||||
}
|
||||
|
||||
class DynamicDoubleMultiplierConverter : IMultiValueConverter {
|
||||
public static DynamicDoubleMultiplierConverter Instance { get; } = new DynamicDoubleMultiplierConverter();
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (ConvertUtils.try_convert_args(values, out double value, out double mult)) {
|
||||
return value * mult;
|
||||
}
|
||||
return new ValidationResult(false, "unable to convert values to doubles.");
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ namespace mbts.ui.dpi {
|
||||
}
|
||||
|
||||
public static void DrawHorizontalLine(this DrawingContext drawing_context, System.Windows.Media.Pen pen, Point start, double length, bool snap_to_device_pixels) {
|
||||
if (pen == null || !length.is_real_positive()) {
|
||||
if (pen == null || !length.IsRealNonZero()) {
|
||||
// no need to flip out and throw an exception
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Markup;
|
||||
|
||||
@@ -20,7 +19,7 @@ namespace mbts.ui {
|
||||
public double RoundingUnit { get; set; }
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) =>
|
||||
RoundingUnit.is_real_non_zero() ? Math.Round(Value * RoundingUnit) / RoundingUnit : Value;
|
||||
RoundingUnit.IsRealNonZero() ? Math.Round(Value * RoundingUnit) / RoundingUnit : Value;
|
||||
}
|
||||
|
||||
internal class DoubleStringTypeConverter : TypeConverter
|
||||
|
||||
@@ -45,16 +45,15 @@ namespace mbts.spark {
|
||||
readonly SecurityImmutable _security;
|
||||
readonly MBOSecurityQueryImmutable _query;
|
||||
readonly Dictionary<(string,bool), FormattedText> _cache;
|
||||
readonly Func<int, string> _fmt_mbo_qty;
|
||||
int _max_order_size = 1;
|
||||
double _height = 0;
|
||||
double _number_width = 0;
|
||||
|
||||
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query, Func<int, string> fmt_mbo_qty) {
|
||||
|
||||
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query) {
|
||||
_mbo = mbo;
|
||||
_security = security;
|
||||
_query = query;
|
||||
_fmt_mbo_qty = fmt_mbo_qty ?? new Func<int, string>(v => v.ToString());
|
||||
|
||||
// prime the cache with numbers on the left hand side
|
||||
_cache =
|
||||
@@ -111,7 +110,7 @@ namespace mbts.spark {
|
||||
|
||||
pdp.row_numbers = p.orders.Select((o, i) => format_text((i+1).ToString(), ismine(o))).ToList();
|
||||
pdp.numbers_width = _number_width = Math.Max(_number_width, (int)pdp.row_numbers.Max(r => r.Width) + text_margin);
|
||||
pdp.row_labels = p.orders.Select(o => format_text(_fmt_mbo_qty(o.Size) + (ismine(o) ? "*" : ""), ismine(o))).ToList();
|
||||
pdp.row_labels = p.orders.Select(o => format_text(o.Size.ToString() + (ismine(o) ? "*" : ""), ismine(o))).ToList();
|
||||
var max_label_width = pdp.row_labels.Max(l => l.Width) + text_margin;
|
||||
|
||||
var available_width = pdp.width - pdp.numbers_width - max_label_width;
|
||||
|
||||
@@ -7,53 +7,26 @@ using System.Windows.Input;
|
||||
namespace mbts.spark {
|
||||
public class CalculatorLogic : DisposableViewModelBase {
|
||||
readonly UserProperty<uint?> _value;
|
||||
readonly OptionalDisplayQuantityMultiplier _display_translator = new OptionalDisplayQuantityMultiplier();
|
||||
IDisposable _prop_changes_echo;
|
||||
int[] _buttons;
|
||||
|
||||
public CalculatorLogic(uint? initial_size) {
|
||||
public CalculatorLogic(uint? initial_size) =>
|
||||
_value = new UserProperty<uint?>(backing(nameof(value), initial_size));
|
||||
_echo_props_changed();
|
||||
}
|
||||
|
||||
public CalculatorLogic(uint? initial_size, IObservable<int[]> buttons_source) : this(initial_size) =>
|
||||
_dispose = buttons_source.Subscribe(newval => buttons = newval);
|
||||
|
||||
public uint display_divisor {
|
||||
get => _display_translator.conversion_data.value;
|
||||
set => _display_translator.conversion_data = new QuantityConversionData(value, QuantityConversionData.OpType.DivideActual);
|
||||
}
|
||||
public uint? value {
|
||||
get => _display_translator.actual_value;
|
||||
set => _display_translator.actual_value = value;
|
||||
}
|
||||
public uint? display_value {
|
||||
get => _display_translator.display_value;
|
||||
set => _display_translator.display_value = value;
|
||||
}
|
||||
public uint? value { get => _value.Value; set => _value.Value = value; }
|
||||
public bool value_setter_called => _value.setter_called;
|
||||
public IObservable<uint?> value_observable => _value.Select(vals => vals.newval);
|
||||
public int[] buttons { get => _buttons; set => Set(ref _buttons, value); }
|
||||
public ICommand click => new DelegateCommand<uint>(handle_calculator_click);
|
||||
public ICommand set_display_value_command => new DelegateCommand<uint>(v => _display_translator.display_value = v);
|
||||
public ICommand set_actual_value_command => new DelegateCommand<uint>(v => _display_translator.actual_value = v);
|
||||
public ICommand clear => new DelegateCommand(() => _display_translator.actual_value = null);
|
||||
public ICommand set_value_command => new DelegateCommand<uint>(v => _value.Value = v);
|
||||
public ICommand clear => new DelegateCommand(() => _value.Value = null);
|
||||
|
||||
public IObservable<uint?> observe_actual_value() =>
|
||||
_display_translator.subscribe_actual_value();
|
||||
public IObservable<uint?> observe_current(bool start_immediately = false) =>
|
||||
_value.observe_current(start_immediately);
|
||||
|
||||
// Don't use a default value for 'is_actual_value.'
|
||||
// Quietly multiplying (or not multiplying) the size input is dangerous -
|
||||
// force the caller to be specific.
|
||||
public void bypass_value_setter(uint value, bool is_actual_value) {
|
||||
Disposable.dispose(ref _prop_changes_echo);
|
||||
if (is_actual_value) {
|
||||
this.value = value;
|
||||
} else {
|
||||
display_value = value;
|
||||
}
|
||||
if (_prop_changes_echo != null)
|
||||
_echo_props_changed();
|
||||
}
|
||||
public void bypass_value_setter(uint value) => _value.bypass_setter(value);
|
||||
|
||||
public bool validate() {
|
||||
if (!value.HasValue || value.Value == 0) {
|
||||
@@ -65,34 +38,25 @@ namespace mbts.spark {
|
||||
public void flash_error() =>
|
||||
flash_error(nameof(value)).ignore();
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
Disposable.dispose(ref _prop_changes_echo);
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
void _echo_props_changed() {
|
||||
// dispose the echo to avoid raising prop changed events more than once
|
||||
_prop_changes_echo?.Dispose();
|
||||
_prop_changes_echo = _display_translator.connect_owner(this, nameof(display_value), nameof(value));
|
||||
}
|
||||
|
||||
void handle_calculator_click(uint value) {
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) || value == 0) {
|
||||
_display_translator.actual_value = value;
|
||||
_value.Value = value;
|
||||
} else if (KeyboardExtensions.IsCtrlDown()) {
|
||||
if (!_display_translator.actual_value.HasValue) {
|
||||
_display_translator.actual_value = 0;
|
||||
} else if (value > _display_translator.actual_value) {
|
||||
_display_translator.actual_value = 0;
|
||||
} else {
|
||||
_display_translator.actual_value -= value;
|
||||
if (!_value.Value.HasValue)
|
||||
_value.Value = 0;
|
||||
else {
|
||||
if (value > _value.Value)
|
||||
_value.Value = 0;
|
||||
else
|
||||
_value.Value -= value;
|
||||
}
|
||||
} else {
|
||||
if (!_display_translator.actual_value.HasValue)
|
||||
_display_translator.actual_value = value;
|
||||
}
|
||||
else {
|
||||
if (!_value.Value.HasValue)
|
||||
_value.Value = value;
|
||||
else
|
||||
_display_translator.actual_value += value;
|
||||
_value.Value += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
using bts.utils;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.ui {
|
||||
interface IDisplayTranslator<T> {
|
||||
T display_value { get; set; }
|
||||
T actual_value { get; set; }
|
||||
event Action value_changed;
|
||||
}
|
||||
|
||||
readonly struct QuantityConversionData : IEquatable<QuantityConversionData> {
|
||||
public QuantityConversionData(uint value, OpType op) {
|
||||
this.value = value;
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public enum OpType {
|
||||
None,
|
||||
MultiplyActual,
|
||||
DivideActual
|
||||
}
|
||||
|
||||
public uint value { get; }
|
||||
public OpType op { get; }
|
||||
|
||||
public bool Equals(QuantityConversionData other) =>
|
||||
value == other.value &&
|
||||
op == other.op;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is QuantityConversionData other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase(value.GetHashCode(), op.GetHashCode());
|
||||
|
||||
public static bool operator ==(QuantityConversionData x, QuantityConversionData y) => x.Equals(y);
|
||||
public static bool operator !=(QuantityConversionData x, QuantityConversionData y) => !x.Equals(y);
|
||||
}
|
||||
|
||||
class DisplayQuantityMultiplier : IDisplayTranslator<uint> {
|
||||
uint _actual_value;
|
||||
QuantityConversionData _conversion_data;
|
||||
|
||||
public event Action value_changed;
|
||||
|
||||
public uint actual_value {
|
||||
get => _actual_value;
|
||||
set {
|
||||
if (_actual_value != value) {
|
||||
_actual_value = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint display_value {
|
||||
get => _conversion_data.to_display(_actual_value);
|
||||
set => actual_value = _conversion_data.from_display(value);
|
||||
}
|
||||
|
||||
public QuantityConversionData conversion_data {
|
||||
get => _conversion_data;
|
||||
set {
|
||||
if (_conversion_data != value) {
|
||||
_conversion_data = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OptionalDisplayQuantityMultiplier : IDisplayTranslator<uint?> {
|
||||
uint? _actual_value;
|
||||
QuantityConversionData _conversion_data;
|
||||
|
||||
public event Action value_changed;
|
||||
|
||||
public uint? actual_value {
|
||||
get => _actual_value;
|
||||
set {
|
||||
if (_actual_value != value) {
|
||||
_actual_value = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint? display_value {
|
||||
get => _actual_value.HasValue ? _conversion_data.to_display(_actual_value.Value) : _actual_value;
|
||||
set => actual_value = value.HasValue ? _conversion_data.from_display(value.Value) : value;
|
||||
}
|
||||
|
||||
public QuantityConversionData conversion_data {
|
||||
get => _conversion_data;
|
||||
set {
|
||||
if (_conversion_data != value) {
|
||||
_conversion_data = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class DisplayTranslatorExtensions {
|
||||
public static IDisposable connect_owner<T>(this IDisplayTranslator<T> display_translator, IRaiseNotifyPropertyChanged owner, params string[] properties) {
|
||||
if (display_translator is null)
|
||||
throw new ArgumentNullException(nameof(display_translator));
|
||||
if (owner is null)
|
||||
throw new ArgumentNullException(nameof(owner));
|
||||
display_translator.value_changed += on_value_changed;
|
||||
return Disposable.Create(() => display_translator.value_changed -= on_value_changed);
|
||||
void on_value_changed() {
|
||||
for (int i = 0; i < properties.Length; i++)
|
||||
owner.raise_notify_property_changed(properties[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public static IObservable<T> subscribe_actual_value<T>(this IDisplayTranslator<T> display_translator) =>
|
||||
subscribe_actual_value(display_translator, EqualityComparer<T>.Default);
|
||||
|
||||
public static IObservable<T> subscribe_actual_value<T>(this IDisplayTranslator<T> display_translator, IEqualityComparer<T> comparer) {
|
||||
if (comparer is null)
|
||||
throw new ArgumentNullException(nameof(comparer));
|
||||
return Observable.Create<T>(o => {
|
||||
var curr = display_translator.actual_value;
|
||||
o.OnNext(curr);
|
||||
display_translator.value_changed += on_value_changed;
|
||||
return Disposable.Create(() => display_translator.value_changed -= on_value_changed);
|
||||
void on_value_changed() {
|
||||
var next = display_translator.actual_value;
|
||||
if (!comparer.Equals(curr, next))
|
||||
o.OnNext(curr = next);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static uint to_display(this QuantityConversionData quantity_conversion_data, uint actual_value) {
|
||||
switch (quantity_conversion_data.op) {
|
||||
case QuantityConversionData.OpType.DivideActual: return actual_value.safe_divide(quantity_conversion_data.value);
|
||||
case QuantityConversionData.OpType.MultiplyActual: return actual_value * quantity_conversion_data.value;
|
||||
default: return actual_value;
|
||||
}
|
||||
}
|
||||
|
||||
public static int to_display(this QuantityConversionData quantity_conversion_data, int actual_value) {
|
||||
switch (quantity_conversion_data.op) {
|
||||
case QuantityConversionData.OpType.DivideActual: return (int)(actual_value.safe_divide(quantity_conversion_data.value));
|
||||
case QuantityConversionData.OpType.MultiplyActual: return (int)(actual_value * quantity_conversion_data.value);
|
||||
default: return actual_value;
|
||||
}
|
||||
}
|
||||
|
||||
public static double to_display(this QuantityConversionData quantity_conversion_data, double actual_value) {
|
||||
switch (quantity_conversion_data.op) {
|
||||
case QuantityConversionData.OpType.DivideActual: return actual_value.safe_divide(quantity_conversion_data.value);
|
||||
case QuantityConversionData.OpType.MultiplyActual: return actual_value * quantity_conversion_data.value;
|
||||
default: return actual_value;
|
||||
}
|
||||
}
|
||||
|
||||
public static uint from_display(this QuantityConversionData quantity_conversion_data, uint display_value) {
|
||||
switch (quantity_conversion_data.op) {
|
||||
case QuantityConversionData.OpType.DivideActual: return display_value * quantity_conversion_data.value;
|
||||
case QuantityConversionData.OpType.MultiplyActual: return display_value.safe_divide(quantity_conversion_data.value);
|
||||
default: return display_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,9 +109,6 @@ namespace mbts.ui.spark {
|
||||
/// These are private members, so any code-readability cost is confined to a limited scope.
|
||||
delegate void _SizeFormatter(ref uint merged, ref uint implied, ref StringBuilder sb);
|
||||
delegate void _SizeFormatterNPS(ref NativePriceSize nps, ref StringBuilder sb);
|
||||
|
||||
public static Func<uint, string> get_value_formatter(uint divisor) =>
|
||||
value => (value / divisor).ToString();
|
||||
|
||||
public static Func<NativePriceSize, string> get_formatter_nps(MarketSizeDisplay size_display) {
|
||||
_SizeFormatterNPS implied_absent = _get_formatter_nps(size_display.normal);
|
||||
@@ -123,11 +120,6 @@ namespace mbts.ui.spark {
|
||||
};
|
||||
}
|
||||
|
||||
public static Func<NativePriceSize, string> get_formatter_nps(MarketSizeDisplay size_display, uint display_divisor) {
|
||||
var raw_fmt_size = get_formatter(size_display);
|
||||
return nps => raw_fmt_size((nps.implied_size + nps.direct_size) / display_divisor, nps.implied_size / display_divisor);
|
||||
}
|
||||
|
||||
public static SizeFormatter get_formatter(MarketSizeDisplay size_display) {
|
||||
_SizeFormatter implied_absent = _get_formatter(size_display.normal);
|
||||
_SizeFormatter implied_present = _get_formatter(size_display.implied_present ?? size_display.normal);
|
||||
@@ -289,4 +281,4 @@ namespace mbts.ui.spark {
|
||||
public void refresh() => _display.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,18 +39,13 @@ namespace bts.api {
|
||||
|
||||
static string _format(SecurityImmutable? security) {
|
||||
switch (security?.ContractCase) {
|
||||
case ContractOneofCase.Future:
|
||||
if(security?.HasIce == true)
|
||||
return _format_ICE(security);
|
||||
return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
|
||||
case ContractOneofCase.Future: return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
|
||||
case ContractOneofCase.Spread:
|
||||
if (security?.HasGlbx == true) {
|
||||
// In case we ever bring it back, pretty printed spread names can be found here:
|
||||
// GlbxSecurityFormatter.FormatSpread(security, out string result)
|
||||
return security?.Glbx.SecurityDesc;
|
||||
}
|
||||
else if(security?.HasIce == true)
|
||||
return _format_ICE(security);
|
||||
return string.Join("", _format_future_legs(SecurityHelper.get_flat_future_legs(security?.Spread.Legs)));
|
||||
case ContractOneofCase.Synthetic: return security?.Synthetic.Name;
|
||||
default: return "Unknown-security";
|
||||
@@ -84,28 +79,5 @@ namespace bts.api {
|
||||
default: return $"{fl.quantity:+0;-0}{sym_exp_name}";
|
||||
}
|
||||
}
|
||||
|
||||
static string _format_ICE (SecurityImmutable? security){
|
||||
string symbol = security?.Symbol,
|
||||
strip = security?.Ice.StripName,
|
||||
md_desc = security?.Ice.MarketDescription,
|
||||
type_append = "";
|
||||
|
||||
if(md_desc.Contains("25k ICE Lots")){
|
||||
if(md_desc.Contains("TAS"))
|
||||
type_append = "TAS";
|
||||
}
|
||||
else if(md_desc.Contains("ICE Lots")){
|
||||
if(md_desc.Contains("TAS"))
|
||||
type_append = "ICE Lots TAS";
|
||||
else
|
||||
type_append = "ICE Lots";
|
||||
}
|
||||
else if(md_desc.Contains("TAS"))
|
||||
type_append = "TAS";
|
||||
type_append = string.IsNullOrEmpty(type_append) ?
|
||||
" " : $" {type_append} ";
|
||||
return $"{symbol}{type_append}{strip}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,28 +110,6 @@ namespace bts.utils {
|
||||
return epsilon_equals(with_int_value, d);
|
||||
}
|
||||
|
||||
public static bool is_real(this double value) {
|
||||
switch (value) {
|
||||
case double.PositiveInfinity:
|
||||
case double.NegativeInfinity:
|
||||
case double.NaN:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool is_real_positive(this double value) =>
|
||||
value > 0 && value != double.PositiveInfinity;
|
||||
|
||||
public static bool is_real_negative(this double value) =>
|
||||
value < 0 && value != double.NegativeInfinity;
|
||||
|
||||
public static bool is_real_non_zero(this double value) =>
|
||||
value > 0 ?
|
||||
!double.IsPositiveInfinity(value) :
|
||||
value < 0 && !double.IsNegativeInfinity(value);
|
||||
|
||||
public static double factor_out(double value, params double[] factors) {
|
||||
foreach (double f in factors)
|
||||
value = factor_out(value, f, out int _);
|
||||
@@ -151,27 +129,6 @@ namespace bts.utils {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int safe_divide(this int value, int divisor) =>
|
||||
divisor == 0 ? value : value / divisor;
|
||||
|
||||
public static int safe_divide(this int value, uint divisor) =>
|
||||
divisor == 0 ? value : (int)(value / divisor);
|
||||
|
||||
public static double safe_divide(this int value, double divisor) =>
|
||||
divisor.is_real_non_zero() ? value / divisor : value;
|
||||
|
||||
public static uint safe_divide(this uint value, uint divisor) =>
|
||||
divisor == 0 ? value : value / divisor;
|
||||
|
||||
public static double safe_divide(this uint value, double divisor) =>
|
||||
divisor.is_real_non_zero() ? value / divisor : value;
|
||||
|
||||
public static ulong safe_divide(this ulong value, uint divisor) =>
|
||||
divisor == 0 ? value : value / divisor;
|
||||
|
||||
public static double safe_divide(this double value, double divisor) =>
|
||||
divisor.is_real_non_zero() && value.is_real_non_zero() ? value / divisor : value;
|
||||
|
||||
/// <summary>
|
||||
/// Adds <see cref="mod"/> to the modulos result if negative.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,9 +14,6 @@ namespace bts.utils {
|
||||
}
|
||||
|
||||
public static class FormattingDelegates {
|
||||
public static Func<T, string> default_string<T>() =>
|
||||
v => v.ToString();
|
||||
|
||||
public static class Price {
|
||||
public static readonly Func<double, string> Value =
|
||||
p => double.IsNaN(p) ? "" : p.ToString("#,0.00");
|
||||
|
||||
@@ -13,8 +13,5 @@ namespace bts.utils {
|
||||
[Pure]
|
||||
public static bool IsNullOrEmpty(this string str) =>
|
||||
string.IsNullOrEmpty(str);
|
||||
|
||||
public static bool SafeContains(this string str, string value) =>
|
||||
str != null && str.Contains(value);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<server name="default">
|
||||
<ip>63.247.113.163</ip>
|
||||
<port>3000</port>
|
||||
<userName>katas2f-ps</userName>
|
||||
<userName>bluetrd-ps</userName>
|
||||
<password>Starts123</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
@@ -24,8 +24,8 @@
|
||||
<server name="endex">
|
||||
<ip>63.247.113.77</ip>
|
||||
<port>3000</port>
|
||||
<userName>katas2f-ps</userName>
|
||||
<password>Starts123</password>
|
||||
<userName>Value from ICE Support</userName>
|
||||
<password>Value from ICE Support</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
<localNetworkInterface></localNetworkInterface>
|
||||
|
||||
@@ -105,35 +105,14 @@ message BasePositionBatch {
|
||||
google.protobuf.Timestamp current_as_of = 10; // UTC
|
||||
}
|
||||
|
||||
// security overrides /////////////////////////////////////
|
||||
message CppOverride{
|
||||
double value = 1;
|
||||
}
|
||||
|
||||
message SecurityParameters{
|
||||
uint32 security_key = 1;
|
||||
CppOverride cpp_override = 2;
|
||||
// other overrides
|
||||
}
|
||||
|
||||
message SecurityParametersBatchOp{
|
||||
repeated SecurityParameters op_params = 1;
|
||||
}
|
||||
|
||||
message SecurityParametersBatchOpResponse {
|
||||
bool ok = 1;
|
||||
string msg = 2;
|
||||
}
|
||||
|
||||
service PositionService {
|
||||
option (bts.api.service_description) = "Provides Position and PNL, per security, per account";
|
||||
|
||||
rpc create_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
|
||||
rpc update_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
|
||||
rpc remove_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
|
||||
rpc set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
|
||||
rpc update_security_parameters (SecurityParametersBatchOp) returns (SecurityParametersBatchOpResponse);
|
||||
|
||||
rpc set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
|
||||
|
||||
// subscription
|
||||
rpc manual_fills(bts.api.VoidMessage) returns (stream ManualFill);
|
||||
rpc position_data(bts.api.VoidMessage) returns (stream PositionUnit);
|
||||
|
||||
@@ -1,130 +1,136 @@
|
||||
#
|
||||
# Common Market data options
|
||||
#
|
||||
|
||||
MarketDataConfig {
|
||||
rule: "DeploymentType.type == 'PROD'"
|
||||
value {
|
||||
use_sim: false
|
||||
}
|
||||
}
|
||||
|
||||
PublisherOptions {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
disable_heartbeat: true
|
||||
}
|
||||
}
|
||||
|
||||
PlatformMap {
|
||||
value {
|
||||
glbx_platform: "PLATFORM_MDP3ONIXS"
|
||||
ice_platform: "PLATFORM_ICEONIXS"
|
||||
}
|
||||
}
|
||||
|
||||
PublisherNamespace {
|
||||
value {
|
||||
shared_memory_namespace: @System.os_user
|
||||
}
|
||||
}
|
||||
|
||||
CMEOptions {
|
||||
value {
|
||||
feed_engine {
|
||||
name: "mdp3feed"
|
||||
# udp_socket_buffer_size: 425984
|
||||
# channels_per_thread: 1
|
||||
spin_time: 0
|
||||
}
|
||||
channel {
|
||||
id: "*"
|
||||
feed_engine: "mdp3feed"
|
||||
use_tcp_replay: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#to make this work in dev clone the operations repo
|
||||
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
|
||||
#modify the easyTunnelProxy.properties file to replace
|
||||
#tunnel.multicast.interface=10.96.18.37 with your local ip
|
||||
#run easyTunnelProxy.bat BRENT-FI for Brent Full implied
|
||||
#you should see ice data
|
||||
|
||||
ICEOptions {
|
||||
value {
|
||||
channel {
|
||||
name: "oil" #these names are arbitrary and are for debugging
|
||||
market_type: 3
|
||||
uds_market_type: 3
|
||||
market_type: 4 #ICE IPE Gas Oil Futures
|
||||
uds_market_type: 4
|
||||
market_type: 5 #ICE Futures Brent, IPE Brent Futures
|
||||
uds_market_type: 5
|
||||
market_type: 9 #ICE Futures WTI, ICE WTI Crude Futures
|
||||
uds_market_type: 9
|
||||
market_type: 13 #ICE Heating Oil Futures
|
||||
uds_market_type: 13
|
||||
market_type: 15 #ICE NYH (RBOB) Gasoline Futures
|
||||
uds_market_type: 15
|
||||
##market_type: 21 #ICE Futures US Agricultures, Coffee C
|
||||
##uds_market_type: 21
|
||||
##market_type: 23 #ICE Futures US Agricultures, FCOJ A
|
||||
##uds_market_type: 23
|
||||
|
||||
##market_type: 26 #ICE Futures US Financials DX
|
||||
##uds_market_type: 26
|
||||
|
||||
uds_market_type: 11 #ICE WTI-Brent spreads
|
||||
uds_market_type: 14 #ICE Heating Oil/WTI Futures Crack
|
||||
uds_market_type: 16 #ICE NYH (RBOB) Gasoline/WTI Futures Crack
|
||||
uds_market_type: 58 #ICE Futures UI Heating Oil / Brent
|
||||
uds_market_type: 59 #ICE NYH (RBOB) Gasoline/Brent Futures Crack
|
||||
uds_market_type: 66 #ICE NYH (RBOB) Gasoline/Heating Oil Spread
|
||||
uds_market_type: 70 #ICE Heating Oil/LS Gasoil Futures Spread
|
||||
uds_market_type: 71 #ICE LS Gasoil/Brent Futures Crack
|
||||
uds_market_type: 72 #ICE NYH (RBOB) Gasoline/LS Gasoil Futures Spread
|
||||
|
||||
#onixs_log_mode: Debug
|
||||
}
|
||||
# channel {
|
||||
# name: "juice"
|
||||
# market_type: 26 #ICE Futures US Agricultures, FCOJ A
|
||||
# }
|
||||
ice_config_file: "ConnectivityConfiguration.APITest.xml"
|
||||
#ice_config_file: "ConnectivityConfiguration.PerfTest.xml"
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
delay_seconds: 0
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
value {
|
||||
delay_seconds: 120
|
||||
standard {
|
||||
start: "16:30"
|
||||
end: "16:40"
|
||||
}
|
||||
sunday {
|
||||
start: "14:00"
|
||||
end: "14:10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayFactorOverride {
|
||||
value {
|
||||
override {
|
||||
symbol: "SOM"
|
||||
exchange: "GLBX"
|
||||
type: "SPREAD"
|
||||
price_multiplier: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
#
|
||||
# Common Market data options
|
||||
#
|
||||
|
||||
MarketDataConfig {
|
||||
rule: "DeploymentType.type == 'PROD'"
|
||||
value {
|
||||
use_sim: false
|
||||
}
|
||||
}
|
||||
|
||||
PublisherOptions {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
disable_heartbeat: true
|
||||
}
|
||||
}
|
||||
|
||||
PlatformMap {
|
||||
value {
|
||||
glbx_platform: "PLATFORM_MDP3ONIXS"
|
||||
ice_platform: "PLATFORM_ICEONIXS"
|
||||
}
|
||||
}
|
||||
|
||||
PublisherNamespace {
|
||||
value {
|
||||
shared_memory_namespace: @System.os_user
|
||||
}
|
||||
}
|
||||
|
||||
CMEOptions {
|
||||
value {
|
||||
feed_engine {
|
||||
name: "mdp3feed"
|
||||
# udp_socket_buffer_size: 425984
|
||||
# channels_per_thread: 1
|
||||
spin_time: 0
|
||||
}
|
||||
channel {
|
||||
id: "*"
|
||||
feed_engine: "mdp3feed"
|
||||
use_tcp_replay: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ICEPublisherOptions {
|
||||
value {
|
||||
network_interface: "eth1"
|
||||
}
|
||||
}
|
||||
|
||||
#to make this work in dev clone the operations repo
|
||||
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
|
||||
#modify the easyTunnelProxy.properties file to replace
|
||||
#tunnel.multicast.interface=10.96.18.37 with your local ip
|
||||
#run easyTunnelProxy.bat BRENT-FI for Brent Full implied
|
||||
#you should see ice data
|
||||
|
||||
ICEOptions {
|
||||
value {
|
||||
channel {
|
||||
name: "oil" #these names are arbitrary and are for debugging
|
||||
market_type: 3
|
||||
uds_market_type: 3
|
||||
market_type: 4 #ICE IPE Gas Oil Futures
|
||||
uds_market_type: 4
|
||||
market_type: 5 #ICE Futures Brent, IPE Brent Futures
|
||||
uds_market_type: 5
|
||||
market_type: 9 #ICE Futures WTI, ICE WTI Crude Futures
|
||||
uds_market_type: 9
|
||||
market_type: 13 #ICE Heating Oil Futures
|
||||
uds_market_type: 13
|
||||
market_type: 15 #ICE NYH (RBOB) Gasoline Futures
|
||||
uds_market_type: 15
|
||||
##market_type: 21 #ICE Futures US Agricultures, Coffee C
|
||||
##uds_market_type: 21
|
||||
##market_type: 23 #ICE Futures US Agricultures, FCOJ A
|
||||
##uds_market_type: 23
|
||||
|
||||
##market_type: 26 #ICE Futures US Financials DX
|
||||
##uds_market_type: 26
|
||||
|
||||
uds_market_type: 11 #ICE WTI-Brent spreads
|
||||
uds_market_type: 14 #ICE Heating Oil/WTI Futures Crack
|
||||
uds_market_type: 16 #ICE NYH (RBOB) Gasoline/WTI Futures Crack
|
||||
uds_market_type: 58 #ICE Futures UI Heating Oil / Brent
|
||||
uds_market_type: 59 #ICE NYH (RBOB) Gasoline/Brent Futures Crack
|
||||
uds_market_type: 66 #ICE NYH (RBOB) Gasoline/Heating Oil Spread
|
||||
uds_market_type: 70 #ICE Heating Oil/LS Gasoil Futures Spread
|
||||
uds_market_type: 71 #ICE LS Gasoil/Brent Futures Crack
|
||||
uds_market_type: 72 #ICE NYH (RBOB) Gasoline/LS Gasoil Futures Spread
|
||||
|
||||
#onixs_log_mode: Debug
|
||||
}
|
||||
# channel {
|
||||
# name: "juice"
|
||||
# market_type: 26 #ICE Futures US Agricultures, FCOJ A
|
||||
# }
|
||||
ice_config_file: "ConnectivityConfiguration.APITest.xml"
|
||||
#ice_config_file: "ConnectivityConfiguration.PerfTest.xml"
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
delay_seconds: 0
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
value {
|
||||
delay_seconds: 120
|
||||
standard {
|
||||
start: "16:30"
|
||||
end: "16:40"
|
||||
}
|
||||
sunday {
|
||||
start: "14:00"
|
||||
end: "14:10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayFactorOverride {
|
||||
value {
|
||||
override {
|
||||
symbol: "SOM"
|
||||
exchange: "GLBX"
|
||||
type: "SPREAD"
|
||||
price_multiplier: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,13 +168,4 @@ message OffsetAttachUIOptions {
|
||||
|
||||
message ManualFillsOptions {
|
||||
optional bool modal_transactions = 1 [default = true];
|
||||
}
|
||||
|
||||
message SymbolUIConfigurationItem {
|
||||
optional string symbol = 1 [default = ""];
|
||||
optional uint32 display_quantity_divisor = 2 [default = 1];
|
||||
}
|
||||
|
||||
message SymbolUIConfiguration {
|
||||
repeated SymbolUIConfigurationItem items = 1;
|
||||
}
|
||||
@@ -59,10 +59,6 @@ message EnsureOnScreenWithWorkAreaRect {
|
||||
optional bool enabled = 1 [default = false];
|
||||
}
|
||||
|
||||
message CustomFormatIceSecurities{
|
||||
optional bool enabled = 1 [default = true];
|
||||
}
|
||||
|
||||
message IgnoreNansInDayPnL {
|
||||
optional bool enabled = 1 [default = true];
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
|
||||
me->sec = serv.sec_db.resolve(SecurityKey(def.security_key()));
|
||||
me->previous_working_position = Quantity(0);
|
||||
|
||||
auto &native_state = get_native_state(me->state);
|
||||
auto const &native_state = get_native_state(me->state);
|
||||
switch (me->state.status()) {
|
||||
|
||||
case ORDER_STATUS_WORKING:
|
||||
@@ -202,8 +202,6 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
|
||||
}
|
||||
// if we made it this far we are good.
|
||||
me->routing_record = rq.routing_record;
|
||||
*native_state.mutable_routing_record() = rq.routing_record;
|
||||
|
||||
if (me->state.status() != ORDER_STATUS_PENDING_WORKING)
|
||||
me->exchange_order_id = native_state.exchange_order_id();
|
||||
init_fix();
|
||||
|
||||
@@ -215,6 +215,13 @@ api::orderman::OrderDefinition
|
||||
ICENativeOrder::on_init_from_exe_report(const ExecutionReport& er, const PreActivationOrderServices& os)
|
||||
{
|
||||
auto k = er.order_key;
|
||||
auto nextid = me->services->gen_id();
|
||||
if(k.value() < nextid)
|
||||
{
|
||||
k = OrderKey(nextid);
|
||||
LOG(OrderMan, Error, "Updating recovered order id with newly generated id {}", k.value());
|
||||
}
|
||||
|
||||
OrderRecord orecord;
|
||||
orecord.set_key(k.value());
|
||||
orecord.set_parent_key(null_order_key().value());
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="spark_sdk" Version="2.15.10" />
|
||||
<PackageReference Include="System.Reactive" Version="4.4.1" />
|
||||
<PackageReference Include="System.Reactive.Core" Version="4.4.1" />
|
||||
<PackageReference Include="System.Reactive.Interfaces" Version="4.4.1" />
|
||||
<PackageReference Include="System.Reactive.Linq" Version="4.4.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,68 @@
|
||||
using bts.api.orderman;
|
||||
using CommandLine;
|
||||
|
||||
namespace bts.api.csharp {
|
||||
[Verb("watch", HelpText = "List items in the system.")]
|
||||
class WatchOptions {
|
||||
[Option("accounts", Required = false, SetName = "Accounts", HelpText = "print accounts in the system.")]
|
||||
public bool accounts { get; set; }
|
||||
|
||||
[Option("securities", Required = false, SetName = "Securities", HelpText = "print securities in the system (may be combined with --filter-symbols, or --filter-securities).")]
|
||||
public bool securities { get; set; }
|
||||
|
||||
[Option("symbols", Required = false, SetName = "Symbols", HelpText = "print all symbols in the system (may not be combined with other options).")]
|
||||
public bool symbols { get; set; }
|
||||
|
||||
[Option("bbo", Required = false, SetName = "Books", HelpText = "print bbo for an optionally filtered set of securities/symbols.")]
|
||||
public bool bbo { get; set; }
|
||||
|
||||
[Option("orders", Required = false, SetName = "Orders", HelpText = "print orders in the system.")]
|
||||
public bool orders { get; set; }
|
||||
|
||||
[Option("positions", Required = false, SetName = "Positions", HelpText = "print positions in the system (may be combined with --filter-symbols, --filter-securities, and --filter-accounts).")]
|
||||
public bool positions { get; set; }
|
||||
|
||||
// auxiliary filters
|
||||
[Option("filter-securities", Required = false, HelpText = "filter securities with a regular expression (may be combined with --securities, and --bbo).")]
|
||||
public string filter_securities { get; set; }
|
||||
|
||||
[Option("filter-symbols", Required = false, HelpText = "filter securities for a particular set of symbols (may be combined with --securities, and --bbo). Do not confuse with the --symbols option, which prints all symbols in the system.")]
|
||||
public string filter_symbols { get; set; }
|
||||
|
||||
[Option("filter-accounts", Required = false, HelpText = "filter for a particular set of accounts.")]
|
||||
public string filter_accounts { get; set; }
|
||||
}
|
||||
|
||||
[Verb("send-order", HelpText = "Send orders.")]
|
||||
class SendOrdersOptions {
|
||||
// this option only applies to Iceberg orders
|
||||
[Option("show-quantity", Required = false, SetName = "Iceberg")]
|
||||
public double show_quantity { get; set; } = 5;
|
||||
|
||||
[Option("price", Required = false, HelpText = "specify the order price.")]
|
||||
public double price { get; set; } = 100;
|
||||
|
||||
[Option("quantity", Required = false, HelpText = "specify the order quantity.")]
|
||||
public uint quantity { get; set; } = 1;
|
||||
|
||||
[Option("account", Required = false, HelpText = "specify the account with which the order should be sent.")]
|
||||
public string account { get; set; }
|
||||
|
||||
[Option("order-type", Required = false, HelpText = "specify the order type (limit, market, or iceberg).")]
|
||||
public CustomOrderDefinition.CustomOneofCase order_type { get; private set; } = CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
|
||||
/// Run the program with the argument <see cref="WatchOptions.securities"/> to display securities/security keys:
|
||||
/// - display all securities:
|
||||
/// bts.api.csharp.exe watch securities
|
||||
/// - display all 'ES' securities:
|
||||
/// bts.api.csharp.exe watch securities --symbols "ES"
|
||||
/// - display all 'ES' and 'GC' securities:
|
||||
/// bts.api.csharp.exe watch securities --symbols "ES,GC"
|
||||
/// If no security key is specified, a random security is used.
|
||||
[Option("security", Required = false, HelpText = "specify the order security by name, or id.")]
|
||||
public string security { get; set; }
|
||||
|
||||
[Option("side", Required = false, HelpText = "specify the order side (buy/sell).")]
|
||||
public Side side { get; set; } = Side.Buy;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +1,88 @@
|
||||
using bts.api.core;
|
||||
using bts.api.csharp.examples;
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.api.csharp.data;
|
||||
using bts.api.csharp.utils;
|
||||
using bts.api.csharp.watch;
|
||||
using bts.api.orderman;
|
||||
using bts.api.position;
|
||||
using CommandLine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace bts.api.csharp {
|
||||
class Program {
|
||||
static void Main(string[] args) =>
|
||||
Parser.Default.ParseArguments<Options>(args).WithParsed(_run);
|
||||
|
||||
static void _run(Options options) {
|
||||
var exit_event = new ManualResetEvent(false);
|
||||
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
|
||||
var service_context = new ServiceContext();
|
||||
var login = service_context.login("bts", "");
|
||||
login.Wait();
|
||||
var examples = new List<IDisposable>();
|
||||
if (options.subscribe_securities ||! options.any_enabled) {
|
||||
examples.Add(new SecurityExample(service_context.locate<MarketDataService>()));
|
||||
}
|
||||
if (options.subscribe_positions || !options.any_enabled) {
|
||||
examples.Add(new PositionExample(service_context.locate<PositionService>()));
|
||||
}
|
||||
if (options.subscribe_orders || !options.any_enabled) {
|
||||
examples.Add(new OrderExample(service_context.locate<OrderService>()));
|
||||
}
|
||||
if (options.subscribe_book || !options.any_enabled) {
|
||||
examples.Add(new BookExample(service_context.locate<MarketDataService>()));
|
||||
}
|
||||
if (options.send_orders || !options.any_enabled) {
|
||||
examples.Add(SendOrderLoop.init_loop(service_context.locate<OrderService>(), service_context.locate<MarketDataService>()));
|
||||
}
|
||||
Console.CancelKeyPress += on_cancel_key_press;
|
||||
exit_event.WaitOne();
|
||||
foreach (var ex in examples)
|
||||
ex.Dispose();
|
||||
//
|
||||
void on_cancel_key_press(object sender, ConsoleCancelEventArgs e) {
|
||||
exit_event.Set();
|
||||
e.Cancel = true;
|
||||
}
|
||||
static void Main(string[] args) {
|
||||
new Parser(cfg => cfg.CaseInsensitiveEnumValues = true)
|
||||
.ParseArguments<WatchOptions, SendOrdersOptions>(args)
|
||||
.MapResult(
|
||||
_execution_shell<WatchOptions>(_watch),
|
||||
_execution_shell<SendOrdersOptions>(_send_orders, true),
|
||||
errors => 1);
|
||||
}
|
||||
|
||||
class Options {
|
||||
[Option("subscribe-securities", Required = false, HelpText = "run security-subscription example.")]
|
||||
public bool subscribe_securities { get; set; }
|
||||
static Func<T, int> _execution_shell<T>(Func<ServiceContext, T, IDisposable> execute, bool auto_quit = false) {
|
||||
return opts => {
|
||||
var exit_event = new ManualResetEvent(auto_quit);
|
||||
Console.CancelKeyPress += on_cancel_key_press;
|
||||
var service_context = new ServiceContext();
|
||||
service_context.login("bts", "").Wait();
|
||||
execute(service_context, opts);
|
||||
exit_event.WaitOne();
|
||||
return 0;
|
||||
//
|
||||
void on_cancel_key_press(object sender, ConsoleCancelEventArgs e) {
|
||||
exit_event.Set();
|
||||
e.Cancel = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Option("subscribe-orders", Required = false, HelpText = "run order-subscription example.")]
|
||||
public bool subscribe_orders { get; set; }
|
||||
static IDisposable _watch(ServiceContext service_context, WatchOptions options) {
|
||||
if (options.accounts) {
|
||||
// watch accounts
|
||||
return WatchAccounts.run(service_context.locate<AdminService>());
|
||||
} else if (options.securities) {
|
||||
// watch securities
|
||||
var security_predicate = CLIUtils.parse_security_predicate(options.filter_symbols, options.filter_securities);
|
||||
return WatchSecurities.run(service_context.locate<MarketDataService>(), security_predicate);
|
||||
} else if (options.bbo) {
|
||||
// watch bbo
|
||||
return WatchBooks.run(new bts.md.MarketDataClient(),
|
||||
CLIUtils.parse_security_predicate(options.filter_symbols, options.filter_securities));
|
||||
} else if (options.symbols) {
|
||||
// watch symbols
|
||||
return WatchSymbols.run(service_context.locate<MarketDataService>());
|
||||
} else if (options.orders) {
|
||||
// watch orders (optionally filtered by account)
|
||||
var account_predicate = CLIUtils.parse_delimited_predicate<OrderRecordImmutable>(
|
||||
options.filter_accounts, order => order.Def.Account);
|
||||
return WatchOrders.run(service_context.locate<OrderService>(), account_predicate);
|
||||
} else if (options.positions) {
|
||||
// watch positions (optionally filtered by account)
|
||||
var account_predicate = CLIUtils.parse_delimited_predicate<PositionUnitImmutable>(
|
||||
options.filter_accounts, pos => pos.Account);
|
||||
return WatchPositions.run(service_context.locate<PositionService>(), account_predicate);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[Option("subscribe-positions", Required = false, HelpText = "run position-subscription example.")]
|
||||
public bool subscribe_positions { get; set; }
|
||||
|
||||
[Option("subscribe-books", Required = false, HelpText = "run book-subscription example.")]
|
||||
public bool subscribe_book { get; set; }
|
||||
|
||||
[Option("send-orders", Required = false, HelpText = "run send-orders example.")]
|
||||
public bool send_orders { get; set; }
|
||||
|
||||
// if none are enabled, all options will be flipped on,
|
||||
// so the user need not specify a single argument to see the examples run.
|
||||
public bool any_enabled =>
|
||||
subscribe_securities ||
|
||||
subscribe_orders ||
|
||||
subscribe_positions ||
|
||||
subscribe_book ||
|
||||
send_orders;
|
||||
static IDisposable _send_orders(ServiceContext service_context, SendOrdersOptions options) {
|
||||
var custom_order_def = OrderUtils.custom_order_definitioni(options.order_type);
|
||||
if (!custom_order_def.HasValue) {
|
||||
Console.WriteLine($"Unable to create order of type '{options.order_type}.' The following types are available:\n\t{string.Join("\n\t", OrderUtils.known_order_types)}");
|
||||
return null;
|
||||
}
|
||||
var user = new NameIDPair(service_context.user, service_context.user_id);
|
||||
var admin_service = service_context.locate<AdminService>();
|
||||
var account = AdminUtils.get_account_async(user, admin_service, options.account).WaitResult();
|
||||
var md_service = service_context.locate<MarketDataService>();
|
||||
var security = SecurityUtils.get_security_async(md_service.subscribei_securities(), options.security).WaitResult();
|
||||
var model = new OrderModel(service_context.locate<OrderService>());
|
||||
/// Note that the call below (<see cref="OrderModel.send_order(NameIDPair, NameIDPair, uint, Side, uint, double, CustomOrderDefinition)"/>)
|
||||
/// blocks the current thread until an ack is received from the server. If you wish to avoid blocking the thread as such,
|
||||
/// use the following method instead: <see cref="OrderModel.send_order_async(NameIDPair, NameIDPair, uint, Side, uint, double, CustomOrderDefinition)"/>.
|
||||
model.send_order(user, account, security.Id, options.side, options.quantity, options.price, custom_order_def.Value.CloneToMutable());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace bts.api.csharp {
|
||||
namespace bts.api.csharp.data {
|
||||
readonly struct NameIDPair {
|
||||
public NameIDPair(string name, uint id) {
|
||||
this.name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
@@ -0,0 +1,71 @@
|
||||
using bts.api.csharp.utils;
|
||||
using bts.api.orderman;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api.csharp.data {
|
||||
/// <summary>
|
||||
/// Exemplifies how the <see cref="OrderService"/> api may be used to send orders.
|
||||
/// </summary>
|
||||
class OrderModel {
|
||||
readonly OrderService _service;
|
||||
|
||||
public OrderModel(OrderService service) =>
|
||||
_service = service;
|
||||
|
||||
public static OrderDefinition create_order_definition(NameIDPair user, NameIDPair account, uint security_key, Side side, CustomOrderDefinition custom_def) {
|
||||
return new OrderDefinition {
|
||||
Account = account.name,
|
||||
AccountId = account.id,
|
||||
User = user.name,
|
||||
UserId = user.id,
|
||||
SecurityKey = security_key,
|
||||
Side = side,
|
||||
CustomDef = custom_def
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an order of the type specified by <paramref name="custom_order_definition"/> (asynchronous).
|
||||
/// </summary>
|
||||
/// <returns>The order id if the submission was successful, otherwise null.</returns>
|
||||
public async Task<uint?> send_order_async(NameIDPair user, NameIDPair account, uint security_key, Side side, uint quantity, double price, CustomOrderDefinition custom_order_definition) {
|
||||
Console.WriteLine($"Creating order {OrderUtils.fmt_order(side, quantity, price, user.name, account.name, custom_order_definition)}");
|
||||
var response = await _service.create_order_async(new Create {
|
||||
Quantity = quantity,
|
||||
Price = price,
|
||||
NewOrder = create_order_definition(user, account, security_key, side, custom_order_definition)
|
||||
});
|
||||
if (response.success) {
|
||||
Console.WriteLine($"Order submission succeeded. Order id is {response.value.OrderKey}.");
|
||||
return response.value.OrderKey;
|
||||
} else {
|
||||
Console.WriteLine($"Order submission failed: [{response.value.ErrorCode}] {response.value.Msg}.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an order with specified parameters (the thread will be blocked until an ack is received from the server).
|
||||
/// </summary>
|
||||
public uint? send_order(NameIDPair user, NameIDPair account, uint security_key, Side side, uint quantity, double price, CustomOrderDefinition custom_order_definition) {
|
||||
/// Note, the thread will be blocked until a response is received from the server.
|
||||
/// For this reason, <see cref="OrderService.create_order_async(Create)"/> is usually preferred.
|
||||
/// An example of how to use that method is provided in <see cref="send_order_async(NameIDPair, NameIDPair, CustomOrderDefinition, uint, double)"/>.
|
||||
Console.WriteLine($"Creating order {OrderUtils.fmt_order(side, quantity, price, user.name, account.name, custom_order_definition)}");
|
||||
var response = _service.create_order(new Create {
|
||||
Quantity = quantity,
|
||||
Price = price,
|
||||
NewOrder = create_order_definition(user, account, security_key, side, custom_order_definition)
|
||||
});
|
||||
switch (response.ErrorCode) {
|
||||
case OrderError.Ok:
|
||||
Console.WriteLine($"Order submission succeeded. Order id is {response.OrderKey}.");
|
||||
return response.OrderKey;
|
||||
default:
|
||||
Console.WriteLine($"Order submission failed: [{response.ErrorCode}] {response.Msg}.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using bts.api.core;
|
||||
using bts.api.csharp.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using SecurityKey = System.UInt32;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="MarketDataService"/> api to observe book updates
|
||||
/// for security in the system, and organize them into a Dictionary.
|
||||
/// </summary>
|
||||
class BookExample : IDisposable {
|
||||
readonly Dictionary<SecurityKey, BookEntryImmutable> _books = new Dictionary<SecurityKey, BookEntryImmutable>();
|
||||
readonly IDisposable _subscription;
|
||||
|
||||
public BookExample(MarketDataService market_data_service) {
|
||||
_subscription = market_data_service.subscribei_book()
|
||||
.ObserveOn(SynchronizationContext.Current)
|
||||
.Subscribe(_on_book_update);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_subscription.Dispose();
|
||||
}
|
||||
|
||||
void _on_book_update(Update<BookEntryImmutable, uint> update) {
|
||||
foreach (var new_book in update.added) {
|
||||
// of course, we shouldn't have entries for any books in update.added yet
|
||||
Debug.Assert(!_books.ContainsKey(new_book.Key));
|
||||
// display bbo in the console window
|
||||
_print_book_info(new_book.Key, $"book received for the first time, bbo is: {BookUtils.fmt_bbo(new_book)}");
|
||||
// add the new book to our map
|
||||
_books.Add(new_book.Key, new_book);
|
||||
}
|
||||
foreach (var change in update.changes) {
|
||||
// of course, the security key shouldn't change
|
||||
Debug.Assert(change.Old.Key == change.New.Key);
|
||||
// our book entry for this security should be identitical to change.Old
|
||||
Debug.Assert(change.Old.Bid.SequenceEqual(_books[change.Old.Key].Bid));
|
||||
Debug.Assert(change.Old.Ask.SequenceEqual(_books[change.Old.Key].Ask));
|
||||
// display the current book in the console window
|
||||
_print_book_info(change.New.Key, $"book changed, now bbo is: {BookUtils.fmt_bbo(change.New)}");
|
||||
// update our map with the changed book
|
||||
_books[change.New.Key] = change.New;
|
||||
}
|
||||
foreach (var old_book in update.removed) {
|
||||
// of course, we should have entries for all books in update.removed
|
||||
Debug.Assert(_books.Remove(old_book.Key));
|
||||
}
|
||||
}
|
||||
|
||||
static void _print_book_info(SecurityKey key, string message) {
|
||||
Console.WriteLine($"Security {key}: {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using AccountKey = System.UInt32;
|
||||
using OrderKey = System.UInt32;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="OrderService"/> to observe order updates,
|
||||
/// and group them by account.
|
||||
/// </summary>
|
||||
class OrderExample : IDisposable {
|
||||
readonly Dictionary<AccountKey, Dictionary<OrderKey, OrderRecordImmutable>> _data = new Dictionary<AccountKey, Dictionary<AccountKey, OrderRecordImmutable>>();
|
||||
IDisposable _subscription;
|
||||
|
||||
public OrderExample(OrderService order_service) {
|
||||
/// 'OrderService' has 2 methods which can be used to subscribe to order updates.
|
||||
/// -------------------------------------------------------------------------------------
|
||||
/// 1) <see cref="OrderService.subscribe_orders(string)"/>.
|
||||
/// 2) <see cref="OrderService.subscribei_orders(string)"/>.
|
||||
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
|
||||
/// --------------------------------------------------------------------------------------
|
||||
_subscription = order_service.subscribei_orders()
|
||||
// the 'ObserveOn' call below ensures that the
|
||||
// callback will be invoked this thread.
|
||||
// Be sure to do this if you don't want to worry
|
||||
// about multi-threading issues (locking, etc).
|
||||
.ObserveOn(SynchronizationContext.Current)
|
||||
.Subscribe(_on_update);
|
||||
|
||||
}
|
||||
|
||||
public event Action<AccountKey> account_orders_changed;
|
||||
|
||||
public void Dispose() {
|
||||
_subscription.Dispose();
|
||||
}
|
||||
|
||||
public IEnumerable<OrderRecordImmutable> get_account_orders(AccountKey account) {
|
||||
if (_data.TryGetValue(account, out var account_orders)) {
|
||||
return account_orders.Values;
|
||||
}
|
||||
return Enumerable.Empty<OrderRecordImmutable>();
|
||||
}
|
||||
|
||||
void _on_order_added(OrderRecordImmutable new_order) {
|
||||
if (!_data.TryGetValue(new_order.Def.AccountId, out var account_orders)) {
|
||||
// this is the first order for this account.
|
||||
// initialize the account orders dictionary, and add it to our data.
|
||||
account_orders = new Dictionary<AccountKey, OrderRecordImmutable>();
|
||||
_data.Add(new_order.Def.AccountId, account_orders);
|
||||
}
|
||||
// now add the order to account_orders
|
||||
account_orders.Add(new_order.Key, new_order);
|
||||
}
|
||||
|
||||
void _on_order_changed(Change<OrderRecordImmutable> change) {
|
||||
// ch.New and ch.Old have the same key.
|
||||
Debug.Assert(change.New.Key == change.Old.Key);
|
||||
var key = change.New.Key;
|
||||
// react to a changed order
|
||||
if (change.New.State.FilledQuantity != change.Old.State.FilledQuantity) {
|
||||
// Display new fills in the console window.
|
||||
var num_new_fills = change.New.State.FilledQuantity - change.Old.State.FilledQuantity;
|
||||
_print_order_info(key, $"received {num_new_fills} new fills.");
|
||||
// Be mindful that orders can be overfilled.
|
||||
if (change.New.State.FilledQuantity > change.New.State.Quantity) {
|
||||
// Display overfills in the console window.
|
||||
var num_over_fills = change.New.State.FilledQuantity - change.New.State.Quantity;
|
||||
_print_order_info(key, $"overfilled by {num_over_fills}");
|
||||
} else {
|
||||
// Display working qty in the console window.
|
||||
var working_qty = change.New.State.Quantity - change.New.State.FilledQuantity;
|
||||
_print_order_info(key, $"still working {working_qty}.");
|
||||
}
|
||||
}
|
||||
// Display order status changes in the console window.
|
||||
if (change.New.State.Status != change.Old.State.Status) {
|
||||
_print_order_info(key, $"status changed from {change.Old.State.Status} to {change.New.State.Status}.");
|
||||
}
|
||||
// Account will not change, so our dictionary will definitely have a key for this account.
|
||||
Debug.Assert(_data.ContainsKey(change.New.Def.AccountId));
|
||||
var account_orders = _data[change.New.Def.AccountId];
|
||||
// The value for this key in account_orders should equal ch.Old.
|
||||
Debug.Assert(account_orders[key].Equals(change.Old));
|
||||
// update account orders with the changed order.
|
||||
account_orders[key] = change.New;
|
||||
}
|
||||
|
||||
void _on_order_removed(OrderRecordImmutable old_val) {
|
||||
// An entry for this order should be present in our data,
|
||||
// as it is in the 'removed' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(_data.ContainsKey(old_val.Def.AccountId));
|
||||
var account_orders = _data[old_val.Def.AccountId];
|
||||
// the removed value should match the one currently in our data.
|
||||
// Assert this is the case, then remove the order.
|
||||
Debug.Assert(account_orders[old_val.Key].Equals(old_val));
|
||||
account_orders.Remove(old_val.Key);
|
||||
_print_order_info(old_val.Key, "has been removed.");
|
||||
}
|
||||
|
||||
void _on_update(Update<OrderRecordImmutable, OrderKey> update) {
|
||||
// record which accounts experienced an order update, then
|
||||
// at the end of this method, fire the account_orders_changed event for each
|
||||
var accounts_with_order_updates = new HashSet<AccountKey>();
|
||||
foreach (OrderRecordImmutable new_order in update.added) {
|
||||
_on_order_added(new_order);
|
||||
accounts_with_order_updates.Add(new_order.Def.AccountId);
|
||||
}
|
||||
foreach (Change<OrderRecordImmutable> change in update.changes) {
|
||||
_on_order_changed(change);
|
||||
accounts_with_order_updates.Add(change.New.Def.AccountId);
|
||||
}
|
||||
foreach (OrderRecordImmutable old_order in update.removed) {
|
||||
_on_order_removed(old_order);
|
||||
accounts_with_order_updates.Add(old_order.Def.AccountId);
|
||||
}
|
||||
// display all accounts with order updates in the Console window.
|
||||
Console.WriteLine("The following accounts experienced order changes: ");
|
||||
foreach (var account in accounts_with_order_updates) {
|
||||
Console.WriteLine("\n\t" + account);
|
||||
}
|
||||
// lastly, fire the account_orders_changed event for each account experienced an order update.
|
||||
foreach (var account in accounts_with_order_updates) {
|
||||
account_orders_changed?.Invoke(account);
|
||||
}
|
||||
}
|
||||
|
||||
void _print_order_info(OrderKey order_key, string message) {
|
||||
Console.WriteLine($"Order {order_key}: {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
/// <summary>
|
||||
/// A pair of security, and account id,
|
||||
/// which uniquely identifies each PositionUnit.
|
||||
/// </summary>
|
||||
readonly struct PositionKey {
|
||||
public PositionKey(uint security_id, uint account_id) {
|
||||
this.security_id = security_id;
|
||||
this.account_id = account_id;
|
||||
}
|
||||
|
||||
public uint security_id { get; }
|
||||
public uint account_id { get; }
|
||||
|
||||
public static PositionKey from_position(PositionUnitImmutable p) =>
|
||||
new PositionKey(p.SecurityId, p.AccountId);
|
||||
|
||||
public override string ToString() =>
|
||||
$"Sec {security_id}, Acc {account_id}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the <see cref="PositionService"/> api to monitor positions
|
||||
/// for every account/security pair, and organize them into a Dictionary.
|
||||
/// </summary>
|
||||
class PositionExample : IDisposable {
|
||||
readonly Dictionary<PositionKey, PositionUnitImmutable> _data = new Dictionary<PositionKey, PositionUnitImmutable>();
|
||||
IDisposable _subscription;
|
||||
|
||||
public PositionExample(PositionService position_service) {
|
||||
/// 'PositionService' has 2 methods which can be used to subscribe to position updates.
|
||||
/// -------------------------------------------------------------------------------------
|
||||
/// 1) <see cref="PositionService.subscribe_position_data(string)"/>.
|
||||
/// 2) <see cref="PositionService.subscribei_position_data(string)"/>.
|
||||
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
|
||||
/// --------------------------------------------------------------------------------------
|
||||
_subscription = position_service.subscribei_position_data()
|
||||
// the 'ObserveOn' call below ensures that the
|
||||
// callback will be invoked this thread.
|
||||
// Be sure to do this if you don't want to worry
|
||||
// about multi-threading issues (locking, etc).
|
||||
.ObserveOn(SynchronizationContext.Current)
|
||||
.Subscribe(_on_update);
|
||||
}
|
||||
|
||||
public event Action data_changed;
|
||||
|
||||
public IReadOnlyDictionary<PositionKey, PositionUnitImmutable> data => _data;
|
||||
|
||||
public void Dispose() =>
|
||||
_subscription.Dispose();
|
||||
|
||||
void _on_position_added(PositionUnitImmutable new_val) {
|
||||
var k = PositionKey.from_position(new_val);
|
||||
// this entry should NOT be present in our _data, as it is in the 'added' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(!_data.ContainsKey(k));
|
||||
_print_position_info(k, $"initial position received. Day: Long {new_val.DayLongPos}, Short {new_val.DayShortPos}, Net {new_val.DayLongPos - new_val.DayShortPos}.");
|
||||
// add the new position unit to _data
|
||||
_data.Add(k, new_val);
|
||||
}
|
||||
|
||||
void _on_position_changed(Change<PositionUnitImmutable> change) {
|
||||
var k = PositionKey.from_position(change.New);
|
||||
// ch.New and ch.Old have the same key (i.e., the Account/Security pair),
|
||||
Debug.Assert(change.New.AccountId == change.Old.AccountId);
|
||||
Debug.Assert(change.New.SecurityId == change.Old.SecurityId);
|
||||
// of course, the value for this key in our dictionary should equal ch.Old.
|
||||
Debug.Assert(_data[k].Equals(change.Old));
|
||||
// Display changes to the day position in the console window.
|
||||
// 1) check if the day long changed
|
||||
if (change.New.DayLongPos != change.Old.DayLongPos) {
|
||||
Console.WriteLine($"[{k}] day long changed from {change.Old.DayLongPos} to {change.New.DayLongPos}.");
|
||||
}
|
||||
// 2) check if day short changed
|
||||
if (change.New.DayShortPos != change.Old.DayShortPos) {
|
||||
Console.WriteLine($"[{k}] day short changed from {change.Old.DayShortPos} to {change.New.DayShortPos}.");
|
||||
}
|
||||
// 3) check if net day position changed
|
||||
var new_net_day_pos = change.New.DayLongPos - change.New.DayShortPos;
|
||||
var old_net_day_pos = change.Old.DayLongPos - change.Old.DayShortPos;
|
||||
if (new_net_day_pos != old_net_day_pos) {
|
||||
Console.WriteLine($"[{k}] day net changed from {old_net_day_pos} to {new_net_day_pos}.");
|
||||
}
|
||||
// update _data with the changed position.
|
||||
_data[k] = change.New;
|
||||
}
|
||||
|
||||
void _on_position_removed(PositionUnitImmutable old_val) {
|
||||
var k = PositionKey.from_position(old_val);
|
||||
// this entry should be present in our _data, as it is in the 'removed' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(_data.ContainsKey(k));
|
||||
// this should match our data. Assert this is the case.
|
||||
Debug.Assert(_data[k].Equals(old_val));
|
||||
_data.Remove(k);
|
||||
Console.WriteLine($"[{k}] position has been removed.");
|
||||
}
|
||||
|
||||
void _on_update(Update<PositionUnitImmutable, int> update) {
|
||||
foreach (PositionUnitImmutable p in update.added) {
|
||||
_on_position_added(p);
|
||||
}
|
||||
foreach (Change<PositionUnitImmutable> ch in update.changes) {
|
||||
_on_position_changed(ch);
|
||||
}
|
||||
foreach (PositionUnitImmutable p in update.removed) {
|
||||
_on_position_removed(p);
|
||||
}
|
||||
data_changed?.Invoke();
|
||||
}
|
||||
|
||||
void _print_position_info(PositionKey position_key, string message) {
|
||||
Console.WriteLine($"Position {position_key}: {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
using bts.api.core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using SecurityKey = System.UInt32;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="MarketDataService"/> api to observe the
|
||||
/// state of securities in the system.
|
||||
/// </summary>
|
||||
class SecurityExample : IDisposable {
|
||||
readonly IDisposable _subscription;
|
||||
readonly Dictionary<SecurityKey, SecurityImmutable> _securities = new Dictionary<SecurityKey, SecurityImmutable>();
|
||||
|
||||
public SecurityExample(MarketDataService market_data_service) {
|
||||
_subscription = market_data_service.subscribei_securities()
|
||||
.ObserveOn(SynchronizationContext.Current)
|
||||
.Subscribe(_on_securities_update);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_subscription.Dispose();
|
||||
}
|
||||
|
||||
void _on_securities_update(Update<SecurityImmutable, uint> update) {
|
||||
foreach (var new_security in update.added) {
|
||||
// of course, we shouldn't have entries for any books in update.added yet
|
||||
Debug.Assert(!_securities.ContainsKey(new_security.Id));
|
||||
// display bbo in the console window
|
||||
Console.WriteLine($"received new security: {new_security}.");
|
||||
// add the new security to our map
|
||||
_securities.Add(new_security.Id, new_security);
|
||||
}
|
||||
// As you may have noticed, all of the subcribe methods in the api
|
||||
// return an IObservable<Update<T>>. It is worth noting that for security updates,
|
||||
// 'changes' will always be empty.
|
||||
Debug.Assert(!update.changes.Any());
|
||||
foreach (var old_security in update.removed) {
|
||||
// of course, we should have entries for all books in update.removed
|
||||
Debug.Assert(_securities.Remove(old_security.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
using bts.api.csharp.utils;
|
||||
using bts.api.orderman;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
static class OrderTemplates {
|
||||
public static CustomOrderDefinition limit() => new CustomOrderDefinition { Limit = new LimitOrderDefinition() };
|
||||
public static CustomOrderDefinition market() => new CustomOrderDefinition { Market = new MarketOrderDefinition() };
|
||||
public static CustomOrderDefinition iceberg(double show_quantity) => iceberg(show_quantity, limit());
|
||||
public static CustomOrderDefinition iceberg(double show_quantity, CustomOrderDefinition child) =>
|
||||
new CustomOrderDefinition {
|
||||
Iceberg = new IcebergOrderDefinition {
|
||||
ShowQuantity = show_quantity,
|
||||
Child = child
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exemplifies how the <see cref="OrderService"/> api may be used to
|
||||
/// send several common order types (limit, market, and iceberg).
|
||||
/// </summary>
|
||||
class SendOrderModel {
|
||||
readonly OrderService _service;
|
||||
|
||||
public SendOrderModel(OrderService service) {
|
||||
_service = service;
|
||||
}
|
||||
|
||||
public OrderDefinition create_order_definition(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_def) {
|
||||
return new OrderDefinition {
|
||||
Account = account.name,
|
||||
AccountId = account.id,
|
||||
User = user.name,
|
||||
UserId = user.id,
|
||||
SecurityKey = security_key,
|
||||
CustomDef = custom_def
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a limit order (asynchronous).
|
||||
/// </summary>
|
||||
/// <returns>The order id if the submission was successful, otherwise null.</returns>
|
||||
public Task<uint?> send_limit_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity, double price) {
|
||||
return send_order_async(user, account, security_key, OrderTemplates.limit(), quantity, price);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a market order (asynchronous). Note no price parameter is required for market orders.
|
||||
/// </summary>
|
||||
/// <returns>The order id if the submission was successful, otherwise null.</returns>
|
||||
public Task<uint?> send_market_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity) {
|
||||
return send_order_async(user, account, security_key, OrderTemplates.market(), quantity, 0.0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an iceberg of limit orders (asynchronous).
|
||||
/// </summary>
|
||||
/// <returns>The order id if the submission was successful, otherwise null.</returns>
|
||||
public Task<uint?> send_iceberg_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity, double price, double show_quantity) {
|
||||
return send_order_async(user, account, security_key, OrderTemplates.iceberg(show_quantity), quantity, price);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an order of the type specified by <paramref name="custom_order_definition"/> (asynchronous).
|
||||
/// </summary>
|
||||
/// <returns>The order id if the submission was successful, otherwise null.</returns>
|
||||
public async Task<uint?> send_order_async(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_order_definition, uint quantity, double price) {
|
||||
Console.WriteLine($"submitting order for user ({user}), account ({account}): {OrderUtils.fmt_order(quantity, price, custom_order_definition)}.");
|
||||
var reply = await _service.create_order_async(new Create {
|
||||
Quantity = quantity,
|
||||
Price = price,
|
||||
NewOrder = create_order_definition(user, account, security_key, custom_order_definition)
|
||||
});
|
||||
if (reply.success) {
|
||||
Console.WriteLine($"received ack that order has been submitted. Order id is {reply.value.OrderKey}.");
|
||||
return reply.value.OrderKey;
|
||||
} else {
|
||||
Console.WriteLine($"order submission was unsuccessful: [{reply.value.ErrorCode}] {reply.value.Msg}.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void send_order(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_order_definition, uint quantity, double price) {
|
||||
/// Note, the thread will be blocked until a response is received from the server.
|
||||
/// For this reason, <see cref="OrderService.create_order_async(Create)"/> is usually preferred.
|
||||
/// An example of how to use that method is provided in <see cref="send_order_async(NameIDPair, NameIDPair, CustomOrderDefinition, uint, double)"/>.
|
||||
Console.WriteLine($"Creating order {OrderUtils.fmt_order(quantity, price, custom_order_definition)}");
|
||||
var response = _service.create_order(new Create {
|
||||
Quantity = quantity,
|
||||
Price = price,
|
||||
NewOrder = create_order_definition(user, account, security_key, custom_order_definition)
|
||||
});
|
||||
switch (response.ErrorCode) {
|
||||
case OrderError.Ok:
|
||||
_print_order_info(response.OrderKey, "successfully created.");
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine($"Failed to create order: [{response.ErrorCode}] {response.Msg}.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void _print_order_info(uint order_key, string message) {
|
||||
Console.WriteLine($"Order {order_key}: {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
/// <summary>
|
||||
/// The code in this example is a bit more advanced than the others,
|
||||
/// as it is designed to send orders, asynchronously, in a continuous loop.
|
||||
/// For straightforward examples of how to send orders with the api,
|
||||
/// check out <see cref="SendOrderModel"/>.
|
||||
/// </summary>
|
||||
static class SendOrderLoop {
|
||||
/// <summary>
|
||||
/// As a security key is required to send an order, this method
|
||||
/// uses the <see cref="MarketDataService"/> to observe the securities stream,
|
||||
/// then on the first update, initializes the send-order-loop <see cref="LoopImpl"/>
|
||||
/// with one of the newly received security keys.
|
||||
/// </summary>
|
||||
public static IDisposable init_loop(OrderService order_service, MarketDataService market_data_service) {
|
||||
// If you need to familiarize yourself with the IObservable pattern, check out the official docs:
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.iobservable-1?view=netcore-3.1
|
||||
|
||||
var single_security_stream = market_data_service.subscribei_securities()
|
||||
.Where(u => u.all.Any())
|
||||
.Take(1)
|
||||
.Select(u => u.all.FirstOrDefault());
|
||||
|
||||
return init_loop(single_security_stream, create_loop);
|
||||
IDisposable create_loop(SecurityImmutable sec) {
|
||||
return new LoopImpl(order_service, sec.Id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Observes the security stream and creates a new send-order-loop
|
||||
/// whenever a new security arrives. The previous send-order-loop
|
||||
/// is disposed when the next security arrives, so only one send-order-loop
|
||||
/// shall be active at a time.
|
||||
/// </summary>
|
||||
/// <param name="security">The security stream used to initialize send-order-loops.</param>
|
||||
/// <returns>An IDisposable that unsubsribes from the securities stream, and disposes the active send-order-loop.</returns>
|
||||
public static IDisposable init_loop(IObservable<SecurityImmutable> security, Func<SecurityImmutable, IDisposable> factory) {
|
||||
IDisposable active_loop = null;
|
||||
var security_sub = security.Subscribe(s =>
|
||||
Interlocked.Exchange(ref active_loop, factory(s))?.Dispose());
|
||||
return Disposable.Create(dispose);
|
||||
void dispose() {
|
||||
security_sub.Dispose();
|
||||
active_loop?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class LoopImpl : IDisposable {
|
||||
bool _is_disposed;
|
||||
|
||||
public LoopImpl(OrderService service, uint security_key)
|
||||
: this(service, Users.Default, Accounts.Default, security_key, 10, TimeSpan.FromSeconds(3)) { }
|
||||
|
||||
public LoopImpl(OrderService service, NameIDPair user, NameIDPair account, uint security_key, int max_orders, TimeSpan delay_between_orders) {
|
||||
var model = new SendOrderModel(service);
|
||||
send_orders_continuously();
|
||||
|
||||
async void send_orders_continuously() {
|
||||
// Periodically send an order with one of the CustomOrderDefinitions below.
|
||||
// If you are unfamiliar with async/await, this will also serve as a simple example
|
||||
// of how those keywords may be used. However, there is no substite for the official docs:
|
||||
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
|
||||
var order_templates = new CustomOrderDefinitionImmutable[] {
|
||||
new CustomOrderDefinitionImmutable(OrderTemplates.limit()),
|
||||
new CustomOrderDefinitionImmutable(OrderTemplates.market()),
|
||||
new CustomOrderDefinitionImmutable(OrderTemplates.iceberg(5))
|
||||
};
|
||||
for (int num_orders_sent = 0; num_orders_sent < max_orders && !_is_disposed; num_orders_sent++) {
|
||||
int template_index = num_orders_sent % order_templates.Length;
|
||||
CustomOrderDefinitionImmutable templatei = order_templates[template_index];
|
||||
// The order templates we created are immutable, however the api uses the mutable form.
|
||||
// Not to worry tho, there is a nifty method called 'CloneToMutable.'
|
||||
// Note that all of the non-primitive immutable parameters in the api methods have a 'CloneToMutable' method,
|
||||
// it is not unique to CustomOrderDefinitionImmutable.
|
||||
CustomOrderDefinition template = templatei.CloneToMutable();
|
||||
await model.send_order_async(user, account, security_key, template, 12, 100);
|
||||
// pause for a moment before sending the next order
|
||||
await Task.Delay(delay_between_orders);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// flip _is_disposed to true, so the 'send_orders_continuously' loop
|
||||
// knows it's time to stop.
|
||||
_is_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using bts.api.admin;
|
||||
using bts.api.csharp.data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class AdminUtils {
|
||||
public static IReadOnlyDictionary<uint, NameIDPair> map_accounts(AdminDataImmutable admin_datai) {
|
||||
return admin_datai.Accounts.ToDictionary(acc => acc.Id, acc => new NameIDPair(acc.Name, acc.Id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Links users to the set of accounts on which they have trading permissions.
|
||||
/// </summary>
|
||||
public static IEnumerable<(NameIDPair user, IEnumerable<uint> accounts)> user_account_permissions(AdminDataImmutable admin_datai) {
|
||||
return admin_datai.Users
|
||||
.Select(user => (new NameIDPair(user.Name, user.Id), _user_tradable_accounts(user)));
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<NameIDPair, ImmutableHashSet<NameIDPair>> map_user_account_permissions(AdminDataImmutable admin_datai) {
|
||||
var account_lookup = admin_datai.Accounts.ToLookup(acc => acc.Id);
|
||||
return admin_datai.Users.ToDictionary(
|
||||
user => new NameIDPair(user.Name, user.Id),
|
||||
user => ImmutableHashSet.CreateRange(_user_tradable_accounts(user)
|
||||
.Select(acc_id => new NameIDPair(account_lookup[acc_id].Single().Name, acc_id))));
|
||||
}
|
||||
|
||||
static IEnumerable<uint> _user_tradable_accounts(BTSUserImmutable user) {
|
||||
return user.UserData.AccountPermissions.AccountPermissions
|
||||
.Where(kv => kv.Value.TradingPermission.HasPermissions)
|
||||
.Select(kv => kv.Key);
|
||||
}
|
||||
|
||||
public static NameIDPair get_account(NameIDPair user, AdminService admin_service, string account_name = null) {
|
||||
var task = get_account_async(user, admin_service, account_name);
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Either searches for the account with the specified name,
|
||||
/// or picks a random account for which the specified user has trading permissions.
|
||||
/// </summary>
|
||||
public static async Task<NameIDPair> get_account_async(NameIDPair user, AdminService admin_service, string account_name = null) {
|
||||
return await (account_name == null ? auto_account(user, admin_service) : find_account(admin_service, account_name).FirstAsync());
|
||||
}
|
||||
|
||||
public static IObservable<NameIDPair> auto_account(NameIDPair user, AdminService admin_service) {
|
||||
return admin_service.subscribei_admin_data()
|
||||
.Where(update => update.all.Any())
|
||||
.Select(update => map_user_account_permissions(update.all.First()))
|
||||
.Select(account_perms => account_perms.TryGetValue(user, out var accs) ? accs : ImmutableHashSet<NameIDPair>.Empty)
|
||||
.Where(accs => accs.Count > 0)
|
||||
.Select(accs => accs.First())
|
||||
.Take(1);
|
||||
}
|
||||
|
||||
public static IObservable<NameIDPair> find_account(AdminService admin_service, string account) {
|
||||
return admin_service.subscribei_admin_data()
|
||||
.Select(update => {
|
||||
foreach (var admin_data in update.all) {
|
||||
foreach (var acc in admin_data.Accounts) {
|
||||
if (acc.Name == account)
|
||||
return new NameIDPair(acc.Name, acc.Id);
|
||||
}
|
||||
}
|
||||
return (NameIDPair?)null;
|
||||
})
|
||||
.Where(acc => acc.HasValue)
|
||||
.Select(acc => acc.Value)
|
||||
.Take(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class CLIUtils {
|
||||
public static IEnumerable<string> parse_delimited(string cli_arg, string file_arg, string delimiter) {
|
||||
var cli_items = _parse_delimited(cli_arg, delimiter);
|
||||
if (file_arg != null) {
|
||||
try {
|
||||
var items = _parse_delimited(File.ReadAllText(file_arg), delimiter);
|
||||
return cli_items is null ? items : cli_items.Concat(items);
|
||||
} catch { }
|
||||
}
|
||||
return cli_items;
|
||||
}
|
||||
|
||||
public static Func<SecurityImmutable, bool> parse_security_predicate(string cli_symbols = null, string cli_security_regex = null) {
|
||||
return _merge_predicates(
|
||||
parse_delimited_predicate<SecurityImmutable>(cli_symbols, sec => sec.Symbol),
|
||||
RegexUtils.default_regex(cli_security_regex)?.to_predicate<SecurityImmutable>());
|
||||
}
|
||||
|
||||
public static Func<T, bool> parse_delimited_predicate<T>(string cli_arg, Func<T, string> get) =>
|
||||
parse_delimited_predicate(cli_arg, null, ",", get);
|
||||
|
||||
public static Func<T, bool> parse_delimited_predicate<T>(string cli_arg, string file_arg, string delimiter, Func<T, string> get) {
|
||||
var items = new HashSet<string>(parse_delimited(cli_arg, file_arg, delimiter));
|
||||
if (items.Count == 0)
|
||||
return null;
|
||||
return sec => items.Contains(get(sec));
|
||||
}
|
||||
|
||||
static IEnumerable<string> _parse_delimited(string delimited_string, string delimiter) {
|
||||
return delimited_string
|
||||
?.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries)
|
||||
?.Select(sym => sym.Trim())
|
||||
?? Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
static Func<T, bool> _merge_predicates<T>(params Func<T, bool>[] predicates) {
|
||||
Func<T, bool> result = null;
|
||||
for (int i = 0; i < predicates.Length; i++) {
|
||||
if (predicates[i] != null) {
|
||||
if (result == null) {
|
||||
result = predicates[i];
|
||||
} else {
|
||||
result += predicates[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class CollectionUtils {
|
||||
public static bool insert_sorted<T>(List<T> list, T value) =>
|
||||
insert_sorted(list, value, Comparer<T>.Default);
|
||||
|
||||
public static bool insert_sorted<T>(List<T> list, T value, IComparer<T> comparer) {
|
||||
if (list is null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
if (comparer is null)
|
||||
throw new ArgumentNullException(nameof(comparer));
|
||||
var sorted_index = list.BinarySearch(value, comparer);
|
||||
if (sorted_index < 0) {
|
||||
// this is a new symbol
|
||||
list.Insert(~sorted_index, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class DelegateUtils {
|
||||
public static Func<TOuter, TResult> wrap<TInner, TOuter, TResult>(this Func<TInner, TResult> func, Func<TOuter, TInner> get_inner) =>
|
||||
outer => func(get_inner(outer));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,32 @@
|
||||
using bts.api.orderman;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class OrderUtils {
|
||||
// add new templates as needed
|
||||
static readonly IReadOnlyDictionary<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinitionImmutable>
|
||||
_custom_def_templates = new Dictionary<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinitionImmutable> {
|
||||
{ CustomOrderDefinition.CustomOneofCase.Limit, new CustomOrderDefinitionImmutable(new CustomOrderDefinition{ Limit = new LimitOrderDefinition() }) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Market, new CustomOrderDefinitionImmutable(new CustomOrderDefinition{ Market = new MarketOrderDefinition() }) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Iceberg, new CustomOrderDefinitionImmutable(new CustomOrderDefinition{ Iceberg = new IcebergOrderDefinition() }) }
|
||||
};
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition.CustomOneofCase> known_order_types => _custom_def_templates.Keys;
|
||||
|
||||
public static CustomOrderDefinitionImmutable? custom_order_definitioni(CustomOrderDefinition.CustomOneofCase order_type) {
|
||||
if (_custom_def_templates.TryGetValue(order_type, out var templatei)) {
|
||||
return templatei.CloneToMutable();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string fmt_order(uint quantity, double price, CustomOrderDefinition custom_order_definition) {
|
||||
return $"{quantity} @ {price}: {custom_order_definition}";
|
||||
}
|
||||
|
||||
public static string fmt_order(Side side, uint quantity, double price, string user, string account, CustomOrderDefinition custom_order_definition) {
|
||||
return $"[{user}][{account}] {side} {fmt_order(quantity, price, custom_order_definition)}.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class RegexUtils {
|
||||
public static Regex default_regex(string pattern) {
|
||||
// these patterns typically come from the user,
|
||||
// so the 'should_ignore' delegate below allows them
|
||||
// to include all results by specifying "all" or "*".
|
||||
return default_regex(pattern, p => string.IsNullOrEmpty(p) || p == "*" || p.ToLower() == "all");
|
||||
}
|
||||
|
||||
public static Regex default_regex(string pattern, Func<string, bool> should_ignore) {
|
||||
if (pattern == null)
|
||||
return null;
|
||||
pattern = pattern.Trim();
|
||||
if (pattern == string.Empty)
|
||||
return null;
|
||||
return new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
}
|
||||
|
||||
public static Func<T, bool> to_predicate<T>(this Regex regex) =>
|
||||
regex == null ? null : new Func<T, bool>(val => regex.IsMatch(val.ToString()));
|
||||
}
|
||||
}
|
||||
+46
-2
@@ -1,8 +1,52 @@
|
||||
using System;
|
||||
using bts.api.core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SymbolTableGroup = bts.api.core.TableUpdateGrouping<bts.api.SecurityImmutable, uint, string>;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class SecurityUtils {
|
||||
public static IObservable<TableUpdate<SymbolTableGroup, string>> GroupBySymbols(this IObservable<TableUpdate<SecurityImmutable, uint>> securities_source) =>
|
||||
securities_source.GroupTable(sec => sec.Id, sec => sec.Symbol);
|
||||
|
||||
public static async Task<SecurityImmutable> get_security_async(IObservable<Update<SecurityImmutable, uint>> source, string security_str = null) {
|
||||
if (!string.IsNullOrEmpty(security_str)) {
|
||||
Func<SecurityImmutable, bool> is_match;
|
||||
// the user may specify the security string, or security key
|
||||
if (uint.TryParse(security_str, out var sec_key)) {
|
||||
is_match = sec => sec.Id == sec_key;
|
||||
} else {
|
||||
// ignore case and white space
|
||||
security_str = security_str.Replace(" ", "").ToLower();
|
||||
is_match = sec => sec.ToString().Replace(" ", "").ToLower() == security_str;
|
||||
}
|
||||
return await source.Select(update => {
|
||||
foreach (var sec in update.all) {
|
||||
if (is_match(sec))
|
||||
return sec;
|
||||
}
|
||||
return (SecurityImmutable?)null;
|
||||
})
|
||||
.Where(sec => sec.HasValue)
|
||||
.Select(sec => sec.Value)
|
||||
.FirstAsync();
|
||||
} else {
|
||||
return await source.Where(update => update.all.Any())
|
||||
.Select(update => update.all.First())
|
||||
.FirstAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Predicates {
|
||||
public static Func<SecurityImmutable, bool> symbols(IEnumerable<string> symbols) {
|
||||
if (symbols is null)
|
||||
return sec => true;
|
||||
// use hashset for quicker Contains check
|
||||
var symbols_set = new HashSet<string>(symbols);
|
||||
return sec => symbols_set.Contains(sec.Symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using bts.api.core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class StringUtils {
|
||||
public static IEnumerable<string> format_columns(IEnumerable<IEnumerable<string>> columns, int min_width = 8, int padding = 3) =>
|
||||
format_columns(columns.Select(lines => (Math.Max(min_width, lines.Max(l => l.Length) + padding), lines)));
|
||||
|
||||
public static IEnumerable<string> format_columns(IEnumerable<(int width, IEnumerable<string> lines)> columns) {
|
||||
var column_layout = columns.Select((column, index) => (column.lines.GetEnumerator(), column.width, index)).ToList();
|
||||
int num_active = column_layout.Count;
|
||||
if (num_active == 0)
|
||||
yield break;
|
||||
while (true) {
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < column_layout.Count; i++) {
|
||||
var (en, width, pos) = column_layout[i];
|
||||
if (en != null && en.MoveNext()) {
|
||||
sb.Append(string.Format($"{{{0},{-width}}}", en.Current));
|
||||
sb.Append(string.Format($"{{{0,1}}}", "| "));
|
||||
} else {
|
||||
sb.Append(string.Format($"{{{0},{-width}}}", ""));
|
||||
sb.Append(string.Format($"{{{0,1}}}", "| "));
|
||||
if (en != null) {
|
||||
// set the enumerable to null to inidicate this column is empty for the remaining rows
|
||||
en.Dispose();
|
||||
column_layout[i] = (null, width, pos);
|
||||
if (--num_active == 0)
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
yield return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class TaskExtensions {
|
||||
public static T WaitResult<T>(this Task<T> task) {
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using bts.api.core;
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class UpdateUtils {
|
||||
/// <summary>
|
||||
/// Uses a value-subscription delegate to create subscriptions (incrementally) for each value in the update sequence.
|
||||
/// When a value is removed or changed, its corresponding subscription is disposed; if it changed, a new subscription is created with the new value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the updates.</typeparam>
|
||||
/// <typeparam name="TKey">The type of keys in the updates.</typeparam>
|
||||
/// <param name="update_stream">The update sequence.</param>
|
||||
/// <param name="subscribe_value">The delegate to create a subscription for a particular value.</param>
|
||||
/// <returns>An IDisposable that, upon being disposed, disposes the subscription to the update sequence, and all active value subscriptions.</returns>
|
||||
public static IDisposable subscribe_incremental_values<T, TKey>(IObservable<Update<T, TKey>> update_stream, Func<T, IDisposable> subscribe_value)
|
||||
where TKey : IEquatable<TKey> {
|
||||
var locker = new object();
|
||||
var value_subs = new Dictionary<TKey, IDisposable>();
|
||||
var source_sub = update_stream.Subscribe(on_update);
|
||||
return Disposable.Create(dispose);
|
||||
|
||||
// local methods
|
||||
void on_update(Update<T, TKey> update) {
|
||||
lock (locker) {
|
||||
foreach (var value in update.removed) {
|
||||
var k = update.key(value);
|
||||
value_subs[k].Dispose();
|
||||
value_subs.Remove(k);
|
||||
}
|
||||
foreach (var ch in update.changes) {
|
||||
var k = update.key(ch.New);
|
||||
value_subs[k].Dispose();
|
||||
value_subs[k] = subscribe_value(ch.New);
|
||||
}
|
||||
foreach (var value in update.added) {
|
||||
value_subs.Add(update.key(value), subscribe_value(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
lock (locker) {
|
||||
source_sub.Dispose();
|
||||
foreach (var loop in value_subs.Values) {
|
||||
loop.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IObservable<TableUpdate<T, TKey>> SafeFilter<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, Func<T, bool> filter) where TKey : IEquatable<TKey> =>
|
||||
filter == null ? source : source.Filter(filter);
|
||||
|
||||
public static bool is_empty<T, TKey>(this TableUpdate<T, TKey> table) where TKey : IEquatable<TKey> =>
|
||||
!table.added.Any() && !table.changes.Any() && !table.removed.Any();
|
||||
|
||||
public static void print_incremental_changes<T, TKey>(Update<T, TKey> update, Func<T, TKey> key_fn, Func<T, string> fmt_value, string title) where TKey :IEquatable<TKey> {
|
||||
print_incremental_changes<T, TKey>(update.added, update.changes, update.removed, v => $"{key_fn(v)}: {fmt_value(v)}", title);
|
||||
}
|
||||
|
||||
public static void print_incremental_changes<T, TKey>(TableUpdate<T, TKey> table, Func<T, TKey> key_fn, Func<T, string> fmt_value, string title) where TKey :IEquatable<TKey> {
|
||||
if (fmt_value is null)
|
||||
fmt_value = v => v.ToString();
|
||||
print_incremental_changes<T, TKey>(table.added, table.changes, table.removed, v => $"{key_fn(v)}: {fmt_value(v)}", title);
|
||||
}
|
||||
|
||||
public static void print_incremental_changes<T, TKey>(IEnumerable<T> added, IEnumerable<Change<T>> changes, IEnumerable<T> removed, Func<T, string> fmt, string title) {
|
||||
if (fmt is null)
|
||||
fmt = v => v?.ToString() ?? "(null)";
|
||||
var console_out = new StringBuilder();
|
||||
Console.WriteLine($"Received {title} update");
|
||||
var lines = StringUtils.format_columns(new[] {
|
||||
added.Select(fmt).Prepend(" Added "),
|
||||
changes.Select(ch => ch.New).Select(fmt).Prepend(" Changed "),
|
||||
removed.Select(fmt).Prepend(" Removed ")
|
||||
});
|
||||
foreach (var l in lines)
|
||||
Console.WriteLine("\t" + l);
|
||||
}
|
||||
|
||||
public static void _print_incremental_changes<T, TKey>(IEnumerable<T> added, IEnumerable<Change<T>> changes, IEnumerable<T> removed, Func<T, string> fmt, string title) {
|
||||
if (fmt is null)
|
||||
fmt = v => v?.ToString() ?? "(null)";
|
||||
var console_out = new StringBuilder();
|
||||
console_out.AppendLine($"Received {title} update");
|
||||
using (var added_en = added.GetEnumerator()) {
|
||||
if (added_en.MoveNext()) {
|
||||
console_out.AppendLine(" Added:");
|
||||
do console_out.AppendLine(" " + fmt(added_en.Current));
|
||||
while (added_en.MoveNext());
|
||||
}
|
||||
}
|
||||
using (var changes_en = changes.GetEnumerator()) {
|
||||
if (changes_en.MoveNext()) {
|
||||
console_out.AppendLine(" Changes");
|
||||
do console_out.AppendLine($" {fmt(changes_en.Current.Old)} --> {fmt(changes_en.Current.New)}");
|
||||
while (changes_en.MoveNext());
|
||||
}
|
||||
}
|
||||
using (var removed_en = removed.GetEnumerator()) {
|
||||
if (removed_en.MoveNext()) {
|
||||
console_out.AppendLine(" Removed");
|
||||
do console_out.AppendLine(" " + fmt(removed_en.Current));
|
||||
while (removed_en.MoveNext());
|
||||
}
|
||||
}
|
||||
Console.Write(console_out.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.api.csharp.data;
|
||||
using bts.api.csharp.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
static class WatchAccounts {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="AdminService"/> to subscribe to administrative data (users, accounts, permissions, etc.),
|
||||
/// and displays account updates in the console window.
|
||||
/// </summary>
|
||||
public static IDisposable run(AdminService admin_service) {
|
||||
// extract accounts from the admin data into an IObservable<IReadOnlyDictionary<uint, NameIDPair>>.
|
||||
IObservable<IReadOnlyDictionary<uint, NameIDPair>> accounts_source =
|
||||
admin_service.subscribei_admin_data()
|
||||
.Select(update => {
|
||||
// Note that update.all is an IEnumerable<AdminDataImmutable>,
|
||||
// however, it should only have one element. Pass the first element
|
||||
// to the _map_accounts method, and return the resultant IReadOnlyDictionary.
|
||||
return _map_accounts(update.all.FirstOrDefault());
|
||||
});
|
||||
|
||||
// Use the 'Table' extension method to transform the accounts source from an IObservable<IReadOnlyDictionary<TKey, TValue>>
|
||||
// into an IObservable<TableUpdate<TKey, TValue>> (the latter includes incremental update properties).
|
||||
/// <see cref="TableUpdate{T, TKey}.added"/>
|
||||
/// <see cref="TableUpdate{T, TKey}.changes"/>
|
||||
/// <see cref="TableUpdate{T, TKey}.removed"/>
|
||||
IObservable<TableUpdate<NameIDPair, uint>> incremental_accounts_source =
|
||||
accounts_source.Table(account => account.id);
|
||||
|
||||
// subscribe to the incremental account updates,
|
||||
// and print the changes in the console window.
|
||||
return incremental_accounts_source
|
||||
.ObserveOn(ThreadPoolScheduler.Instance)
|
||||
.Subscribe(upd => UpdateUtils.print_incremental_changes(upd, acc => acc.id, acc => acc.name, "Accounts"));
|
||||
}
|
||||
|
||||
static IReadOnlyDictionary<uint, NameIDPair> _map_accounts(AdminDataImmutable admin_data) =>
|
||||
admin_data.Accounts.ToDictionary(acc => acc.Id, acc => new NameIDPair(acc.Name, acc.Id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using bts.md;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using SecurityKey = System.UInt32;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
static class WatchBooks {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="MarketDataService"/> to observe book updates
|
||||
/// for an optionally filtered list of securities in the system,
|
||||
/// and organize them into a dictionary.
|
||||
/// </summary>
|
||||
public static IDisposable run(MarketDataClient md_client, Func<SecurityImmutable, bool> security_predicate = null) {
|
||||
var books = new Dictionary<SecurityKey, IMDBook>();
|
||||
object locker = new object();
|
||||
if (md_client is null)
|
||||
throw new ArgumentNullException(nameof(md_client));
|
||||
if (security_predicate is null)
|
||||
security_predicate = new Func<SecurityImmutable, bool>(sec => true);
|
||||
/// unlike the other api subscriptions, which publish <see cref="core.Update{T, TKey}"/>
|
||||
/// this subscription publishes an <see cref="IEnumerable{T}"/>, so the updates are not incremental
|
||||
/// (i.e., don't have added/removed/changed items).
|
||||
return md_client.subscribe_book(security_predicate)
|
||||
.ObserveOn(ThreadPoolScheduler.Instance)
|
||||
.Subscribe(on_update);
|
||||
|
||||
void on_update(IEnumerable<IMDBook> new_books) {
|
||||
lock (locker) {
|
||||
foreach (var book in new_books) {
|
||||
Console.WriteLine($"{book.security} book updated: {_fmt_bbo(book)}.");
|
||||
books[book.security.Id] = book;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static string _fmt_bbo(IMDBook book) {
|
||||
return $"{_fmt_price_size(book.bids.FirstOrDefault())} <--> {_fmt_price_size(book.asks.FirstOrDefault())}";
|
||||
}
|
||||
|
||||
static string _fmt_price_size(PriceSize price_size) {
|
||||
return price_size == null ? "N/A" : $"{price_size.size} @ {price_size.price}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using AccountKey = System.UInt32;
|
||||
using OrderKey = System.UInt32;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
static class WatchOrders {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="OrderService"/> to observe order updates, and group them by account.
|
||||
/// Fills and status changes are displayed in the console window.
|
||||
/// </summary>
|
||||
public static IDisposable run(OrderService order_service, Func<OrderRecordImmutable, bool> predicate) {
|
||||
/// 'OrderService' has 2 methods which can be used to subscribe to order updates.
|
||||
/// -------------------------------------------------------------------------------------
|
||||
/// 1) <see cref="OrderService.subscribe_orders(string)"/>.
|
||||
/// 2) <see cref="OrderService.subscribei_orders(string)"/>.
|
||||
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
|
||||
/// --------------------------------------------------------------------------------------
|
||||
var orders_source = order_service.subscribei_orders();
|
||||
if (predicate != null)
|
||||
orders_source = orders_source.Filter(predicate);
|
||||
return run(orders_source.ObserveOn(ThreadPoolScheduler.Instance));
|
||||
}
|
||||
|
||||
public static IDisposable run(IObservable<Update<OrderRecordImmutable, uint>> orders_source) {
|
||||
object locker = new object();
|
||||
var account_order_map = new Dictionary<AccountKey, Dictionary<OrderKey, OrderRecordImmutable>>();
|
||||
return orders_source.Subscribe(on_update);
|
||||
|
||||
void on_order_added(OrderRecordImmutable new_order) {
|
||||
if (!account_order_map.TryGetValue(new_order.Def.AccountId, out var account_orders)) {
|
||||
// this is the first order for this account.
|
||||
// initialize the account orders dictionary, and add it to our data.
|
||||
account_orders = new Dictionary<AccountKey, OrderRecordImmutable>();
|
||||
account_order_map.Add(new_order.Def.AccountId, account_orders);
|
||||
}
|
||||
_print_order_info(new_order, $"observed for the first time.");
|
||||
// now add the order to account_orders
|
||||
account_orders.Add(new_order.Key, new_order);
|
||||
}
|
||||
|
||||
void on_order_changed(Change<OrderRecordImmutable> change) {
|
||||
// ch.New and ch.Old have the same key.
|
||||
Debug.Assert(change.New.Key == change.Old.Key);
|
||||
var key = change.New.Key;
|
||||
// detect fills
|
||||
if (change.New.State.FilledQuantity != change.Old.State.FilledQuantity) {
|
||||
if (change.New.State.FilledQuantity > change.New.State.Quantity) {
|
||||
// order has been overfilled
|
||||
var num_over_fills = change.New.State.FilledQuantity - change.New.State.Quantity;
|
||||
_print_order_info(change.New, $"overfilled by {num_over_fills}.");
|
||||
} else if (change.New.State.FilledQuantity == change.New.State.Quantity) {
|
||||
// order has been filled to completion
|
||||
_print_order_info(change.New, "filled to completion");
|
||||
} else if (change.New.State.Status == OrderStatus.Working) {
|
||||
// order is still working.
|
||||
var working_qty = change.New.State.Quantity - change.New.State.FilledQuantity;
|
||||
_print_order_info(change.New, $"still working {working_qty}");
|
||||
} else {
|
||||
// order has stopped working prematurely (perhaps it was canceled)
|
||||
_print_order_info(change.New, $"is inactive");
|
||||
}
|
||||
}
|
||||
// Account will not change, so our dictionary will definitely have a key for this account.
|
||||
Debug.Assert(account_order_map.ContainsKey(change.New.Def.AccountId));
|
||||
var account_orders = account_order_map[change.New.Def.AccountId];
|
||||
// The value for this key in account_orders should equal ch.Old.
|
||||
Debug.Assert(account_orders[key].Equals(change.Old));
|
||||
// update account orders with the changed order.
|
||||
account_orders[key] = change.New;
|
||||
}
|
||||
|
||||
void on_order_removed(OrderRecordImmutable old_val) {
|
||||
// An entry for this order should be present in our data,
|
||||
// as it is in the 'removed' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(account_order_map.ContainsKey(old_val.Def.AccountId));
|
||||
var account_orders = account_order_map[old_val.Def.AccountId];
|
||||
// the removed value should match the one currently in our data.
|
||||
// Assert this is the case, then remove the order.
|
||||
Debug.Assert(account_orders[old_val.Key].Equals(old_val));
|
||||
account_orders.Remove(old_val.Key);
|
||||
}
|
||||
|
||||
void on_update(Update<OrderRecordImmutable, OrderKey> update) {
|
||||
lock (locker) {
|
||||
foreach (OrderRecordImmutable new_order in update.added) {
|
||||
on_order_added(new_order);
|
||||
}
|
||||
foreach (Change<OrderRecordImmutable> change in update.changes) {
|
||||
on_order_changed(change);
|
||||
}
|
||||
foreach (OrderRecordImmutable old_order in update.removed) {
|
||||
on_order_removed(old_order);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _print_order_info(OrderRecordImmutable order, string message) {
|
||||
Console.WriteLine($"Order {order.Key} ({order.State.Status}, filled {order.State.FilledQuantity}/{order.State.Quantity}): {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
/// <summary>
|
||||
/// A pair of security, and account id,
|
||||
/// which uniquely identifies each PositionUnit.
|
||||
/// </summary>
|
||||
readonly struct PositionKey {
|
||||
public PositionKey(uint security_id, uint account_id) {
|
||||
this.security_id = security_id;
|
||||
this.account_id = account_id;
|
||||
}
|
||||
|
||||
public uint security_id { get; }
|
||||
public uint account_id { get; }
|
||||
|
||||
public static PositionKey from_position(PositionUnitImmutable p) =>
|
||||
new PositionKey(p.SecurityId, p.AccountId);
|
||||
|
||||
public override string ToString() =>
|
||||
$"Sec {security_id}, Acc {account_id}";
|
||||
}
|
||||
|
||||
static class WatchPositions {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="PositionService"/> api to monitor positions
|
||||
/// for every account/security pair, and organize them into a Dictionary.
|
||||
/// </summary>
|
||||
public static IDisposable run(PositionService position_service, Func<PositionUnitImmutable, bool> predicate) {
|
||||
/// 'PositionService' has 2 methods which can be used to subscribe to position updates.
|
||||
/// -------------------------------------------------------------------------------------
|
||||
/// 1) <see cref="PositionService.subscribe_position_data(string)"/>.
|
||||
/// 2) <see cref="PositionService.subscribei_position_data(string)"/>.
|
||||
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
|
||||
/// --------------------------------------------------------------------------------------
|
||||
return run(position_service.subscribei_position_data().Filter(predicate).ObserveOn(ThreadPoolScheduler.Instance));
|
||||
}
|
||||
|
||||
public static IDisposable run(IObservable<Update<PositionUnitImmutable, int>> position_source) {
|
||||
var data = new Dictionary<PositionKey, PositionUnitImmutable>();
|
||||
return position_source.Subscribe(on_update);
|
||||
|
||||
void on_position_added(PositionUnitImmutable new_val) {
|
||||
var k = PositionKey.from_position(new_val);
|
||||
// this entry should NOT be present in our _data, as it is in the 'added' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(!data.ContainsKey(k));
|
||||
print_position_info(k, $"initial position received. Day: Long {new_val.DayLongPos}, Short {new_val.DayShortPos}, Net {new_val.DayLongPos - new_val.DayShortPos}.");
|
||||
// add the new position unit to _data
|
||||
data.Add(k, new_val);
|
||||
}
|
||||
|
||||
void on_position_changed(Change<PositionUnitImmutable> change) {
|
||||
var k = PositionKey.from_position(change.New);
|
||||
// ch.New and ch.Old have the same key (i.e., the Account/Security pair),
|
||||
Debug.Assert(change.New.AccountId == change.Old.AccountId);
|
||||
Debug.Assert(change.New.SecurityId == change.Old.SecurityId);
|
||||
// of course, the value for this key in our dictionary should equal ch.Old.
|
||||
Debug.Assert(data[k].Equals(change.Old));
|
||||
// Display changes to the day position in the console window.
|
||||
// 1) check if the day long changed
|
||||
if (change.New.DayLongPos != change.Old.DayLongPos) {
|
||||
print_position_info(k, $"day long changed from {change.Old.DayLongPos} to {change.New.DayLongPos}.");
|
||||
}
|
||||
// 2) check if day short changed
|
||||
if (change.New.DayShortPos != change.Old.DayShortPos) {
|
||||
print_position_info(k, $"day short changed from {change.Old.DayShortPos} to {change.New.DayShortPos}.");
|
||||
}
|
||||
// 3) check if net day position changed
|
||||
var new_net_day_pos = change.New.DayLongPos - change.New.DayShortPos;
|
||||
var old_net_day_pos = change.Old.DayLongPos - change.Old.DayShortPos;
|
||||
if (new_net_day_pos != old_net_day_pos) {
|
||||
print_position_info(k, $"day net changed from {old_net_day_pos} to {new_net_day_pos}.");
|
||||
}
|
||||
// update _data with the changed position.
|
||||
data[k] = change.New;
|
||||
}
|
||||
|
||||
void on_position_removed(PositionUnitImmutable old_val) {
|
||||
var k = PositionKey.from_position(old_val);
|
||||
// this entry should be present in our _data, as it is in the 'removed' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(data.ContainsKey(k));
|
||||
// this should match our data. Assert this is the case.
|
||||
Debug.Assert(data[k].Equals(old_val));
|
||||
data.Remove(k);
|
||||
}
|
||||
|
||||
void on_update(Update<PositionUnitImmutable, int> update) {
|
||||
foreach (PositionUnitImmutable p in update.added) {
|
||||
on_position_added(p);
|
||||
}
|
||||
foreach (Change<PositionUnitImmutable> ch in update.changes) {
|
||||
on_position_changed(ch);
|
||||
}
|
||||
foreach (PositionUnitImmutable p in update.removed) {
|
||||
on_position_removed(p);
|
||||
}
|
||||
}
|
||||
|
||||
void print_position_info(PositionKey position_key, string message) {
|
||||
Console.WriteLine($"Position {position_key}: {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using bts.api.core;
|
||||
using bts.api.csharp.utils;
|
||||
using System;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
static class WatchSecurities {
|
||||
public static IDisposable run(MarketDataService market_data_service, Func<SecurityImmutable, bool> predicate) {
|
||||
// watch securities
|
||||
var securities = market_data_service.subscribei_securities().Table();
|
||||
if (predicate != null)
|
||||
securities = securities.Filter(predicate);
|
||||
return securities
|
||||
.ObserveOn(ThreadPoolScheduler.Instance)
|
||||
.Subscribe(upd => {
|
||||
if (!upd.is_empty())
|
||||
UpdateUtils.print_incremental_changes(upd, sec => sec.Id, null, "Securities");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using bts.api.core;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using SymbolTableGroup = bts.api.core.TableUpdateGrouping<bts.api.SecurityImmutable, uint, string>;
|
||||
|
||||
namespace bts.api.csharp.watch {
|
||||
static class WatchSymbols {
|
||||
/// <summary>
|
||||
/// Observes securities via <see cref="MarketDataService.subscribei_securities(string)"/>, and prints newly received symbols to the console.
|
||||
/// </summary>
|
||||
/// <returns>An IDisposable to dispose the subscription to the securities source.</returns>
|
||||
public static IDisposable run(MarketDataService market_data_service) {
|
||||
return run(market_data_service.subscribei_securities().Table());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Observes a security update sequence, and prints newly received symbols to the console.
|
||||
/// </summary>
|
||||
/// <returns>An IDisposable to dispose the subscription to the securities source.</returns>
|
||||
public static IDisposable run(IObservable<TableUpdate<SecurityImmutable, uint>> securities_source) {
|
||||
ushort update_count = 0;
|
||||
object locker = new object();
|
||||
return securities_source.GroupTable(sec => sec.Id, sec => sec.Symbol)
|
||||
.ObserveOn(ThreadPoolScheduler.Instance)
|
||||
.Subscribe(on_update);
|
||||
|
||||
void on_update(TableUpdate<SymbolTableGroup, string> group_update) {
|
||||
lock (locker) {
|
||||
var num_new_securities = group_update.added.Concat(group_update.changed).Sum(grp => grp.table.added.Count());
|
||||
|
||||
Console.WriteLine(
|
||||
$"Security update number {++update_count} provided {num_new_securities} new securities " +
|
||||
$"on {group_update.added.Count()} new symbols.");
|
||||
|
||||
var new_symbols = group_update.added
|
||||
.Select(table_group => table_group.group_key)
|
||||
.OrderBy(sym => sym);
|
||||
|
||||
foreach (var new_symbol in new_symbols) {
|
||||
Console.WriteLine("\t" + new_symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,451 +1,421 @@
|
||||
#include "ICEListener.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <vector>
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
using namespace bts::md::ice;
|
||||
|
||||
namespace bts {
|
||||
namespace md {
|
||||
namespace ice {
|
||||
|
||||
template <>
|
||||
double double_of_price<onixs_ice::Rational>(const onixs_ice::Rational& price, int8_t denominator)
|
||||
{
|
||||
UNUSED(denominator);
|
||||
double dbl = price.convertTo<double>();
|
||||
return dbl;
|
||||
}
|
||||
|
||||
template<>
|
||||
void ICEListener::set_security_specific<onixs_ice::FuturesProductDefinition>(const onixs_ice::FuturesProductDefinition& def,
|
||||
FixedSecurity& fsec)
|
||||
{
|
||||
//fsec.cash_per_point = double_of_price(def.tickValue, fsec.ice_price_denominator);
|
||||
//fsec.cash_per_point = def.tickValue;
|
||||
fsec.cash_per_point = def.unitQuantity;
|
||||
}
|
||||
|
||||
ICEListener::ICEListener(ICESecurityIndex& index, MDProducer& producer, const std::vector<MDProducerChannel> channels) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_symbol_filter(),
|
||||
_channels(channels),
|
||||
_concurrency_monitor(0)
|
||||
{
|
||||
}
|
||||
|
||||
ICEListener::~ICEListener() {}
|
||||
|
||||
std::string ICEListener::parse_contract_symbol(const std::string& sym)
|
||||
{
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
//
|
||||
//State change
|
||||
void ICEListener::onHandlerStateChange(const onixs_ice::HandlerStateChange&)
|
||||
{
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , const onixs_ice::MessageInfo&)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Warnings/Errors
|
||||
void ICEListener::onError(const onixs_ice::Error& error)
|
||||
{
|
||||
std::string error_desc(error.description());
|
||||
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
|
||||
}
|
||||
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
|
||||
}
|
||||
void ICEListener::onWarning(const onixs_ice::Warning& warning)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
|
||||
}
|
||||
|
||||
//
|
||||
// Security discovery
|
||||
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
if(msg.isSpread)
|
||||
on_future_strip(msg, is_interested);
|
||||
else
|
||||
on_future(msg, is_interested);
|
||||
LOG(md,Info,"ICE_Future,{},{},{},{},{},{},{},{},{},{},{},{},{}",
|
||||
is_interested,
|
||||
msg.productName, // NG Swing GDD Futures,
|
||||
msg.hubAlias, // Henry
|
||||
msg.marketDesc, // NG Swing GDD Futures - Henry - 10 Jul 20,
|
||||
msg.stripName, // 10 Jul 20
|
||||
msg.contractSymbol, // CGS SDN1020.N1020,
|
||||
msg.productId,
|
||||
msg.marketId,
|
||||
msg.requestMarketType,
|
||||
msg.securitySubType,
|
||||
msg.contractSymbolExtra,
|
||||
msg.clearedAlias,
|
||||
msg.isSpread);
|
||||
}
|
||||
|
||||
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
LOG(md,Info,"ICE_Strategy,{},{},{},{},{},{},{},{},{},{},{},{},{},{}",
|
||||
is_interested,
|
||||
msg.productName,
|
||||
msg.hubAlias,
|
||||
msg.marketDesc,
|
||||
msg.stripName,
|
||||
msg.contractSymbol,
|
||||
msg.productId,
|
||||
msg.marketId,
|
||||
msg.requestMarketType,
|
||||
msg.securitySubType,
|
||||
log::CharPtrWrapper {""},
|
||||
msg.clearedAlias,
|
||||
msg.productName,
|
||||
0);
|
||||
}
|
||||
|
||||
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onOptionsProductDefinition(const onixs_ice::OptionsProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsMarketDefinition(const onixs_ice::NewOptionsMarketDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
void ICEListener::onOptionsStrategyDefinition(const onixs_ice::OptionsStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsStrategyDefinition(const onixs_ice::NewOptionsStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void ICEListener::onMarketSnapshot(const onixs_ice::MarketSnapshot& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
if (msg.settlePriceDateTime > 0) {
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(msg.settlePriceDateTime) );
|
||||
}
|
||||
|
||||
LOG(md,Diag,"OnixSICE Got Market Snap : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), datetime::today() );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
|
||||
data->handle.set_last_trade( ts, pricesize_of_price_size(msg.lastTradePrice, deal_denominator, msg.lastTradeQuantity),
|
||||
ll_of_timestamp(msg.lastTradeDateTime), msg.volume );
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStatistics(const onixs_ice::MarketStatistics& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
data->handle.set_total_volume(msg.volume);
|
||||
}
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotOrder(const onixs_ice::MarketSnapshotOrder& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPriceLevel& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
// Book
|
||||
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
|
||||
|
||||
auto data = _index.get_security(onixs_book.marketId());
|
||||
if (data == nullptr)
|
||||
return;
|
||||
bts_assert(data->id == onixs_book.marketId());
|
||||
auto denominator = data->handle.security().ice_price_denominator;
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = onixs_book.bids().size();
|
||||
size_t asks_size = onixs_book.offers().size();
|
||||
if (!data->handle.has_depth())
|
||||
{
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dbids; dbids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> ibids; ibids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dasks; dasks.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> iasks; iasks.fill(NO_MARKET);
|
||||
for (size_t x = 0; x < bids_size; ++x)
|
||||
{
|
||||
auto& bid = onixs_book.bids()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dbids[x] = pricesize_of_price_size(bid.price, denominator, bid.quantity - bid.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
ibids[x] = pricesize_of_price_size(bid.price, denominator, bid.impliedQuantity);
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x)
|
||||
{
|
||||
auto& ask = onixs_book.offers()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dasks[x] = pricesize_of_price_size(ask.price, denominator, ask.quantity - ask.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
iasks[x] = pricesize_of_price_size(ask.price, denominator, ask.impliedQuantity);
|
||||
}
|
||||
data->handle.update_books(TimeStamp::now(), dbids, dasks, ibids, iasks, (uint32_t) msg_info.sequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Trades
|
||||
AggressorSide map_aggressor(onixs_ice::AggressorSide::Enum agside)
|
||||
{
|
||||
switch(agside) {
|
||||
default:
|
||||
case onixs_ice::AggressorSide::NoAggressor:
|
||||
return AGGRESSOR_SIDE_NONE;
|
||||
case onixs_ice::AggressorSide::Buy:
|
||||
return AGGRESSOR_SIDE_BUY;
|
||||
case onixs_ice::AggressorSide::Sell:
|
||||
return AGGRESSOR_SIDE_SELL;
|
||||
}
|
||||
}
|
||||
|
||||
void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size,
|
||||
onixs_ice::AggressorSide::Enum agside, const onixs_ice::DateTime& onixs_date, bool from_spread)
|
||||
{
|
||||
auto ts = utils::TimeStamp::now();
|
||||
PriceSize ps = pricesize_of_price_size(price, data->handle.security().ice_deal_denominator, size);
|
||||
auto date = ll_of_timestamp(onixs_date);
|
||||
data->handle.add_trade(ts, ps, date, map_aggressor(agside), 0u, from_spread);
|
||||
}
|
||||
|
||||
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != NULL)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto size = msg.quantity;
|
||||
auto price = msg.price;
|
||||
onixs_ice::AggressorSide::Enum agside = msg.aggressorSide;
|
||||
on_trade(data, price, size, agside, msg.transactDateTime, msg.isSystemPricedLeg);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// RFQ
|
||||
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
co2::QuoteRequestMessage message;
|
||||
message.set_key(data->handle.key());
|
||||
|
||||
auto time = ll_of_timestamp(msg.messageTimestamp);
|
||||
message.set_time(time);
|
||||
|
||||
long long rfqid = msg.rfqSystemId;
|
||||
message.set_request_id(std::to_string(rfqid));
|
||||
|
||||
//https://ref.onixs.biz/cpp-ice-impact-handler-guide/struct_onix_s_1_1_i_c_e_1_1i_mpact_1_1_market_data_1_1_side.html#a8150b7776c2a1749101acf22e868d091
|
||||
switch(msg.side)
|
||||
{
|
||||
case ' ':
|
||||
message.set_side(co2::QuoteRequestMessage::UNDEFINED);
|
||||
break;
|
||||
case '1':
|
||||
message.set_side(co2::QuoteRequestMessage::BUY);
|
||||
break;
|
||||
case '2':
|
||||
message.set_side(co2::QuoteRequestMessage::SELL);
|
||||
break;
|
||||
}
|
||||
message.set_size(msg.quantity);
|
||||
data->handle.add_rfq(ts, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto price = msg.settlementPrice;
|
||||
auto time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_settle_denominator;
|
||||
LOG(md,Diag,"OnixSICE Got Settlement: {} - {}", std::to_string(data->id), std::to_string(price));
|
||||
data->handle.update_settlement(ts, convert_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs_ice::MessageInfo &msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.openPrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.closePrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_closing( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(time) );
|
||||
|
||||
LOG(md,Warn,"OnixSICE Got EOD Market : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), date_of_timestamp(time) );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Feed
|
||||
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
UNUSED(feedId);
|
||||
}
|
||||
|
||||
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
|
||||
{
|
||||
UNUSED(feedId);
|
||||
UNUSED(marketIds);
|
||||
}
|
||||
|
||||
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
|
||||
{}
|
||||
|
||||
void ICEListener::onFeedStopped(onixs_ice::FeedId)
|
||||
{}
|
||||
|
||||
void ICEListener::onSessionNumberChange(onixs_ice::FeedId , short , short )
|
||||
{}
|
||||
|
||||
|
||||
//
|
||||
// Non-onixs calls
|
||||
void ICEListener::add_filter_symbol(const std::string& symbol)
|
||||
{
|
||||
LOG(md, Info, "ICEListener::add_filter_symbol: adding {}", symbol);
|
||||
_symbol_filter.insert(symbol);
|
||||
}
|
||||
|
||||
void ICEListener::heartbeat()
|
||||
{
|
||||
for (auto& c : _channels) { c.heartbeat(); }
|
||||
}
|
||||
|
||||
//
|
||||
// Private functions
|
||||
void ICEListener::add_security(const FixedSecurity& security)
|
||||
{
|
||||
// Set up the shared memory for this security
|
||||
auto handle = _producer.add_security(security);
|
||||
bts_assert(security.numeric_id != 0);
|
||||
// Add it to the index so it can receive messages
|
||||
_index.add_security(security.numeric_id, handle);
|
||||
}
|
||||
|
||||
void ICEListener::update_all_status(CoconutSecurityStatus status)
|
||||
{
|
||||
_index.walk_security_list([=](ICESecurityData& data)
|
||||
{
|
||||
data.handle.set_status(status);
|
||||
});
|
||||
}
|
||||
|
||||
}}} //namespace
|
||||
|
||||
|
||||
#include "ICEListener.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <vector>
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
using namespace bts::md::ice;
|
||||
|
||||
namespace bts {
|
||||
namespace md {
|
||||
namespace ice {
|
||||
|
||||
template <>
|
||||
double double_of_price<onixs_ice::Rational>(const onixs_ice::Rational& price, int8_t denominator)
|
||||
{
|
||||
UNUSED(denominator);
|
||||
double dbl = price.convertTo<double>();
|
||||
return dbl;
|
||||
}
|
||||
|
||||
template<>
|
||||
void ICEListener::set_security_specific<onixs_ice::FuturesProductDefinition>(const onixs_ice::FuturesProductDefinition& def,
|
||||
FixedSecurity& fsec)
|
||||
{
|
||||
//fsec.cash_per_point = double_of_price(def.tickValue, fsec.ice_price_denominator);
|
||||
//fsec.cash_per_point = def.tickValue;
|
||||
fsec.cash_per_point = def.unitQuantity;
|
||||
}
|
||||
|
||||
ICEListener::ICEListener(ICESecurityIndex& index, MDProducer& producer, const std::vector<MDProducerChannel> channels) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_symbol_filter(),
|
||||
_channels(channels),
|
||||
_concurrency_monitor(0)
|
||||
{
|
||||
}
|
||||
|
||||
ICEListener::~ICEListener() {}
|
||||
|
||||
std::string ICEListener::parse_contract_symbol(const std::string& sym)
|
||||
{
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
//
|
||||
//State change
|
||||
void ICEListener::onHandlerStateChange(const onixs_ice::HandlerStateChange&)
|
||||
{
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , const onixs_ice::MessageInfo&)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Warnings/Errors
|
||||
void ICEListener::onError(const onixs_ice::Error& error)
|
||||
{
|
||||
std::string error_desc(error.description());
|
||||
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
|
||||
}
|
||||
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
|
||||
}
|
||||
void ICEListener::onWarning(const onixs_ice::Warning& warning)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
|
||||
}
|
||||
|
||||
//
|
||||
// Security discovery
|
||||
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
if(msg.isSpread)
|
||||
on_future_strip(msg, is_interested);
|
||||
else
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& def, bool& is_interested)
|
||||
{
|
||||
on_uds_future_spread(def, is_interested);
|
||||
}
|
||||
//This is only for UDS
|
||||
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onOptionsProductDefinition(const onixs_ice::OptionsProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsMarketDefinition(const onixs_ice::NewOptionsMarketDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
void ICEListener::onOptionsStrategyDefinition(const onixs_ice::OptionsStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsStrategyDefinition(const onixs_ice::NewOptionsStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void ICEListener::onMarketSnapshot(const onixs_ice::MarketSnapshot& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
if (msg.settlePriceDateTime > 0) {
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(msg.settlePriceDateTime) );
|
||||
}
|
||||
|
||||
LOG(md,Diag,"OnixSICE Got Market Snap : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), datetime::today() );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
|
||||
data->handle.set_last_trade( ts, pricesize_of_price_size(msg.lastTradePrice, deal_denominator, msg.lastTradeQuantity),
|
||||
ll_of_timestamp(msg.lastTradeDateTime), msg.volume );
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStatistics(const onixs_ice::MarketStatistics& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
data->handle.set_total_volume(msg.volume);
|
||||
}
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotOrder(const onixs_ice::MarketSnapshotOrder& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPriceLevel& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
// Book
|
||||
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
|
||||
|
||||
auto data = _index.get_security(onixs_book.marketId());
|
||||
if (data == nullptr)
|
||||
return;
|
||||
bts_assert(data->id == onixs_book.marketId());
|
||||
auto denominator = data->handle.security().ice_price_denominator;
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = onixs_book.bids().size();
|
||||
size_t asks_size = onixs_book.offers().size();
|
||||
if (!data->handle.has_depth())
|
||||
{
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dbids; dbids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> ibids; ibids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dasks; dasks.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> iasks; iasks.fill(NO_MARKET);
|
||||
for (size_t x = 0; x < bids_size; ++x)
|
||||
{
|
||||
auto& bid = onixs_book.bids()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dbids[x] = pricesize_of_price_size(bid.price, denominator, bid.quantity - bid.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
ibids[x] = pricesize_of_price_size(bid.price, denominator, bid.impliedQuantity);
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x)
|
||||
{
|
||||
auto& ask = onixs_book.offers()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dasks[x] = pricesize_of_price_size(ask.price, denominator, ask.quantity - ask.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
iasks[x] = pricesize_of_price_size(ask.price, denominator, ask.impliedQuantity);
|
||||
}
|
||||
data->handle.update_books(TimeStamp::now(), dbids, dasks, ibids, iasks, (uint32_t) msg_info.sequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Trades
|
||||
AggressorSide map_aggressor(onixs_ice::AggressorSide::Enum agside)
|
||||
{
|
||||
switch(agside) {
|
||||
default:
|
||||
case onixs_ice::AggressorSide::NoAggressor:
|
||||
return AGGRESSOR_SIDE_NONE;
|
||||
case onixs_ice::AggressorSide::Buy:
|
||||
return AGGRESSOR_SIDE_BUY;
|
||||
case onixs_ice::AggressorSide::Sell:
|
||||
return AGGRESSOR_SIDE_SELL;
|
||||
}
|
||||
}
|
||||
|
||||
void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size,
|
||||
onixs_ice::AggressorSide::Enum agside, const onixs_ice::DateTime& onixs_date, bool from_spread)
|
||||
{
|
||||
auto ts = utils::TimeStamp::now();
|
||||
PriceSize ps = pricesize_of_price_size(price, data->handle.security().ice_deal_denominator, size);
|
||||
auto date = ll_of_timestamp(onixs_date);
|
||||
data->handle.add_trade(ts, ps, date, map_aggressor(agside), 0u, from_spread);
|
||||
}
|
||||
|
||||
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != NULL)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto size = msg.quantity;
|
||||
auto price = msg.price;
|
||||
onixs_ice::AggressorSide::Enum agside = msg.aggressorSide;
|
||||
on_trade(data, price, size, agside, msg.transactDateTime, msg.isSystemPricedLeg);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// RFQ
|
||||
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
co2::QuoteRequestMessage message;
|
||||
message.set_key(data->handle.key());
|
||||
|
||||
auto time = ll_of_timestamp(msg.messageTimestamp);
|
||||
message.set_time(time);
|
||||
|
||||
long long rfqid = msg.rfqSystemId;
|
||||
message.set_request_id(std::to_string(rfqid));
|
||||
|
||||
//https://ref.onixs.biz/cpp-ice-impact-handler-guide/struct_onix_s_1_1_i_c_e_1_1i_mpact_1_1_market_data_1_1_side.html#a8150b7776c2a1749101acf22e868d091
|
||||
switch(msg.side)
|
||||
{
|
||||
case ' ':
|
||||
message.set_side(co2::QuoteRequestMessage::UNDEFINED);
|
||||
break;
|
||||
case '1':
|
||||
message.set_side(co2::QuoteRequestMessage::BUY);
|
||||
break;
|
||||
case '2':
|
||||
message.set_side(co2::QuoteRequestMessage::SELL);
|
||||
break;
|
||||
}
|
||||
message.set_size(msg.quantity);
|
||||
data->handle.add_rfq(ts, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto price = msg.settlementPrice;
|
||||
auto time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_settle_denominator;
|
||||
LOG(md,Diag,"OnixSICE Got Settlement: {} - {}", std::to_string(data->id), std::to_string(price));
|
||||
data->handle.update_settlement(ts, convert_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs_ice::MessageInfo &msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.openPrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.closePrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_closing( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(time) );
|
||||
|
||||
LOG(md,Warn,"OnixSICE Got EOD Market : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), date_of_timestamp(time) );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Feed
|
||||
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
UNUSED(feedId);
|
||||
}
|
||||
|
||||
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
|
||||
{
|
||||
UNUSED(feedId);
|
||||
UNUSED(marketIds);
|
||||
}
|
||||
|
||||
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
|
||||
{}
|
||||
|
||||
void ICEListener::onFeedStopped(onixs_ice::FeedId)
|
||||
{}
|
||||
|
||||
void ICEListener::onSessionNumberChange(onixs_ice::FeedId , short , short )
|
||||
{}
|
||||
|
||||
|
||||
//
|
||||
// Non-onixs calls
|
||||
void ICEListener::add_filter_symbol(const std::string& symbol)
|
||||
{
|
||||
_symbol_filter.insert(symbol);
|
||||
}
|
||||
|
||||
void ICEListener::heartbeat()
|
||||
{
|
||||
for (auto& c : _channels) { c.heartbeat(); }
|
||||
}
|
||||
|
||||
//
|
||||
// Private functions
|
||||
void ICEListener::add_security(const FixedSecurity& security)
|
||||
{
|
||||
// Set up the shared memory for this security
|
||||
auto handle = _producer.add_security(security);
|
||||
bts_assert(security.numeric_id != 0);
|
||||
// Add it to the index so it can receive messages
|
||||
_index.add_security(security.numeric_id, handle);
|
||||
}
|
||||
|
||||
void ICEListener::update_all_status(CoconutSecurityStatus status)
|
||||
{
|
||||
_index.walk_security_list([=](ICESecurityData& data)
|
||||
{
|
||||
data.handle.set_status(status);
|
||||
});
|
||||
}
|
||||
|
||||
}}} //namespace
|
||||
|
||||
|
||||
@@ -119,25 +119,7 @@ private:
|
||||
enum SecurityFilterAction { ADD, SKIP, DELETE };
|
||||
|
||||
std::string parse_contract_symbol(const std::string& sym);
|
||||
template<typename Definition>
|
||||
std::string get_contract_symbol(const Definition& def){
|
||||
const auto& sym = def.contractSymbol;
|
||||
// TODO : this is only exception right now but for HHD sstrips
|
||||
// def.contractSymbol is empty !
|
||||
if (sym.size() == 0 &&
|
||||
(
|
||||
def.marketDesc.rfind("NG Swing GDD Futures - Henry", 0) == 0 ||
|
||||
def.marketDesc.rfind("NG GDD for LD1 Futures Spr - Henry", 0) == 0
|
||||
))
|
||||
return "HHD";
|
||||
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
|
||||
template<typename Definition>
|
||||
SecurityFilterAction filter_security(int exchange_id, const Definition& def)
|
||||
{
|
||||
@@ -148,7 +130,7 @@ private:
|
||||
}
|
||||
if(_symbol_filter.count("*") > 0)
|
||||
return ADD;
|
||||
return _symbol_filter.count(get_contract_symbol(def)) > 0 ? ADD : SKIP;
|
||||
return _symbol_filter.count( parse_contract_symbol(def.contractSymbol) ) > 0 ? ADD : SKIP;
|
||||
}
|
||||
void flush_books();
|
||||
void update_all_status(co2::CoconutSecurityStatus status);
|
||||
@@ -164,7 +146,7 @@ template<typename Definition>
|
||||
void ICEListener::set_security_common(const Definition& def, FixedSecurity& fsec)
|
||||
{
|
||||
fsec.numeric_id = def.marketId;
|
||||
std::string sym = get_contract_symbol(def);
|
||||
std::string sym = parse_contract_symbol(def.contractSymbol);
|
||||
if(!sym.empty())
|
||||
{
|
||||
fsec.symbol( sym );
|
||||
@@ -183,7 +165,7 @@ void ICEListener::set_security_common(const Definition& def, FixedSecurity& fsec
|
||||
fsec.ice_settle_denominator = (int8_t)(def.settlePriceDenominator - '0');
|
||||
double min_tick = double_of_price(def.incrementPrice, fsec.ice_price_denominator);
|
||||
fsec.low_tick = fsec.high_tick = min_tick;
|
||||
fsec.price_break = 0.0;
|
||||
fsec.price_break = 0.0;
|
||||
|
||||
fsec.ice_market_description(def.marketDesc);
|
||||
fsec.ice_cleared_alias(def.clearedAlias);
|
||||
@@ -225,12 +207,12 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
|
||||
// auto expiration = date_of_maturity(sec);
|
||||
// if (expiration < datetime::today()) return;
|
||||
|
||||
FixedSpreadSecurityXL fsec;
|
||||
FixedSpreadSecurityL fsec;
|
||||
set_security_common(def, fsec);
|
||||
fsec.alt_symbol(def.strategySymbol);
|
||||
|
||||
const auto& legs = def.legs;
|
||||
if (legs.size() > 0 && legs.size() <= 512) {
|
||||
const auto& legs = def.legs;
|
||||
if (legs.size() > 0) {
|
||||
fsec.set_leg_count(legs.size());
|
||||
for(size_t x = 0; x < fsec.leg_count(); ++x)
|
||||
{
|
||||
@@ -256,8 +238,6 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
|
||||
add_security(fsec);
|
||||
is_interested = true;
|
||||
}
|
||||
else
|
||||
LOG(md, Error, "Ignoring security {} with {} legs", def.marketDesc, legs.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -268,7 +248,7 @@ void ICEListener::on_future_strip(const FutureDefinition& def, bool& is_interest
|
||||
auto exchange_id = def.marketId;
|
||||
if (filter_security(exchange_id, def) != ADD) return;
|
||||
|
||||
FixedSpreadSecurityXL fsec;
|
||||
FixedSpreadSecurityXS fsec;
|
||||
set_security_common(def, fsec);
|
||||
fsec.alt_symbol(def.stripName);
|
||||
|
||||
|
||||
@@ -73,17 +73,31 @@ onixs_ice::MarketSubscriptions market_subcription_for_channel(const ICEChannel&
|
||||
onixs_ice::MarketSubscriptions subs;
|
||||
for(const auto& x : chan.market_types)
|
||||
{
|
||||
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(futures);
|
||||
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
//onixs_ice::MarketSubType::NonImplied
|
||||
if(x == 26)
|
||||
{
|
||||
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(futures);
|
||||
}
|
||||
else
|
||||
{
|
||||
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::FullImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(futures);
|
||||
}
|
||||
}
|
||||
for(const auto& x : chan.uds_market_types)
|
||||
{
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
if(x == 26)
|
||||
{
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
else
|
||||
{
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::FullImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
}
|
||||
return subs;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,6 @@ struct PositionServicePNLHelper::PImpl {
|
||||
void PositionServicePNLHelper::compute_pnl(
|
||||
const std::map<int, PositionUnit const*>& in_pos,
|
||||
const std::map<std::string,uint32_t>& account_map,
|
||||
const std::map<uint32_t,double>& cpp_override_map,
|
||||
std::vector<PNLUnit>& out_pnl,
|
||||
std::vector<AccountPNL>& out_account_pnl){
|
||||
out_account_pnl.clear();
|
||||
@@ -146,8 +145,6 @@ void PositionServicePNLHelper::compute_pnl(
|
||||
const PositionUnit& pu = *(p.second);
|
||||
uint32_t sec_key = pu.security_id();
|
||||
double cpp = me->handle_index.get_cpp(SecurityKey(sec_key));
|
||||
auto it = cpp_override_map.find(sec_key);
|
||||
cpp = (it != cpp_override_map.end()) ? it->second : cpp;
|
||||
double bid, ask, last, settle;
|
||||
me->handle_index.get_md(SecurityKey(sec_key),bid,ask,last,settle);
|
||||
PNLUnit pnlu;
|
||||
|
||||
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user