Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
888b4e22c0 | ||
|
|
534ce5a8ec | ||
|
|
c2b9ac57dc | ||
|
|
2783361325 | ||
|
|
46d35131d7 | ||
|
|
3b95154c04 | ||
|
|
1a6b918853 | ||
|
|
bc22605a25 | ||
|
|
f3d28add1c | ||
|
|
63d260a6f8 | ||
|
|
9b4a63b4a8 |
No files matched your search
+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.13.0.0 ${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/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: 0df20ec20a...296f277d40.
+1
-1
Submodule externals updated: a558597a1a...2ea5e355a6.
@@ -1,370 +1,372 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM {
|
||||
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
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;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
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());
|
||||
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;
|
||||
|
||||
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), 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), 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), 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), 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));
|
||||
bid_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidMergedSize));
|
||||
bid_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidDirectSize));
|
||||
bid_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidImpliedSize));
|
||||
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids));
|
||||
ask_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Ask));
|
||||
ask_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskMergedSize));
|
||||
ask_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskDirectSize));
|
||||
ask_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskImpliedSize));
|
||||
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks));
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_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)))
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
#region Bound Properties
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
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 double bid_price => _bid_price.Value;
|
||||
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.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 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, _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;
|
||||
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
|
||||
public ICommand bid_price_click { get; }
|
||||
public ICommand bid_merged_size_click { get; }
|
||||
public ICommand bid_direct_size_click { get; }
|
||||
public ICommand bid_implied_size_click { get; }
|
||||
public ICommand working_bids_click { get; }
|
||||
public ICommand ask_price_click { get; }
|
||||
public ICommand ask_merged_size_click { get; }
|
||||
public ICommand ask_direct_size_click { get; }
|
||||
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; }
|
||||
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM {
|
||||
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
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;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
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());
|
||||
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;
|
||||
|
||||
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), 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), 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), 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), 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));
|
||||
bid_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidMergedSize));
|
||||
bid_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidDirectSize));
|
||||
bid_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidImpliedSize));
|
||||
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids));
|
||||
ask_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Ask));
|
||||
ask_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskMergedSize));
|
||||
ask_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskDirectSize));
|
||||
ask_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskImpliedSize));
|
||||
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks));
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_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)))
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
#region Bound Properties
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
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 double bid_price => _bid_price.Value;
|
||||
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.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 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, _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>();
|
||||
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
#endregion
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_price_from_spread.set(from_spread);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
dirty |= _settlement.set_new(md.md.settlement_price);
|
||||
dirty |= _session_low.set_new(md.md.session_low);
|
||||
dirty |= _session_high.set_new(md.md.session_high);
|
||||
dirty |= _day_change.set_new(last_price - settlement);
|
||||
_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) =>
|
||||
other != null &&
|
||||
other.security_key == security_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as Row);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
security_key.GetHashCode();
|
||||
|
||||
#region Show Windows
|
||||
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book(MarketViewView.Columns col) {
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.WorkingBids: _show_order_book(Side.Buy); break;
|
||||
case MarketViewView.Columns.WorkingAsks: _show_order_book(Side.Sell); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_order_book(Side side) {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
void _show_order_ticket(MarketViewView.Columns col) {
|
||||
var order = _orders_model.factory.order(security);
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.Bid:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.BidMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Ask:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.AskMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, ask_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Last:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.LastSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, last_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Callbacks
|
||||
void _on_orders(ITableUpdateTransform<OrderRecordImmutable, uint> orders) {
|
||||
foreach (var o in orders.removed)
|
||||
_order_summarizer.remove_order(o);
|
||||
foreach (var o in orders.added)
|
||||
_order_summarizer.add_order(o);
|
||||
bool dirty = false;
|
||||
_refresh_orders(ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
}
|
||||
|
||||
void _refresh_orders(ref bool dirty) {
|
||||
_position.on_next_distinct(_order_summarizer.position, ref dirty);
|
||||
_working_bid_volume.on_next_distinct(_order_summarizer.working_bid_qty, ref dirty);
|
||||
_working_ask_volume.on_next_distinct(_order_summarizer.working_ask_qty, ref dirty);
|
||||
_working_best_bid.on_next_distinct(_order_summarizer.best_working_bid, ref dirty);
|
||||
_working_best_ask.on_next_distinct(_order_summarizer.best_working_ask, ref dirty);
|
||||
_inside_bids.on_next_distinct(_order_summarizer.best_working_bid >= bid_price, ref dirty);
|
||||
_inside_asks.on_next_distinct(_order_summarizer.best_working_ask <= ask_price, ref dirty);
|
||||
|
||||
RaisePropertyChanged(
|
||||
// Implied might not be ever set, so it probably does not RaisePropertyChanged either
|
||||
// This might be part of the filter funk?
|
||||
//nameof(bid_implied_size),
|
||||
//nameof(ask_implied_size),
|
||||
nameof(working_bids_text),
|
||||
nameof(working_asks_text)
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
|
||||
class MyOrderSummarizer {
|
||||
SortedSetMap<double, OrderRecordImmutable> _bids, _asks;
|
||||
int _best_bid_working_qty, _best_ask_working_qty, _position, _working_bid_qty, _working_ask_qty;
|
||||
|
||||
public MyOrderSummarizer() {
|
||||
_bids = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.bid_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
_asks = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.ask_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
}
|
||||
|
||||
// bid
|
||||
public double best_working_bid => _bids.Count > 0 ? _bids.at_index(0).Key : double.NaN;
|
||||
public int best_working_bid_qty => _best_bid_working_qty;
|
||||
public int working_bid_qty => _working_bid_qty;
|
||||
// ask
|
||||
public double best_working_ask => _asks.Count > 0 ? _asks.at_index(0).Key : double.NaN;
|
||||
public int best_working_ask_qty => _best_ask_working_qty;
|
||||
public int working_ask_qty => _working_ask_qty;
|
||||
// pos
|
||||
public int position => _position;
|
||||
|
||||
public void add_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty += o.working_quantity();
|
||||
_bids.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty += o.working_quantity();
|
||||
_asks.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
public void remove_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty -= o.working_quantity();
|
||||
_bids.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty -= o.working_quantity();
|
||||
_asks.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
void _refresh() {
|
||||
_best_bid_working_qty = _bids.Count > 0 ? _bids.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
_best_ask_working_qty = _asks.Count > 0 ? _asks.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// commands
|
||||
public ICommand bid_price_click { get; }
|
||||
public ICommand bid_merged_size_click { get; }
|
||||
public ICommand bid_direct_size_click { get; }
|
||||
public ICommand bid_implied_size_click { get; }
|
||||
public ICommand working_bids_click { get; }
|
||||
public ICommand ask_price_click { get; }
|
||||
public ICommand ask_merged_size_click { get; }
|
||||
public ICommand ask_direct_size_click { get; }
|
||||
public ICommand ask_implied_size_click { get; }
|
||||
public ICommand working_asks_click { get; }
|
||||
public ICommand default_command { get; }
|
||||
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
#endregion
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_price_from_spread.set(from_spread);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
dirty |= _settlement.set_new(md.md.settlement_price);
|
||||
dirty |= _session_low.set_new(md.md.session_low);
|
||||
dirty |= _session_high.set_new(md.md.session_high);
|
||||
dirty |= _day_change.set_new(last_price - settlement);
|
||||
_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) =>
|
||||
other != null &&
|
||||
other.security_key == security_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as Row);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
security_key.GetHashCode();
|
||||
|
||||
#region Show Windows
|
||||
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book(MarketViewView.Columns col) {
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.WorkingBids: _show_order_book(Side.Buy); break;
|
||||
case MarketViewView.Columns.WorkingAsks: _show_order_book(Side.Sell); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_order_book(Side side) {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
void _show_order_ticket(MarketViewView.Columns col) {
|
||||
var order = _orders_model.factory.order(security);
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.Bid:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.BidMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Ask:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.AskMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, ask_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Last:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.LastSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, last_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Callbacks
|
||||
void _on_orders(ITableUpdateTransform<OrderRecordImmutable, uint> orders) {
|
||||
foreach (var o in orders.removed)
|
||||
_order_summarizer.remove_order(o);
|
||||
foreach (var o in orders.added)
|
||||
_order_summarizer.add_order(o);
|
||||
bool dirty = false;
|
||||
_refresh_orders(ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
}
|
||||
|
||||
void _refresh_orders(ref bool dirty) {
|
||||
_position.on_next_distinct(_order_summarizer.position, ref dirty);
|
||||
_working_bid_volume.on_next_distinct(_order_summarizer.working_bid_qty, ref dirty);
|
||||
_working_ask_volume.on_next_distinct(_order_summarizer.working_ask_qty, ref dirty);
|
||||
_working_best_bid.on_next_distinct(_order_summarizer.best_working_bid, ref dirty);
|
||||
_working_best_ask.on_next_distinct(_order_summarizer.best_working_ask, ref dirty);
|
||||
_inside_bids.on_next_distinct(_order_summarizer.best_working_bid >= bid_price, ref dirty);
|
||||
_inside_asks.on_next_distinct(_order_summarizer.best_working_ask <= ask_price, ref dirty);
|
||||
|
||||
RaisePropertyChanged(
|
||||
// Implied might not be ever set, so it probably does not RaisePropertyChanged either
|
||||
// This might be part of the filter funk?
|
||||
//nameof(bid_implied_size),
|
||||
//nameof(ask_implied_size),
|
||||
nameof(working_bids_text),
|
||||
nameof(working_asks_text)
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
|
||||
class MyOrderSummarizer {
|
||||
SortedSetMap<double, OrderRecordImmutable> _bids, _asks;
|
||||
int _best_bid_working_qty, _best_ask_working_qty, _position, _working_bid_qty, _working_ask_qty;
|
||||
|
||||
public MyOrderSummarizer() {
|
||||
_bids = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.bid_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
_asks = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.ask_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
}
|
||||
|
||||
// bid
|
||||
public double best_working_bid => _bids.Count > 0 ? _bids.at_index(0).Key : double.NaN;
|
||||
public int best_working_bid_qty => _best_bid_working_qty;
|
||||
public int working_bid_qty => _working_bid_qty;
|
||||
// ask
|
||||
public double best_working_ask => _asks.Count > 0 ? _asks.at_index(0).Key : double.NaN;
|
||||
public int best_working_ask_qty => _best_ask_working_qty;
|
||||
public int working_ask_qty => _working_ask_qty;
|
||||
// pos
|
||||
public int position => _position;
|
||||
|
||||
public void add_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty += o.working_quantity();
|
||||
_bids.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty += o.working_quantity();
|
||||
_asks.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
public void remove_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty -= o.working_quantity();
|
||||
_bids.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty -= o.working_quantity();
|
||||
_asks.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
void _refresh() {
|
||||
_best_bid_working_qty = _bids.Count > 0 ? _bids.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
_best_ask_working_qty = _asks.Count > 0 ? _asks.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,286 +1,294 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
|
||||
|
||||
// securities_connectable should NOT be connected already.
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
_settings_manager = settings_manager;
|
||||
// Columns
|
||||
const string merged_size_col_header = "Qty", direct_size_col_header = "Direct", implied_size_col_header = "Implied", working_col_header = "Working";
|
||||
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.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.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.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.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);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.security.cleared_alias(), nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ProductDescription, "Product Description", r => r.security.product_name(), nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
Width = width,
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
|
||||
font_color_override = font_color,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
BindingBase price_binding(string prop) {
|
||||
var mb = new MultiBinding { Converter = price_converter };
|
||||
mb.Bindings.Add(new Binding(prop) { Mode = BindingMode.OneWay });
|
||||
mb.Bindings.Add(new Binding($"{nameof(Row.security)}.{nameof(SecurityImmutable.ContractSpec)}") { Mode = BindingMode.OneWay });
|
||||
return mb;
|
||||
}
|
||||
//
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
if (s.grid != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
if (s.filters is FilterStates filters) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
|
||||
}
|
||||
}), 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)
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public static IComparer<Row> get_column_comparer(Columns column) {
|
||||
switch (column) {
|
||||
case Columns.Security: return wrap(r => r.security);
|
||||
case Columns.SecurityKey: return wrap(r => r.security.Id);
|
||||
case Columns.Volume: return wrap(r => r.volume);
|
||||
case Columns.Last: return wrap(r => r.last_price);
|
||||
case Columns.LastSize: return wrap(r => r.last_size);
|
||||
case Columns.Position: return wrap(r => r.position);
|
||||
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
|
||||
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
|
||||
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
|
||||
case Columns.BidMergedSize: return wrap(r => r.bid_merged_size);
|
||||
case Columns.Bid: return wrap(r => r.bid_price);
|
||||
case Columns.Ask: return wrap(r => r.ask_price);
|
||||
case Columns.AskMergedSize: return wrap(r => r.ask_merged_size);
|
||||
case Columns.AskDirectSize: return wrap(r => r.ask_direct_size);
|
||||
case Columns.AskImpliedSize: return wrap(r => r.ask_implied_size);
|
||||
case Columns.WorkingAsks: return wrap(r => r.working_ask_volume);
|
||||
case Columns.Settlement: return wrap(r => r.settlement);
|
||||
case Columns.DayChange: return wrap(r => r.day_change);
|
||||
case Columns.SessionLow: return wrap(r => r.session_low);
|
||||
case Columns.SessionHigh: return wrap(r => r.session_high);
|
||||
case Columns.MarketDescription: return wrap(r => r.market_description);
|
||||
case Columns.ClearedAlias: return wrap(r => r.security.cleared_alias());
|
||||
case Columns.ProductDescription: return wrap(r => r.security.product_name());
|
||||
case Columns.HubAlias: return wrap(r => r.security.hub_alias());
|
||||
case Columns.StripName: return wrap(r => r.security.strip_name());
|
||||
case Columns.MicCode: return wrap(r => r.security.mic_code());
|
||||
default: return null;
|
||||
}
|
||||
IComparer<Row> wrap<T>(Func<Row, T> selector) =>
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
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.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 {
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_merged_size = bid_merged_size_filter.get_state(),
|
||||
bid_direct_size = bid_direct_size_filter.get_state(),
|
||||
bid_implied_size = bid_implied_size_filter.get_state(),
|
||||
ask_price = ask_price_filter.get_state(),
|
||||
ask_merged_size = ask_merged_size_filter.get_state(),
|
||||
ask_direct_size = ask_direct_size_filter.get_state(),
|
||||
ask_implied_size = ask_implied_size_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
last_price_from_spread = last_price_from_spread_filter.get_state(),
|
||||
last_size = last_size_filter.get_state(),
|
||||
day_change = day_change_filter.get_state(),
|
||||
settlement = settlement_filter.get_state(),
|
||||
session_low = session_low_filter.get_state(),
|
||||
session_high = session_high_filter.get_state(),
|
||||
position = position_filter.get_state(),
|
||||
raw_day_position = raw_day_position_filter.get_state(),
|
||||
working_bid_volume = working_bid_volume_filter.get_state(),
|
||||
working_ask_volume = working_ask_volume_filter.get_state(),
|
||||
volume = volume_filter.get_state(),
|
||||
security_key = security_key_filter.get_state(),
|
||||
market_description = market_description_filter.get_state(),
|
||||
cleared_alias = cleared_alias_filter.get_state(),
|
||||
product_description = product_description_filter.get_state(),
|
||||
hub_alias = hub_alias_filter.get_state(),
|
||||
strip_name = strip_name_filter.get_state(),
|
||||
mic_code = mic_code_filter.get_state(),
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
|
||||
|
||||
void _refresh() {
|
||||
foreach (var r in _row_vms) r.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
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;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
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;
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
|
||||
|
||||
// securities_connectable should NOT be connected already.
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
_settings_manager = settings_manager;
|
||||
// Columns
|
||||
const string merged_size_col_header = "Qty", direct_size_col_header = "Direct", implied_size_col_header = "Implied", working_col_header = "Working";
|
||||
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.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.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.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.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);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.security.cleared_alias(), nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ProductDescription, "Product Description", r => r.security.product_name(), nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
Width = width,
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
|
||||
font_color_override = font_color,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
BindingBase price_binding(string prop) {
|
||||
var mb = new MultiBinding { Converter = price_converter };
|
||||
mb.Bindings.Add(new Binding(prop) { Mode = BindingMode.OneWay });
|
||||
mb.Bindings.Add(new Binding($"{nameof(Row.security)}.{nameof(SecurityImmutable.ContractSpec)}") { Mode = BindingMode.OneWay });
|
||||
return mb;
|
||||
}
|
||||
//
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
if (s.grid != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
if (s.filters is FilterStates filters) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
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),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public static IComparer<Row> get_column_comparer(Columns column) {
|
||||
switch (column) {
|
||||
case Columns.Security: return wrap(r => r.security);
|
||||
case Columns.SecurityKey: return wrap(r => r.security.Id);
|
||||
case Columns.Volume: return wrap(r => r.volume);
|
||||
case Columns.Last: return wrap(r => r.last_price);
|
||||
case Columns.LastSize: return wrap(r => r.last_size);
|
||||
case Columns.Position: return wrap(r => r.position);
|
||||
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
|
||||
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
|
||||
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
|
||||
case Columns.BidMergedSize: return wrap(r => r.bid_merged_size);
|
||||
case Columns.Bid: return wrap(r => r.bid_price);
|
||||
case Columns.Ask: return wrap(r => r.ask_price);
|
||||
case Columns.AskMergedSize: return wrap(r => r.ask_merged_size);
|
||||
case Columns.AskDirectSize: return wrap(r => r.ask_direct_size);
|
||||
case Columns.AskImpliedSize: return wrap(r => r.ask_implied_size);
|
||||
case Columns.WorkingAsks: return wrap(r => r.working_ask_volume);
|
||||
case Columns.Settlement: return wrap(r => r.settlement);
|
||||
case Columns.DayChange: return wrap(r => r.day_change);
|
||||
case Columns.SessionLow: return wrap(r => r.session_low);
|
||||
case Columns.SessionHigh: return wrap(r => r.session_high);
|
||||
case Columns.MarketDescription: return wrap(r => r.market_description);
|
||||
case Columns.ClearedAlias: return wrap(r => r.security.cleared_alias());
|
||||
case Columns.ProductDescription: return wrap(r => r.security.product_name());
|
||||
case Columns.HubAlias: return wrap(r => r.security.hub_alias());
|
||||
case Columns.StripName: return wrap(r => r.security.strip_name());
|
||||
case Columns.MicCode: return wrap(r => r.security.mic_code());
|
||||
default: return null;
|
||||
}
|
||||
IComparer<Row> wrap<T>(Func<Row, T> selector) =>
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
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 FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_merged_size = bid_merged_size_filter.get_state(),
|
||||
bid_direct_size = bid_direct_size_filter.get_state(),
|
||||
bid_implied_size = bid_implied_size_filter.get_state(),
|
||||
ask_price = ask_price_filter.get_state(),
|
||||
ask_merged_size = ask_merged_size_filter.get_state(),
|
||||
ask_direct_size = ask_direct_size_filter.get_state(),
|
||||
ask_implied_size = ask_implied_size_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
last_price_from_spread = last_price_from_spread_filter.get_state(),
|
||||
last_size = last_size_filter.get_state(),
|
||||
day_change = day_change_filter.get_state(),
|
||||
settlement = settlement_filter.get_state(),
|
||||
session_low = session_low_filter.get_state(),
|
||||
session_high = session_high_filter.get_state(),
|
||||
position = position_filter.get_state(),
|
||||
raw_day_position = raw_day_position_filter.get_state(),
|
||||
working_bid_volume = working_bid_volume_filter.get_state(),
|
||||
working_ask_volume = working_ask_volume_filter.get_state(),
|
||||
volume = volume_filter.get_state(),
|
||||
security_key = security_key_filter.get_state(),
|
||||
market_description = market_description_filter.get_state(),
|
||||
cleared_alias = cleared_alias_filter.get_state(),
|
||||
product_description = product_description_filter.get_state(),
|
||||
hub_alias = hub_alias_filter.get_state(),
|
||||
strip_name = strip_name_filter.get_state(),
|
||||
mic_code = mic_code_filter.get_state(),
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
|
||||
|
||||
void _refresh() {
|
||||
foreach (var r in _row_vms) r.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ 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
|
||||
|
||||
@@ -1,39 +1,43 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
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(" ", ""))));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,13 +39,18 @@ namespace bts.api {
|
||||
|
||||
static string _format(SecurityImmutable? security) {
|
||||
switch (security?.ContractCase) {
|
||||
case ContractOneofCase.Future: return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
|
||||
case ContractOneofCase.Future:
|
||||
if(security?.HasIce == true)
|
||||
return _format_ICE(security);
|
||||
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";
|
||||
@@ -79,5 +84,26 @@ 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";
|
||||
}
|
||||
type_append = string.IsNullOrEmpty(type_append) ?
|
||||
" " : $" {type_append} ";
|
||||
return $"{symbol}{type_append}{strip}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<server name="default">
|
||||
<ip>63.247.113.163</ip>
|
||||
<port>3000</port>
|
||||
<userName>bluetrd-ps</userName>
|
||||
<userName>katas2f-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>Value from ICE Support</userName>
|
||||
<password>Value from ICE Support</password>
|
||||
<userName>katas2f-ps</userName>
|
||||
<password>Starts123</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
<localNetworkInterface></localNetworkInterface>
|
||||
|
||||
@@ -105,14 +105,35 @@ 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 set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
|
||||
rpc update_security_parameters (SecurityParametersBatchOp) returns (SecurityParametersBatchOpResponse);
|
||||
|
||||
// subscription
|
||||
rpc manual_fills(bts.api.VoidMessage) returns (stream ManualFill);
|
||||
rpc position_data(bts.api.VoidMessage) returns (stream PositionUnit);
|
||||
|
||||
@@ -1,136 +1,130 @@
|
||||
#
|
||||
# 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
|
||||
}
|
||||
}
|
||||
}
|
||||
#
|
||||
# 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,10 @@ 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 const &native_state = get_native_state(me->state);
|
||||
auto &native_state = get_native_state(me->state);
|
||||
switch (me->state.status()) {
|
||||
|
||||
case ORDER_STATUS_WORKING:
|
||||
@@ -202,6 +202,8 @@ 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,13 +215,6 @@ 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,421 +1,451 @@
|
||||
#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
|
||||
|
||||
|
||||
#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
|
||||
|
||||
|
||||
@@ -119,7 +119,20 @@ 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)
|
||||
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)
|
||||
{
|
||||
@@ -130,7 +143,7 @@ private:
|
||||
}
|
||||
if(_symbol_filter.count("*") > 0)
|
||||
return ADD;
|
||||
return _symbol_filter.count( parse_contract_symbol(def.contractSymbol) ) > 0 ? ADD : SKIP;
|
||||
return _symbol_filter.count(get_contract_symbol(def)) > 0 ? ADD : SKIP;
|
||||
}
|
||||
void flush_books();
|
||||
void update_all_status(co2::CoconutSecurityStatus status);
|
||||
@@ -146,7 +159,7 @@ template<typename Definition>
|
||||
void ICEListener::set_security_common(const Definition& def, FixedSecurity& fsec)
|
||||
{
|
||||
fsec.numeric_id = def.marketId;
|
||||
std::string sym = parse_contract_symbol(def.contractSymbol);
|
||||
std::string sym = get_contract_symbol(def);
|
||||
if(!sym.empty())
|
||||
{
|
||||
fsec.symbol( sym );
|
||||
@@ -165,7 +178,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);
|
||||
@@ -207,12 +220,12 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
|
||||
// auto expiration = date_of_maturity(sec);
|
||||
// if (expiration < datetime::today()) return;
|
||||
|
||||
FixedSpreadSecurityL fsec;
|
||||
FixedSpreadSecurityXL fsec;
|
||||
set_security_common(def, fsec);
|
||||
fsec.alt_symbol(def.strategySymbol);
|
||||
|
||||
const auto& legs = def.legs;
|
||||
if (legs.size() > 0) {
|
||||
const auto& legs = def.legs;
|
||||
if (legs.size() > 0 && legs.size() <= 512) {
|
||||
fsec.set_leg_count(legs.size());
|
||||
for(size_t x = 0; x < fsec.leg_count(); ++x)
|
||||
{
|
||||
@@ -238,6 +251,8 @@ 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());
|
||||
}
|
||||
|
||||
|
||||
@@ -248,7 +263,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;
|
||||
|
||||
FixedSpreadSecurityXS fsec;
|
||||
FixedSpreadSecurityXL fsec;
|
||||
set_security_common(def, fsec);
|
||||
fsec.alt_symbol(def.stripName);
|
||||
|
||||
|
||||
@@ -73,31 +73,17 @@ onixs_ice::MarketSubscriptions market_subcription_for_channel(const ICEChannel&
|
||||
onixs_ice::MarketSubscriptions subs;
|
||||
for(const auto& x : chan.market_types)
|
||||
{
|
||||
//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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
for(const auto& x : chan.uds_market_types)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
return subs;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ 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();
|
||||
@@ -145,6 +146,8 @@ 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;
|
||||
|
||||
@@ -17,7 +17,7 @@ private:
|
||||
std::unique_ptr<PImpl> me;
|
||||
private:
|
||||
double _compute_day_pnl (const bts::api::position::PNLUnit&);
|
||||
double _get_cpp (uint32_t);
|
||||
//double _get_cpp (uint32_t);
|
||||
// aggregates [sec, account]->pnl tp [account]->pnl
|
||||
void _aggregate_account_pnl(
|
||||
const std::vector<api::position::PNLUnit>& in_pnl,
|
||||
@@ -29,6 +29,7 @@ private:
|
||||
~PositionServicePNLHelper();
|
||||
void compute_pnl(const std::map<int, bts::api::position::PositionUnit const*>& in_pos,
|
||||
const std::map<std::string,uint32_t>& account_map,
|
||||
const std::map<uint32_t,double>& cpp_override_map,
|
||||
std::vector<bts::api::position::PNLUnit>& out_pnl,
|
||||
std::vector<bts::api::position::AccountPNL>& out_account_pnl);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ using namespace bts::api::position;
|
||||
using namespace bts::md;
|
||||
|
||||
static auto const manual_fills_ns = "manual_fills";
|
||||
static auto const security_params_ns = "security_parameters";
|
||||
|
||||
struct PositionServiceServer::PImpl {
|
||||
bts::orderman::PositionMap state;
|
||||
@@ -37,6 +38,7 @@ struct PositionServiceServer::PImpl {
|
||||
int id;
|
||||
// manual fills management
|
||||
std::unique_ptr<persistence::IStore> manual_fills_store;
|
||||
std::unique_ptr<persistence::IStore> security_params_store;
|
||||
PositionServiceBase::Manual_fillsStatefulPublisher manual_fills_publisher;
|
||||
// pnl
|
||||
PositionServiceBase::Pnl_dataStatefulPublisher pnl_publisher;
|
||||
@@ -47,6 +49,7 @@ struct PositionServiceServer::PImpl {
|
||||
bts::api::admin::AdminService admin_service;
|
||||
api::admin::AdminData admin_data;
|
||||
std::map<std::string, uint32_t> account_map;
|
||||
std::map<uint32_t, double> cpp_override_map;
|
||||
|
||||
PImpl(
|
||||
bts::api::ServiceContext &context,
|
||||
@@ -64,6 +67,8 @@ struct PositionServiceServer::PImpl {
|
||||
// manual fills
|
||||
manual_fills_store(
|
||||
std::make_unique<persistence::SqliteStore>("manual_fills")),
|
||||
security_params_store(
|
||||
std::make_unique<persistence::SqliteStore>("security_parameters")),
|
||||
manual_fills_publisher(manual_fills_publisher),
|
||||
// pnl
|
||||
pnl_publisher(pnl_publisher),
|
||||
@@ -73,7 +78,9 @@ struct PositionServiceServer::PImpl {
|
||||
// admin
|
||||
admin_service(context.locate<bts::api::admin::AdminService>()),
|
||||
admin_data(),
|
||||
account_map() {}
|
||||
account_map(),
|
||||
cpp_override_map()
|
||||
{}
|
||||
};
|
||||
|
||||
PositionServiceServer::PositionServiceServer(bts::api::ServiceContext &context,
|
||||
@@ -196,6 +203,27 @@ void PositionServiceServer::_clear_and_publish_pos(){
|
||||
me->pos_publisher.add_or_update(*p.second);
|
||||
me->pos_publisher.flush();
|
||||
}
|
||||
|
||||
void PositionServiceServer::_load_security_params(){
|
||||
me->cpp_override_map.clear();
|
||||
auto all = me->security_params_store->retrieve_all()[security_params_ns];
|
||||
SecurityParameters rec;
|
||||
for (auto const &kvp : all)
|
||||
if (utils::pb::from_string<SecurityParameters>(kvp.second, &rec)){
|
||||
if(rec.has_cpp_override()){
|
||||
LOG(OrderMan, Info, "Overriding cpp, key:{} cpp:{}",
|
||||
rec.security_key(),
|
||||
rec.cpp_override().value());
|
||||
me->cpp_override_map[rec.security_key()] = rec.cpp_override().value();
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG(OrderMan, Error,
|
||||
"Could not recover security parameter for security {} from storage. "
|
||||
"This should be investigated",
|
||||
kvp.first);
|
||||
}
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Manual Fills API //////////////////////////////////////////////////////////
|
||||
void PositionServiceServer::create_manual_fill(
|
||||
@@ -294,6 +322,30 @@ void PositionServiceServer::set_base_position(
|
||||
_clear_and_publish_pos();
|
||||
}
|
||||
|
||||
void PositionServiceServer::update_security_parameters(
|
||||
const SecurityParametersBatchOp& req,
|
||||
const api::Response<SecurityParametersBatchOpResponse>&){
|
||||
LOG(persist, Info, "PositionServiceServer::update_security_parameters bp count:{}",
|
||||
req.op_params_size());
|
||||
for (const auto& p : req.op_params()){
|
||||
std::string key_str = std::to_string(p.security_key());
|
||||
auto db_p = me->security_params_store->retrieve_one(security_params_ns, key_str);
|
||||
auto found = db_p.first;
|
||||
if (!found) {
|
||||
me->security_params_store->insert(security_params_ns,
|
||||
key_str,
|
||||
utils::pb::to_string(p));
|
||||
}
|
||||
else {
|
||||
me->security_params_store->insert(security_params_ns,
|
||||
key_str,
|
||||
utils::pb::to_string(p));
|
||||
}
|
||||
}
|
||||
_load_security_params();
|
||||
_compute_pnl_and_publish();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// PNL helpers ///////////////////////////////////////////////////////////////
|
||||
void PositionServiceServer::_compute_pnl_and_publish() {
|
||||
@@ -301,7 +353,8 @@ void PositionServiceServer::_compute_pnl_and_publish() {
|
||||
me->state.get_pos(in_pos);
|
||||
std::vector<PNLUnit> pnl;
|
||||
std::vector<AccountPNL> account_pnl;
|
||||
me->pnl_helper.compute_pnl(in_pos, me->account_map, pnl, account_pnl);
|
||||
me->pnl_helper.compute_pnl(in_pos, me->account_map, me->cpp_override_map,
|
||||
pnl, account_pnl);
|
||||
for (const auto &u : pnl)
|
||||
me->pnl_publisher.add_or_update(u);
|
||||
for (const auto &u : account_pnl)
|
||||
|
||||
@@ -52,6 +52,7 @@ class PositionServiceServer : public bts::api::position::PositionServiceBase {
|
||||
void _compute_pnl_and_publish();
|
||||
void _set_admin_data(const api::admin::AdminData &admin_data);
|
||||
void _clear_and_publish_pos();
|
||||
void _load_security_params();
|
||||
public:
|
||||
PositionServiceServer(bts::api::ServiceContext &context, md::MDClient &);
|
||||
|
||||
@@ -74,6 +75,11 @@ class PositionServiceServer : public bts::api::position::PositionServiceBase {
|
||||
const ::bts::api::position::BasePositionBatch &request,
|
||||
const api::Response<::bts::api::position::SetBasePositionResponse>
|
||||
&reply) override;
|
||||
|
||||
void update_security_parameters(
|
||||
const ::bts::api::position::SecurityParametersBatchOp &request,
|
||||
const api::Response<::bts::api::position::SecurityParametersBatchOpResponse>
|
||||
&reply) override;
|
||||
|
||||
~PositionServiceServer();
|
||||
};
|
||||
|
||||
Regular → Executable
File mode changed.
Reference in new issue
Block a user