Compare commits

..
20 Commits
Author SHA1 Message Date
Taha Afzal c30bab899f Changing TASCounter to use the new formula 2020-07-29 09:43:00 -04:00
Taha Afzal 46544f36f1 Pyramid Algo now sends hedges for fills while a cancel is in flight 2020-07-23 13:13:52 -04:00
John Colvin c7df3ac43c Merge remote-tracking branch 'origin/csv_export' into prod 2020-07-22 10:44:58 -05:00
Pedro Pinto 400143c84d Protected against ticker subs for expired securities. 2020-07-09 14:45:51 -04:00
Taha Afzal 479f3dccff Adding OnSignal (this is a new algo which is hidden by default) 2020-07-09 13:07:28 -04:00
Taha Afzal c272d8a8c2 Pyramid Algo: Takes care of the rounding error when Top == sum (child orders) 2020-07-08 13:35:21 -04:00
John Colvin fbd8bbd4a8 algo abbreviation code uses string.Join instead of list_string (the latter method is redundant, should be removed) 2020-07-07 13:16:18 -05:00
John Colvin 3da52b350f fix pure crash (algo abbreviation code choked on names that end with a space) 2020-07-07 13:15:55 -05:00
Jake Norbut 9d1b03d1e2 fixing merge 2020-03-04 11:51:05 -06:00
Jake Norbut 9a677cf980 merging master into csv_export; taking master 2020-03-04 11:50:56 -06:00
Jake Norbut 25445d58d0 refactoring TaS CSV export 2019-12-18 13:48:14 -06:00
Jake Norbut 0b313a8aa8 refactoring Fills CSV export 2019-12-18 13:47:36 -06:00
Jake Norbut 54f954fd4b adding CSV export to order book 2019-12-18 13:47:03 -06:00
Jake Norbut 980f38ba03 using OrderedDictionary to CSVs are output with a predictable column order 2019-12-18 13:46:22 -06:00
Jake Norbut 9eada2724b Having the Command be on the Window rather than the VM; nice thinking John 2019-12-18 10:11:10 -06:00
Jake Norbut b5f53ee918 adding csv_properties to TimeAndSalesVM.Row 2019-12-18 10:10:04 -06:00
Jake Norbut efc175f654 adding export to csv command to Fills window 2019-12-17 10:32:58 -06:00
Jake Norbut 6074afd999 adding csv export command to fills vm 2019-12-17 10:32:15 -06:00
Jake Norbut 219dff4055 adding new export_csv IDialogCommand which just creates a new file 2019-12-17 10:28:57 -06:00
Jake Norbut 9a3b07b622 adding ToCsv IEnumerable ext method in a new util namespace 2019-12-17 10:21:38 -06:00
56 changed files with 2548 additions and 1561 deletions

No files matched your search

+1 -1
View File
@@ -55,7 +55,7 @@ else ()
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0 ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
COMMENT "Creating server runtime layout"
@@ -1,372 +1,370 @@
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();
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; }
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
// commands
public ICommand bid_price_click { get; }
public ICommand bid_merged_size_click { get; }
public ICommand bid_direct_size_click { get; }
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;
}
}
}
}
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,294 +1,286 @@
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();
}
}
}
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();
}
}
}
@@ -1,6 +1,7 @@
using bts.api;
using bts.co2;
using bts.utils;
using bts.utils.csv;
using mbts;
using mbts.spark;
using mbts.ui.controls;
@@ -44,6 +45,18 @@ namespace bts.shell.time_and_sales {
public void raise_invalidated() =>
invalidated?.Invoke();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(TimeAndSalesRowVM),
new string[] {
nameof(security),
nameof(time),
nameof(price),
nameof(size),
nameof(aggressor_side),
nameof(security_key),
});
public bool Equals(TimeAndSalesRowVM other) =>
other != null &&
row_key == other.row_key;
@@ -2,6 +2,7 @@
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts;
using mbts.co2;
using mbts.persist;
@@ -114,6 +115,8 @@ namespace bts.shell.time_and_sales {
show_in_context_menu = _can_pause_updates
};
_commands = new SparkCommand[] {
// csv export
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(TimeAndSalesRowVM.csv_ordered_properties), "TaS")),
// ladder
SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder)
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
@@ -5,6 +5,7 @@ using bts.collections;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.collections;
using mbts.persist;
using mbts.spark.filters;
@@ -330,6 +331,27 @@ namespace mbts.spark {
public override bool Equals(object obj) => Equals(obj as Row);
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(typeof(Row),
new string[] {
nameof(security),
nameof(order_type),
nameof(root_algo_display_name),
nameof(side),
nameof(fill_time),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(size),
nameof(price),
nameof(fill_kind),
nameof(fill_status),
nameof(fill_key),
nameof(order_key),
nameof(security_key)
});
public event Action invalidated;
void IRowVM.raise_invalidated() => invalidated?.Invoke();
}
@@ -4,6 +4,7 @@ using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -61,6 +62,7 @@ namespace mbts.spark {
_view = new FillsView { DataContext = _vm };
_commands = new List<SparkCommand> {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
@@ -143,7 +143,7 @@ namespace mbts.spark {
string candidate = string.Empty;
if (a.HasValue) {
var algo_name = a.Value.name;
var words = algo_name.Split(delim);
var words = algo_name.Split(delim, StringSplitOptions.RemoveEmptyEntries);
if (words.Length == 1) {
var single_word = words[0];
if (single_word.Length > 1) {
@@ -152,7 +152,7 @@ namespace mbts.spark {
candidate = single_word;
}
} else {
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
candidate = string.Join(string.Empty, words.Take(2).Select(w => w.Substring(0, 1)));
}
}
return candidate.ToUpper();
@@ -4,6 +4,7 @@ using bts.api.orderman;
using bts.collections;
using bts.spark;
using bts.utils;
using bts.utils.csv;
using mbts.ui;
using mbts.ui.controls;
using System;
@@ -11,6 +12,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
@@ -59,6 +61,35 @@ namespace mbts.spark {
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
public event PropertyChangedEventHandler PropertyChanged;
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(OrderBookRowVM),
new string[] {
nameof(security_text),
nameof(symbol),
nameof(time_created),
nameof(last_change),
nameof(user),
nameof(account),
nameof(exchange),
nameof(time_in_force),
nameof(order_type),
nameof(root_algo_display_name),
nameof(order_sub_type),
nameof(side),
nameof(quantity),
nameof(filled_quantity),
nameof(price),
nameof(avg_price),
nameof(last_price),
nameof(piq),
nameof(status),
// last message (could contains commas, so skipping)
nameof(security_key),
nameof(order_key),
nameof(parent_order_key),
});
// Core
public OrderSecurityRootPair order_tuple { get; private set; }
public OrderRecordImmutable order => order_tuple.order.order;
@@ -6,6 +6,7 @@ using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts.persist;
using mbts.spark.utilities;
using mbts.ui;
@@ -231,6 +232,7 @@ namespace mbts.spark {
toggle_collapse_all_command,
toggle_expand_all_command,
show_grid_settings_command,
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
// hidden cmds (i.e., keyboard shortcuts at window level)
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
@@ -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,43 +1,39 @@
using System;
using System.Text.RegularExpressions;
namespace mbts.spark.filters {
static class RegexFilterUtils {
public static Regex build_regex(string str) {
if (str == null)
return null;
str = str.Trim();
if (str == string.Empty)
return null;
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
}
}
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
string _text = string.Empty;
Regex _regex;
public RegexFilterVM(Func<TRow, string> get_row_val, string prop_name = null) : base(get_row_val, prop_name) { }
public string text {
get => _text;
set => _regex = RegexFilterUtils.build_regex(_text = (value ?? string.Empty));
}
public override string get_state() => _text ?? string.Empty;
protected override bool _get_active() => _regex != null;
protected override bool _filter(string value) =>
_regex != null && (
_regex.IsMatch(value) ||
(!string.IsNullOrEmpty(value) && _regex.IsMatch(value.Replace(" ", ""))));
using System;
using System.Text.RegularExpressions;
protected override void _load_state_internal(string state) => text = state;
protected override void _clear() {
_close();
_text = string.Empty;
_regex = null;
RaisePropertyChanged(nameof(text));
_raise_filter_changed();
}
}
namespace mbts.spark.filters {
static class RegexFilterUtils {
public static Regex build_regex(string str) {
if (str == null)
return null;
str = str.Trim();
if (str == string.Empty)
return null;
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
}
}
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
string _text = string.Empty;
Regex _regex;
public RegexFilterVM(Func<TRow, string> get_row_val, string prop_name = null) : base(get_row_val, prop_name) { }
public string text {
get => _text;
set => _regex = RegexFilterUtils.build_regex(_text = (value ?? string.Empty));
}
public override string get_state() => _text ?? string.Empty;
protected override bool _get_active() => _regex != null;
protected override bool _filter(string value) => _regex != null && _regex.IsMatch(value);
protected override void _load_state_internal(string state) => text = state;
protected override void _clear() {
_close();
_text = string.Empty;
_regex = null;
RaisePropertyChanged(nameof(text));
_raise_filter_changed();
}
}
}
@@ -64,6 +64,25 @@ namespace mbts.ui {
}
}, can_execute == null ? null : new Func<object, bool>(_ => can_execute()));
public static IDialogCommand export_csv(
Func<string> get_file_content,
string source_window_name) =>
new AnonymousFileDialogCommand<object>(_ =>
_create_save_file_dialog(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
$"BTS_Spark_{source_window_name}_{DateTime.Now.ToString("yyyyMMdd_HHmm")}.csv",
new[] { ("CSV (comma delimited)", new string[] { "csv" }) }
),
(_, file) => {
try {
File.WriteAllText(file, get_file_content.Invoke());
} catch (Exception ex) {
BTSMessageBox.Show($"Error exporting data to csv: {ex}.", "CSV Export Error", BTSMessageBoxButtons.OK, System.Windows.MessageBoxImage.Error, true, false, true);
}
},
// we are always able to perform an export, even when no rows (it will just be a header)
(_) => true);
static TDialog _create_file_dialog<TDialog>(string initial_dir = null, IReadOnlyList<(string name, string[] extensions)> filter_items = null) where TDialog : FileDialog, new() {
var rv = new TDialog();
if (!string.IsNullOrEmpty(initial_dir))
@@ -39,18 +39,13 @@ namespace bts.api {
static string _format(SecurityImmutable? security) {
switch (security?.ContractCase) {
case ContractOneofCase.Future:
if(security?.HasIce == true)
return _format_ICE(security);
return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
case ContractOneofCase.Future: return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
case ContractOneofCase.Spread:
if (security?.HasGlbx == true) {
// In case we ever bring it back, pretty printed spread names can be found here:
// GlbxSecurityFormatter.FormatSpread(security, out string result)
return security?.Glbx.SecurityDesc;
}
else if(security?.HasIce == true)
return _format_ICE(security);
return string.Join("", _format_future_legs(SecurityHelper.get_flat_future_legs(security?.Spread.Legs)));
case ContractOneofCase.Synthetic: return security?.Synthetic.Name;
default: return "Unknown-security";
@@ -84,28 +79,5 @@ namespace bts.api {
default: return $"{fl.quantity:+0;-0}{sym_exp_name}";
}
}
static string _format_ICE (SecurityImmutable? security){
string symbol = security?.Symbol,
strip = security?.Ice.StripName,
md_desc = security?.Ice.MarketDescription,
type_append = "";
if(md_desc.Contains("25k ICE Lots")){
if(md_desc.Contains("TAS"))
type_append = "TAS";
}
else if(md_desc.Contains("ICE Lots")){
if(md_desc.Contains("TAS"))
type_append = "ICE Lots TAS";
else
type_append = "ICE Lots";
}
else if(md_desc.Contains("TAS"))
type_append = "TAS";
type_append = string.IsNullOrEmpty(type_append) ?
" " : $" {type_append} ";
return $"{symbol}{type_append}{strip}";
}
}
}
+52
View File
@@ -0,0 +1,52 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
namespace bts.utils.csv {
public static class CsvHelper {
public static string ToCsv<T>(this IEnumerable<T> items, OrderedDictionary properties = null) where T : class {
var output = "";
var delimiter = ",";
if (properties == null) {
properties = new OrderedDictionary();
foreach (var pi in typeof(T).GetProperties()) {
properties.Add(pi.Name, pi);
}
}
using (var sw = new StringWriter()) {
var dict_values = properties.Values.OfType<PropertyInfo>();
var header = dict_values.Select(n => n.Name).Aggregate((first, second) => first + delimiter + second);
sw.WriteLine(header);
foreach (var i in items) {
var row = dict_values.Select(x => x.GetValue(i, null))
.Select(x => x == null ? "null" : x.ToString())
.Aggregate((first, second) => first + delimiter + second);
sw.WriteLine(row);
}
output = sw.ToString();
}
return output;
}
public static OrderedDictionary create_ordering_maintainer(Type t, string[] property_names_in_order) {
OrderedDictionary od = new OrderedDictionary();
var props = t.GetProperties();
foreach (var n in property_names_in_order) {
var query = props.Where(pi => pi.Name == n);
if (query.Any()) {
var property_info = query.First();
if (!od.Contains(property_info)) {
od.Add(n, property_info);
}
}
}
return od;
}
}
}
@@ -15,7 +15,7 @@
<server name="default">
<ip>63.247.113.163</ip>
<port>3000</port>
<userName>katas2f-ps</userName>
<userName>bluetrd-ps</userName>
<password>Starts123</password>
<getStripInfoMessages>N</getStripInfoMessages>
<strategyPreference>New</strategyPreference>
@@ -24,8 +24,8 @@
<server name="endex">
<ip>63.247.113.77</ip>
<port>3000</port>
<userName>katas2f-ps</userName>
<password>Starts123</password>
<userName>Value from ICE Support</userName>
<password>Value from ICE Support</password>
<getStripInfoMessages>N</getStripInfoMessages>
<strategyPreference>New</strategyPreference>
<localNetworkInterface></localNetworkInterface>
+1 -1
View File
@@ -1 +1 @@
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cpp;metrics/metrics.cpp;metricsLine truncated
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cpp;metrics/metrics.cpp;metricsLine truncated
+2 -23
View File
@@ -105,35 +105,14 @@ message BasePositionBatch {
google.protobuf.Timestamp current_as_of = 10; // UTC
}
// security overrides /////////////////////////////////////
message CppOverride{
double value = 1;
}
message SecurityParameters{
uint32 security_key = 1;
CppOverride cpp_override = 2;
// other overrides
}
message SecurityParametersBatchOp{
repeated SecurityParameters op_params = 1;
}
message SecurityParametersBatchOpResponse {
bool ok = 1;
string msg = 2;
}
service PositionService {
option (bts.api.service_description) = "Provides Position and PNL, per security, per account";
rpc create_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
rpc update_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
rpc remove_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
rpc set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
rpc update_security_parameters (SecurityParametersBatchOp) returns (SecurityParametersBatchOpResponse);
rpc set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
// subscription
rpc manual_fills(bts.api.VoidMessage) returns (stream ManualFill);
rpc position_data(bts.api.VoidMessage) returns (stream PositionUnit);
+136 -130
View File
@@ -1,130 +1,136 @@
#
# Common Market data options
#
MarketDataConfig {
rule: "DeploymentType.type == 'PROD'"
value {
use_sim: false
}
}
PublisherOptions {
rule: "DeploymentType.type == 'dev'"
value {
disable_heartbeat: true
}
}
PlatformMap {
value {
glbx_platform: "PLATFORM_MDP3ONIXS"
ice_platform: "PLATFORM_ICEONIXS"
}
}
PublisherNamespace {
value {
shared_memory_namespace: @System.os_user
}
}
CMEOptions {
value {
feed_engine {
name: "mdp3feed"
# udp_socket_buffer_size: 425984
# channels_per_thread: 1
spin_time: 0
}
channel {
id: "*"
feed_engine: "mdp3feed"
use_tcp_replay: false
}
}
}
#to make this work in dev clone the operations repo
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
#modify the easyTunnelProxy.properties file to replace
#tunnel.multicast.interface=10.96.18.37 with your local ip
#run easyTunnelProxy.bat BRENT-FI for Brent Full implied
#you should see ice data
ICEOptions {
value {
channel {
name: "oil" #these names are arbitrary and are for debugging
market_type: 3
uds_market_type: 3
market_type: 4 #ICE IPE Gas Oil Futures
uds_market_type: 4
market_type: 5 #ICE Futures Brent, IPE Brent Futures
uds_market_type: 5
market_type: 9 #ICE Futures WTI, ICE WTI Crude Futures
uds_market_type: 9
market_type: 13 #ICE Heating Oil Futures
uds_market_type: 13
market_type: 15 #ICE NYH (RBOB) Gasoline Futures
uds_market_type: 15
##market_type: 21 #ICE Futures US Agricultures, Coffee C
##uds_market_type: 21
##market_type: 23 #ICE Futures US Agricultures, FCOJ A
##uds_market_type: 23
##market_type: 26 #ICE Futures US Financials DX
##uds_market_type: 26
uds_market_type: 11 #ICE WTI-Brent spreads
uds_market_type: 14 #ICE Heating Oil/WTI Futures Crack
uds_market_type: 16 #ICE NYH (RBOB) Gasoline/WTI Futures Crack
uds_market_type: 58 #ICE Futures UI Heating Oil / Brent
uds_market_type: 59 #ICE NYH (RBOB) Gasoline/Brent Futures Crack
uds_market_type: 66 #ICE NYH (RBOB) Gasoline/Heating Oil Spread
uds_market_type: 70 #ICE Heating Oil/LS Gasoil Futures Spread
uds_market_type: 71 #ICE LS Gasoil/Brent Futures Crack
uds_market_type: 72 #ICE NYH (RBOB) Gasoline/LS Gasoil Futures Spread
#onixs_log_mode: Debug
}
# channel {
# name: "juice"
# market_type: 26 #ICE Futures US Agricultures, FCOJ A
# }
ice_config_file: "ConnectivityConfiguration.APITest.xml"
#ice_config_file: "ConnectivityConfiguration.PerfTest.xml"
}
}
MDBootDelayWindow {
rule: "DeploymentType.type == 'dev'"
value {
delay_seconds: 0
}
}
MDBootDelayWindow {
value {
delay_seconds: 120
standard {
start: "16:30"
end: "16:40"
}
sunday {
start: "14:00"
end: "14:10"
}
}
}
DisplayFactorOverride {
value {
override {
symbol: "SOM"
exchange: "GLBX"
type: "SPREAD"
price_multiplier: 1
}
}
}
#
# Common Market data options
#
MarketDataConfig {
rule: "DeploymentType.type == 'PROD'"
value {
use_sim: false
}
}
PublisherOptions {
rule: "DeploymentType.type == 'dev'"
value {
disable_heartbeat: true
}
}
PlatformMap {
value {
glbx_platform: "PLATFORM_MDP3ONIXS"
ice_platform: "PLATFORM_ICEONIXS"
}
}
PublisherNamespace {
value {
shared_memory_namespace: @System.os_user
}
}
CMEOptions {
value {
feed_engine {
name: "mdp3feed"
# udp_socket_buffer_size: 425984
# channels_per_thread: 1
spin_time: 0
}
channel {
id: "*"
feed_engine: "mdp3feed"
use_tcp_replay: false
}
}
}
ICEPublisherOptions {
value {
network_interface: "eth1"
}
}
#to make this work in dev clone the operations repo
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
#modify the easyTunnelProxy.properties file to replace
#tunnel.multicast.interface=10.96.18.37 with your local ip
#run easyTunnelProxy.bat BRENT-FI for Brent Full implied
#you should see ice data
ICEOptions {
value {
channel {
name: "oil" #these names are arbitrary and are for debugging
market_type: 3
uds_market_type: 3
market_type: 4 #ICE IPE Gas Oil Futures
uds_market_type: 4
market_type: 5 #ICE Futures Brent, IPE Brent Futures
uds_market_type: 5
market_type: 9 #ICE Futures WTI, ICE WTI Crude Futures
uds_market_type: 9
market_type: 13 #ICE Heating Oil Futures
uds_market_type: 13
market_type: 15 #ICE NYH (RBOB) Gasoline Futures
uds_market_type: 15
##market_type: 21 #ICE Futures US Agricultures, Coffee C
##uds_market_type: 21
##market_type: 23 #ICE Futures US Agricultures, FCOJ A
##uds_market_type: 23
##market_type: 26 #ICE Futures US Financials DX
##uds_market_type: 26
uds_market_type: 11 #ICE WTI-Brent spreads
uds_market_type: 14 #ICE Heating Oil/WTI Futures Crack
uds_market_type: 16 #ICE NYH (RBOB) Gasoline/WTI Futures Crack
uds_market_type: 58 #ICE Futures UI Heating Oil / Brent
uds_market_type: 59 #ICE NYH (RBOB) Gasoline/Brent Futures Crack
uds_market_type: 66 #ICE NYH (RBOB) Gasoline/Heating Oil Spread
uds_market_type: 70 #ICE Heating Oil/LS Gasoil Futures Spread
uds_market_type: 71 #ICE LS Gasoil/Brent Futures Crack
uds_market_type: 72 #ICE NYH (RBOB) Gasoline/LS Gasoil Futures Spread
#onixs_log_mode: Debug
}
# channel {
# name: "juice"
# market_type: 26 #ICE Futures US Agricultures, FCOJ A
# }
ice_config_file: "ConnectivityConfiguration.APITest.xml"
#ice_config_file: "ConnectivityConfiguration.PerfTest.xml"
}
}
MDBootDelayWindow {
rule: "DeploymentType.type == 'dev'"
value {
delay_seconds: 0
}
}
MDBootDelayWindow {
value {
delay_seconds: 120
standard {
start: "16:30"
end: "16:40"
}
sunday {
start: "14:00"
end: "14:10"
}
}
}
DisplayFactorOverride {
value {
override {
symbol: "SOM"
exchange: "GLBX"
type: "SPREAD"
price_multiplier: 1
}
}
}
+1 -4
View File
@@ -59,10 +59,6 @@ message EnsureOnScreenWithWorkAreaRect {
optional bool enabled = 1 [default = false];
}
message CustomFormatIceSecurities{
optional bool enabled = 1 [default = true];
}
message IgnoreNansInDayPnL {
optional bool enabled = 1 [default = true];
}
@@ -73,4 +69,5 @@ message DynamicAlgos{
optional bool enable_tas = 3 [default = false];
optional bool enable_tascounter = 4 [default = false];
optional bool enable_limit_cover = 5 [default = false];
optional bool enable_onsignal = 6 [default = false];
}
+3
View File
@@ -211,6 +211,9 @@ inline bool gr_than(double p1, double p2, double tick_size) {
return gr_than_eq(p1, p2, tick_size) &&
!test_price_equality(p1, p2, tick_size);
}
inline bool test_price_equality(double price_1, double price_2) {
return std::fabs(price_2 - price_1) < market_epsilon();
}
} // namespace securities
} // namespace bts
+88 -73
View File
@@ -1,5 +1,6 @@
#pragma once
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/md/IntPrice.h"
#include "lib/md/MDConsumer.h"
@@ -10,12 +11,11 @@
#include "lib/mercury/events/TimerEvent.h"
#include "lib/orderman/events/BookUpdateEvent.h"
#include "lib/orderman/events/MDRequestEvent.h"
#include "lib/orderman/events/TickerUpdateEvent.h"
#include "lib/orderman/events/StatusUpdateEvent.h"
#include "lib/orderman/events/TickerUpdateEvent.h"
#include "lib/tracing/PerfLog.h"
#include "lib/tracing/bts_trace.h"
#include "lib/utils/TimeStamp.h"
#include "lib/domain_model/Security.h"
#include <date/date.h>
@@ -31,9 +31,9 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
public:
MDSource(md::MDConsumer &consumer, metrics::MetricsContainer &stats,
trace::PerfLog &log)
: _handles(), _pending_handles(), _status_subs(), _md_consumer(consumer),
_book_update(), _ticker_update(), _status_update(),
_perf_log(log), _stats(stats), _loop_timer(), _counter(0) {
: _handles(), _pending_handles(), _status_subs(), _md_consumer(consumer),
_book_update(), _ticker_update(), _status_update(), _perf_log(log),
_stats(stats), _loop_timer(), _counter(0) {
_trades_buffer.Reserve(1000);
auto cme_channels = md::get_configured_cme_channels();
@@ -67,9 +67,8 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
auto ticker_channel_cout_metric =
std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_ticker_channel_count");
auto status_count_metric =
std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_status_count");
auto status_count_metric = std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_status_count");
_stats.publish(security_count_metric);
_stats.publish(synthetic_count_metric);
_stats.publish(ticker_channel_cout_metric);
@@ -78,37 +77,40 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
_book_update = make_stream<BookUpdateEvent>();
_ticker_update = make_stream<TickerUpdateEvent>();
_status_update = make_stream<StatusUpdateEvent>();
subscribe<MDRequestEvent>(
[this, security_count_metric, synthetic_count_metric,
ticker_channel_cout_metric, status_count_metric](auto const &update) {
if (update.events) {
for (auto const &ev : *update.events) {
auto k = ev.security_key;
subscribe<MDRequestEvent>([this, security_count_metric,
synthetic_count_metric,
ticker_channel_cout_metric,
status_count_metric](auto const &update) {
if (update.events) {
for (auto const &ev : *update.events) {
auto k = ev.security_key;
switch (ev.md_type) {
case MDRequestType::BOOK:
on_book_sub(k, *synthetic_count_metric);
security_count_metric->set(_handles.size());
break;
case MDRequestType::TICKER:
on_ticker_sub(k, *ticker_channel_cout_metric);
break;
case MDRequestType::STATUS:
on_status_sub(k);
status_count_metric->set(_status_subs.size());
break;
}
}
}
});
switch (ev.md_type) {
case MDRequestType::BOOK:
on_book_sub(k, *synthetic_count_metric);
security_count_metric->set(_handles.size());
break;
case MDRequestType::TICKER:
on_ticker_sub(k, *ticker_channel_cout_metric);
break;
case MDRequestType::STATUS:
on_status_sub(k);
status_count_metric->set(_status_subs.size());
break;
}
}
}
});
security_count_metric->set(_handles.size());
}
private:
void on_poll(trace::PerfLog& log) override {
void on_poll(trace::PerfLog &log) override {
auto scoped_do_work = trace::log_scope(log, {}, "MDSource::on_poll", utils::TimeStamp::Interval::from_microseconds(2));
auto scoped_do_work =
trace::log_scope(log, {}, "MDSource::on_poll",
utils::TimeStamp::Interval::from_microseconds(2));
// Book Updates
Book book;
@@ -158,11 +160,11 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
}
// Status updates
for (auto& s : _status_subs) {
for (auto &s : _status_subs) {
auto status = s.handle.trading_status();
if (status != s.status) {
s.status = status;
publish(_status_update, SecurityKey { s.handle.key() }, status);
publish(_status_update, SecurityKey{s.handle.key()}, status);
}
}
@@ -171,7 +173,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
_loop_timer->observe((start - stop).nanoseconds());
_counter += 1;
if (_counter % (2<<20) == 0) {
if (_counter % (2 << 20) == 0) {
// Invoke periodic work every 2M calls on on_poll
// This is approximately 20-30 seconds
do_periodic_work();
@@ -180,35 +182,39 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
void do_periodic_work() {
if (!_pending_handles.empty()) {
for (auto iter = _pending_handles.begin(); iter != _pending_handles.end(); ++iter) {
for (auto iter = _pending_handles.begin();
iter != _pending_handles.end(); ++iter) {
if (iter->handle->is_initialized()) {
auto handle = *(iter->handle);
LOG(OrderMan, Info, "Found md for {}. Pending handle removed.", handle.key());
switch(iter->type) {
case MDRequestType::BOOK:
_handles.push_back({utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(securities::descriptor(
handle.get_normalized_security()))});
break;
case MDRequestType::STATUS:
for (auto& h : _status_subs) {
// no need to reset status
if (h.handle.key() == handle.key()) break;
}
_status_subs.push_back(
StatusRecord{ handle, co2::TRADINGSTATUS_UNKNOWN });
case MDRequestType::TICKER:
LOG(OrderMan, Info, "Found md for {}. Pending handle removed.",
handle.key());
switch (iter->type) {
case MDRequestType::BOOK:
_handles.push_back(
{utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(securities::descriptor(
handle.get_normalized_security()))});
break;
case MDRequestType::STATUS:
for (auto &h : _status_subs) {
// no need to reset status
if (h.handle.key() == handle.key())
break;
}
_status_subs.push_back(
StatusRecord{handle, co2::TRADINGSTATUS_UNKNOWN});
case MDRequestType::TICKER:
// We aren't currently monitoring for pending ticker requests
break;
}
}
}
_pending_handles.erase(
std::remove_if(_pending_handles.begin(),
_pending_handles.end(),
[](const auto& h) { return h.handle->is_initialized(); }),
_pending_handles.end());
std::remove_if(
_pending_handles.begin(), _pending_handles.end(),
[](const auto &h) { return h.handle->is_initialized(); }),
_pending_handles.end());
}
}
@@ -229,18 +235,18 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
auto mhandle = _md_consumer.get_managed_handle(k);
if (mhandle->is_initialized()) {
auto handle = *mhandle;
_handles.push_back({utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(securities::descriptor(
handle.get_normalized_security()))});
_handles.push_back(
{utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(
securities::descriptor(handle.get_normalized_security()))});
if (handle.is_synthetic())
synthetic_count_metric.increment();
}
else {
// A pending handle is a handle for security we are reasonbly sure exists
// but we haven't yet found live market data for.
} else {
// A pending handle is a handle for security we are reasonbly sure
// exists but we haven't yet found live market data for.
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
_pending_handles.push_back({ mhandle, MDRequestType::BOOK });
_pending_handles.push_back({mhandle, MDRequestType::BOOK});
}
}
@@ -253,8 +259,17 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
void on_ticker_sub(SecurityKey sk,
metrics::CounterMetric<int> &channel_count_metric) {
auto handle = _md_consumer.get_managed_handle(sk.value());
auto user_symbol = _md_consumer.get_handle(sk.value()).get_usersymbol();
if (!handle->is_initialized()) {
LOG(OrderMan, Warn,
"Received ticker subscription for security {} which does not have "
"valid market data. Ignoring",
sk.value());
return;
}
auto user_symbol = handle->get_usersymbol();
for (auto &ch : _channels) {
if (ch.user_symbol == user_symbol) {
auto it =
@@ -262,7 +277,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
[sk](auto const &ev) { return ev.key() == sk; });
if (it == ch.securities.end()) {
// first time we are asked for this security, we will
// publish the trades for it next time next round
// publish the trades for it next time around
LOG(OrderMan, Warn,
"Starting to publish ticker updates on user symbol {} for "
"security key: {} (sec count for the symbol: {} )",
@@ -294,7 +309,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
void on_status_sub(SecurityKey sk) {
auto k = sk.value();
for (auto& h : _status_subs) {
for (auto &h : _status_subs) {
if (h.handle.key() == k) {
// reset status so it get resent next on_poll
h.status = co2::TRADINGSTATUS_UNKNOWN;
@@ -303,11 +318,11 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
}
auto handle = _md_consumer.get_managed_handle(k);
if (handle->is_initialized()) {
_status_subs.push_back(StatusRecord{ *handle, co2::TRADINGSTATUS_UNKNOWN });
}
else {
_status_subs.push_back(
StatusRecord{*handle, co2::TRADINGSTATUS_UNKNOWN});
} else {
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
_pending_handles.push_back({ handle, MDRequestType::STATUS });
_pending_handles.push_back({handle, MDRequestType::STATUS});
}
}
@@ -337,7 +352,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
md::MDConsumerHandle handle;
co2::TradingStatus status;
};
boost::container::small_vector<StatusRecord,20> _status_subs;
boost::container::small_vector<StatusRecord, 20> _status_subs;
md::MDConsumer _md_consumer;
mercury::Stream<BookUpdateEvent> _book_update;
+1 -3
View File
@@ -170,7 +170,7 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
me->sec = serv.sec_db.resolve(SecurityKey(def.security_key()));
me->previous_working_position = Quantity(0);
auto &native_state = get_native_state(me->state);
auto const &native_state = get_native_state(me->state);
switch (me->state.status()) {
case ORDER_STATUS_WORKING:
@@ -202,8 +202,6 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
}
// if we made it this far we are good.
me->routing_record = rq.routing_record;
*native_state.mutable_routing_record() = rq.routing_record;
if (me->state.status() != ORDER_STATUS_PENDING_WORKING)
me->exchange_order_id = native_state.exchange_order_id();
init_fix();
@@ -215,6 +215,13 @@ api::orderman::OrderDefinition
ICENativeOrder::on_init_from_exe_report(const ExecutionReport& er, const PreActivationOrderServices& os)
{
auto k = er.order_key;
auto nextid = me->services->gen_id();
if(k.value() < nextid)
{
k = OrderKey(nextid);
LOG(OrderMan, Error, "Updating recovered order id with newly generated id {}", k.value());
}
OrderRecord orecord;
orecord.set_key(k.value());
orecord.set_parent_key(null_order_key().value());
@@ -0,0 +1,247 @@
#include "lib/orderman/orders/algo/algo_orders/OnSignalAlgo.h"
#include "lib/domain_model/Security.h"
#include "lib/orderman/order_factory/IOrderFactory.h"
#include "lib/orderman/orderman_utils/Book.h"
#include "lib/orderman/orderman_utils/orderman_utils.h"
#include "lib/orderman/orders/algo/IAlgoServices.h"
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
#include "lib/orderman/actors/SODPosition.h"
#include "lib/os/os.h"
#include "lib/domain_model/Security.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/datetime.h"
#include <google/protobuf/util/time_util.h>
#include <boost/tokenizer.hpp>
#include <stdint.h>
namespace bts {
namespace orderman {
using namespace api::orderman;
using namespace google::protobuf;
using namespace boost;
struct OnSignalLegData{
const SecurityKey sec_key;
ChildTemplate* order_template = nullptr;
const Price price;
const Quantity quantity;
const api::Side side;
OrderKey order_key;
ChildState order_state;
};
enum class OnSignalAlgo::State { WAITING_FOR_SIGNAL, WORKING, DONE };
struct OnSignalAlgoParams{
std::optional<SecurityKey> signal_key_1 = std::nullopt;
std::optional<google::protobuf::Timestamp> time = std::nullopt;
bool preopen = false;
bool open = false;
std::vector<OnSignalLegData> legs;
OnSignalLegData* find_leg_from_ord_key (OrderKey ord_key){
for (auto& l : legs)
if(l.order_key == ord_key)
return &l;
return nullptr;
}
};
struct OnSignalAlgo::PImpl {
IAlgoServices* algo_services = nullptr;
State state = State::WAITING_FOR_SIGNAL;
OnSignalAlgoParams algo_params;
PImpl(){
}
};
ChildTemplate* _get_template (IBeforeLaunchAlgoServices& serv,
const CustomOrderDefinition& cod,
SecurityKey key,
api::Side side){
OrderDefinition rv = serv.definition();
rv.set_security_key(key.value());
rv.set_side(side);
rv.mutable_custom_def()->CopyFrom(cod);
return &(serv.make_child_template(rv));
}
bool _convert_params (IBeforeLaunchAlgoServices& serv,
const CustomOrderDefinition& cod,
OnSignalAlgoParams& out_p){
const DynamicOrderDefinition& def = cod.dynamic();
const auto& params = def.order_params();
auto hh = get_if<uint32_t>(params, "Time Hours");
auto mm = get_if<uint32_t>(params, "Time Minutes");
auto ss = get_if<uint32_t>(params, "Time Seconds");
if(hh.has_value() && hh.value() <= 23 &&
mm.has_value() && mm.value() <= 59 &&
ss.has_value() && ss.value() <= 59)
out_p.time = SODPosition::today_local_time_to_utc_timestamp (
hh.value(),mm.value(),ss.value(),true,false);
auto preopen = get_if<bool>(params, "Preopen");
out_p.preopen = preopen.has_value() ? preopen.value() : false;
auto open = get_if<bool>(params, "Open");
out_p.open = open.has_value() ? open.value() : false;
auto k1 = get_if<uint32_t>(params, "Signal Contract 1");
if(k1.has_value())
out_p.signal_key_1 = SecurityKey(k1.value());
for (int i=0;;i++){
auto idx = std::to_string(i+1);
auto contract = get_if<uint32_t>(params, "Order " + idx + " Contract");
auto custom_ord = get_if<CustomOrderDefinition>(params, "Order " + idx);
auto price = get_if<double>(params, "Order " + idx + " Price");
auto qty = get_if<int32_t>(params, "Order " + idx + " Quantity");
if(contract.has_value() && custom_ord.has_value() && price.has_value() &&
qty.has_value() && qty.value() != 0){
auto side = qty > 0 ? api::Side::SIDE_BUY : api::Side::SIDE_SELL;
uint32_t eff_qty = std::abs(qty.value());
auto key = SecurityKey(contract.value());
ChildTemplate* child_template = _get_template(serv,custom_ord.value(),key,side);
OnSignalLegData leg =
{
key,
child_template,
Price(price.value()),
Quantity(eff_qty),
side,
OrderKey(0),
ChildState()
};
out_p.legs.push_back(leg);
}
else
break;
}
bool valid = out_p.legs.size() > 0 &&
(out_p.time.has_value() ||
(out_p.signal_key_1.has_value() && (out_p.preopen || out_p.open)));
return valid;
}
OnSignalAlgo::OnSignalAlgo() : me (new PImpl()){}
OnSignalAlgo::~OnSignalAlgo() {}
OrderOpResult OnSignalAlgo::do_before_launch(IBeforeLaunchAlgoServices& serv){
auto& def = serv.definition();
if (def.has_custom_def() &&
def.custom_def().has_dynamic() &&
_convert_params(serv,def.custom_def(),me->algo_params))
return {};
return {OrderError::ORDER_ERROR_INVALID_PARAMETER,
std::string("Parameters are not well formed")};
}
void OnSignalAlgo::do_launch(IAlgoServices& algo_services){
me->algo_services = &algo_services;
me->algo_services->subscribe_timer();
if(me->algo_params.signal_key_1.has_value())
me->algo_services->subscribe_status(me->algo_params.signal_key_1.value());
}
void OnSignalAlgo::do_launch_attached(IAlgoServices& algo_services,
OrderKey,
ChildState){
me->algo_services = &algo_services;
_cxl_and_end(true, "OnSignal Algo can not be attached to working orders");
}
void OnSignalAlgo::do_update(api::orderman::Update const &){
LOG(OrderMan, Warn, "OnSignalAlgo::do_update order:{} state:{} update not allowed, please cxl and re-enter ",
me->algo_services->order_key().value(),
static_cast<int>(me->state));
}
void OnSignalAlgo::on_child_change(OrderKey key, const ChildState& st){
LOG(OrderMan, Info,
"OnSignalAlgo::on_child_change key:{} child_key:{} state:{} status:{} "
"total_fills:{} last_price:{}",
me->algo_services->order_key(),
key,
static_cast<int>(me->state),
static_cast<int>(st.status),
st.total_fills,
st.last_price);
switch (me->state){
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
LOG(OrderMan, Error,
"OnSignalAlgo::on_child_change key:{} in WAITING_FOR_SIGNAL",
me->algo_services->order_key());
break;
case (OnSignalAlgo::State::WORKING):
{
auto ld_ptr = me->algo_params.find_leg_from_ord_key(key);
if (ld_ptr != nullptr)
ld_ptr->order_state = st;
}
for (const auto& l : me->algo_params.legs)
if(l.order_state.status == ChildStatus::WORKING)
return;
_cxl_and_end(false,"");
break;
case (OnSignalAlgo::State::DONE):
break;
}
}
void OnSignalAlgo::on_status(SecurityKey key, co2::TradingStatus s){
if(me->algo_params.signal_key_1.has_value() && me->algo_params.signal_key_1 == key)
switch (me->state){
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
if(me->algo_params.preopen && s == co2::TradingStatus::TRADINGSTATUS_PRE_OPEN)
_fire_orders();
else if(me->algo_params.open && s == co2::TradingStatus::TRADINGSTATUS_NORMAL)
_fire_orders();
break;
case (OnSignalAlgo::State::WORKING):
case (OnSignalAlgo::State::DONE):
break;
}
}
void OnSignalAlgo::on_timer(){
if(me->algo_params.time.has_value())
switch (me->state){
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
{
auto thresh = me->algo_params.time.value();
auto now = util::TimeUtil::GetCurrentTime();
auto diff = util::TimeUtil::TimestampToMicroseconds(now) -
util::TimeUtil::TimestampToMicroseconds(thresh);
if(diff >= 0)
_fire_orders();
break;
}
case (OnSignalAlgo::State::WORKING):
case (OnSignalAlgo::State::DONE):
break;
}
}
void OnSignalAlgo::_fire_orders(){
for (auto& l : me->algo_params.legs)
l.order_key = me->algo_services->launch_child(*l.order_template,l.price,l.quantity);
_switch_state(State::WORKING);
}
void OnSignalAlgo::_cxl_and_end (bool is_error, const std::string& err_msg){
if(is_error)
me->algo_services->terminate_error(err_msg);
else
me->algo_services->complete();
_switch_state(OnSignalAlgo::State::DONE);
}
void OnSignalAlgo::_switch_state (State new_state){
switch (new_state){
case (OnSignalAlgo::State::WAITING_FOR_SIGNAL):
bts_assert(false);
break;
case (OnSignalAlgo::State::WORKING):
bts_assert(me->state == OnSignalAlgo::State::WAITING_FOR_SIGNAL);
break;
case (OnSignalAlgo::State::DONE):
bts_assert(me->state != OnSignalAlgo::State::DONE);
break;
}
LOG(OrderMan, Info,"OnSignalAlgo::_switch_state key:{} {}->{} ",
me->algo_services->order_key(),
static_cast<int>(me->state),
static_cast<int>(new_state));
me->state = new_state;
}
} // namespace orderman
} // namespace bts
@@ -0,0 +1,36 @@
#pragma once
#include "lib/orderman/orders/algo/IAlgoOrder.h"
namespace bts {
namespace orderman {
class ChildTemplate;
struct LegData;
class OnSignalAlgo : public IAlgoOrder {
public:
OnSignalAlgo();
~OnSignalAlgo();
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
void do_launch(IAlgoServices &) override;
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
void do_update(api::orderman::Update const &) override;
void on_child_change(OrderKey, ChildState const &) override;
void on_status(SecurityKey, co2::TradingStatus) override;
void on_timer() override;
private:
enum class State;
struct PImpl;
std::unique_ptr<PImpl> me;
private:
IAlgoServices& _algo() const ;
// acts on md, cognizant of the state
void _switch_state (State new_state);
void _fire_orders();
void _cxl_and_end (bool is_error, const std::string& err_msg);
};
} // namespace orderman
} // namespace bts
@@ -16,7 +16,7 @@ namespace orderman {
using namespace api::orderman;
enum class PyramidAlgo::Status { WAITING_FOR_MD, WAITING_FOR_MD_ATTACH, WORKING, DONE };
enum class PyramidAlgo::Status { WAITING_FOR_MD, WAITING_FOR_MD_ATTACH, WORKING, WAITING_FOR_CXL_ACK, DONE };
enum class PyramidAlgo::ExitPath { CANCEL, ERR, COMPLETE };
struct LegData{
@@ -192,6 +192,7 @@ void PyramidAlgo::do_update(const api::orderman::Update& update) {
"Order cancelled (Cover prices not met)");
break;
}
case PyramidAlgo::Status::WAITING_FOR_CXL_ACK:
case PyramidAlgo::Status::DONE:
break;
}
@@ -215,6 +216,7 @@ IAlgoOrder::OnCancelResponse PyramidAlgo::on_cancel(){
case PyramidAlgo::Status::WORKING:
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,"Algo cancelled");
break;
case (PyramidAlgo::Status::WAITING_FOR_CXL_ACK):
case PyramidAlgo::Status::DONE:
break;
}
@@ -242,6 +244,7 @@ void PyramidAlgo::on_child_change(OrderKey key, const ChildState &st) {
_check_for_completion();
break;
case PyramidAlgo::Status::WORKING:
case (PyramidAlgo::Status::WAITING_FOR_CXL_ACK):
leg->order_state = st;
_algo().update_fill_state(st.total_fills, st.avg_price);
_send_hedges(st.last_fills);
@@ -388,6 +391,7 @@ void PyramidAlgo::_analyze_md_and_act(){
_cxl_and_end(PyramidAlgo::ExitPath::CANCEL,
"Order cancelled (Cover prices not met)");
break;
case PyramidAlgo::Status::WAITING_FOR_CXL_ACK:
case PyramidAlgo::Status::DONE:
break;
}
@@ -405,14 +409,19 @@ void PyramidAlgo::_switch_state(Status new_state){
bts_assert(me->state == PyramidAlgo::Status::WAITING_FOR_MD ||
me->state == PyramidAlgo::Status::WAITING_FOR_MD_ATTACH);
break;
case (PyramidAlgo::Status::WAITING_FOR_CXL_ACK):
break;
case (PyramidAlgo::Status::DONE):
bts_assert(
// when an order is entered and the criteria is not met
me->state == PyramidAlgo::Status::WAITING_FOR_MD ||
// when an order is attached and the market conditions are not met
me->state == PyramidAlgo::Status::WAITING_FOR_MD_ATTACH ||
// when the child order gets into an unexpected state
me->state == PyramidAlgo::Status::WORKING);
// when the child order enters a terminal state, while the algo
// is in a working state (external cxl, target reached)
me->state == PyramidAlgo::Status::WORKING ||
// the usual controlled cxl, ie working->waiting_for_cxl_ack->done
me->state == PyramidAlgo::Status::WAITING_FOR_CXL_ACK);
break;
}
LOG(OrderMan, Info,"PyramidAlgo::_switch_state key:{} {}->{} ",
@@ -494,8 +503,8 @@ void PyramidAlgo::_check_for_completion(){
break;
case (ChildStatus::CANCELED):
_cxl_and_end(PyramidAlgo::ExitPath::ERR,
"Top spread unexpectedly cancelled");
break;
"Top spread cancelled");
break;
}
}
@@ -507,8 +516,11 @@ void PyramidAlgo::_cxl_and_end(PyramidAlgo::ExitPath exit_path,
if(top_ord_state.status == ChildStatus::WORKING){
if(detach_top)
_algo().detach_child(me->legs[0].order_key);
else
else{
_algo().cancel_child(me->legs[0].order_key);
_switch_state(PyramidAlgo::Status::WAITING_FOR_CXL_ACK);
return;
}
}
}
switch(exit_path){
@@ -524,6 +536,7 @@ void PyramidAlgo::_cxl_and_end(PyramidAlgo::ExitPath exit_path,
}
_switch_state(PyramidAlgo::Status::DONE);
}
// true the out qty is changed it be below
// the filled qty
bool PyramidAlgo::_target_reached() const{
@@ -538,6 +551,14 @@ bool PyramidAlgo::_md_recvd() const{
return true;
}
bool gr_than_eq(double p1, double p2){
return securities::test_price_equality(p1,p2) || p1 > p2;
}
bool less_than_eq(double p1, double p2){
return securities::test_price_equality(p1,p2) || p1 < p2;
}
bool PyramidAlgo::_working_state_met() const{
double cover_sum = 0.0;
size_t i = 0;
@@ -545,10 +566,17 @@ bool PyramidAlgo::_working_state_met() const{
if (i++ > 0)
cover_sum += l.opp_md()[0].price();
bool rv = !os::is_nan(cover_sum) &&
((me->side == api::Side::SIDE_BUY &&
me->out_price.value() <= cover_sum) ||
(me->side == api::Side::SIDE_SELL &&
me->out_price.value() >= cover_sum));
(
(
me->side == api::Side::SIDE_BUY &&
less_than_eq(me->out_price.value(),cover_sum)
)
||
(
me->side == api::Side::SIDE_SELL &&
gr_than_eq(me->out_price.value(),cover_sum)
)
);
if (!rv && !os::is_nan(cover_sum)){
// price condition is not met, but if the
// cum qty at the top leg price is > threashold
@@ -11,6 +11,7 @@
#include "PyramidAlgo.h"
#include "TASAlgo.h"
#include "TASCounterAlgo.h"
#include "OnSignalAlgo.h"
using namespace bts;
using namespace api::orderman;
@@ -200,7 +201,7 @@ extern "C" void init_tas_plugin(bts::SparkServices& srv) {
auto idx = std::to_string(i+1);
*tas_desc.add_param_desc() = param_double("Order " + idx + " Price");
*tas_desc.add_param_desc() = param_uint32("Order " + idx + " Quantity");
*tas_desc.add_param_desc() = param_bool("Order " + idx + " Buy?");
*tas_desc.add_param_desc() = param_bool("Order " + idx + " Buy?");
}
srv.register_dynamic_algo_order(tas_desc, []() { return std::make_unique<orderman::TASAlgo>(); });
}
@@ -212,6 +213,27 @@ extern "C" void init_limit_cover(bts::SparkServices & srv) {
srv.register_dynamic_algo_order(lc_desc, []() { return std::make_unique<orderman::LimitCoverAlgo>(); });
}
extern "C" void init_on_signal_plugin(bts::SparkServices& srv) {
DynamicOrderDescriptor on_signal;
on_signal.set_dynamic_order_type("OnSignal");
*on_signal.add_param_desc() = param_uint32("Time Hours");
*on_signal.add_param_desc() = param_uint32("Time Minutes");
*on_signal.add_param_desc() = param_uint32("Time Seconds");
*on_signal.add_param_desc() = param_bool("Preopen");
*on_signal.add_param_desc() = param_bool("Open");
// contracts who's signals have to be received to send the algo
*on_signal.add_param_desc() = param_security_key("Signal Contract 1");
size_t n_child_ords = 10;
for (size_t i=0; i< n_child_ords; i++){
auto idx = std::to_string(i+1);
*on_signal.add_param_desc() = param_security_key("Order " + idx + " Contract");
*on_signal.add_param_desc() = param_order("Order " + idx);
*on_signal.add_param_desc() = param_double("Order " + idx + " Price");
*on_signal.add_param_desc() = param_int32("Order " + idx + " Quantity");
}
srv.register_dynamic_algo_order(on_signal, []() { return std::make_unique<orderman::OnSignalAlgo>(); });
}
extern "C" void init_spark_plugin(bts::SparkServices &srv) {
auto& dyn_algo_config = config::get<config::toggles::DynamicAlgos>();
if(dyn_algo_config.enable_dorman())
@@ -224,6 +246,8 @@ extern "C" void init_spark_plugin(bts::SparkServices &srv) {
init_tascounter_plugin(srv);
if (dyn_algo_config.enable_limit_cover())
init_limit_cover(srv);
if (dyn_algo_config.enable_onsignal())
init_on_signal_plugin(srv);
}
}}}
@@ -26,17 +26,21 @@ enum class TASCounterAlgo::Status { WORKING, DONE };
// (the vwap of the TAS contracts is not used, but for uniformity is kept in the
// the same structure)
struct ContractMarketData{
private:
uint32_t tot_vol = 0;
double running_sum = 0;
public:
Book book;
double estimated_settle = 0.0;
void on_trade (const TickerUpdateEvent::Trade& t){
tot_vol += t.quantity.value();
running_sum += t.price.value()*t.quantity.value();
}
const double vwap(){
double vwap() const{
return tot_vol == 0 ? os::nan() : running_sum/tot_vol;
}
uint32_t traded_qty() const {return tot_vol;}
double traded_value() const {return running_sum;}
};
// the input for the t1 front, second and third orders, as well as the template
// and working order key are kept here
@@ -100,6 +104,47 @@ bool trade_within_time_win (const Timestamp&, const Timestamp&,
const TickerUpdateEvent::Trade& t);
bool now_within_time_win (const Timestamp&, const Timestamp&);
double compute_t1_front_settle (const AlgoData& d){
double rv = d.t1_front.vwap();
rv = round_to_nearest(rv,d.tick_size);
return rv;
}
double compute_t1_second_settle (const AlgoData& d){
double p_t1_front = compute_t1_front_settle(d);
double part_1 = d.t1_second.traded_value();
double part_2 = d.t2_front.traded_value() -
(d.t2_front.traded_qty() * p_t1_front);
uint32_t traded_qty_sum = d.t2_front.traded_qty() +
d.t1_second.traded_qty();
double rv = traded_qty_sum == 0 ? os::nan() :
(part_1+part_2)/traded_qty_sum;
LOG(OrderMan, Diag, "TASCounterAlgo::compute_t1_second_settle "
"p_t1_front:{} part_1:{} part_2:{} traded_qty_sum:{} rv:{}",
p_t1_front, part_1, part_2, traded_qty_sum, rv);
return round_to_nearest(rv,d.tick_size);
}
double compute_t1_third_settle (const AlgoData& d){
double p_t1_front = compute_t1_front_settle(d);
double p_t1_second = compute_t1_second_settle(d);
double part_1 = d.t1_third.traded_value();
double part_2 = d.t2_second.traded_value() -
(d.t2_second.traded_qty()*p_t1_second);
double part_3 = d.t3_front.traded_value() -
(d.t3_front.traded_qty()*p_t1_front) -
(d.t3_front.traded_qty()*p_t1_second);
double traded_qty_sum = d.t3_front.traded_qty() +
d.t2_second.traded_qty() +
d.t1_third.traded_qty();
double rv = traded_qty_sum == 0 ? os::nan() :
(part_1+part_2+part_3)/traded_qty_sum;
LOG(OrderMan, Diag, "TASCounterAlgo::compute_t1_third_settle "
"p_t1_front:{} p_t1_second:{} part_1:{} part_2:{} part_3:{} "
"traded_qty_sum:{} rv:{}",
p_t1_front, p_t1_second, part_1, part_2, part_3, traded_qty_sum, rv);
return round_to_nearest(rv,d.tick_size);
}
TASCounterAlgo::TASCounterAlgo() : me(new PImpl()) {}
TASCounterAlgo::~TASCounterAlgo() {}
@@ -214,17 +259,9 @@ void TASCounterAlgo::_cxl_and_end (bool is_error, const std::string& err_msg){
void TASCounterAlgo::_compute_settles(){
AlgoData& d = me->data;
d.t1_front.estimated_settle = d.t1_front.vwap();
d.t1_second.estimated_settle = d.t2_front.vwap() - d.t1_front.vwap();
d.t1_third.estimated_settle = d.t3_front.vwap() - d.t1_front.vwap() -
d.t1_second.vwap();
// round to closest value
d.t1_front.estimated_settle = round_to_nearest(
d.t1_front.estimated_settle,me->data.tick_size);
d.t1_second.estimated_settle = round_to_nearest(
d.t1_second.estimated_settle,me->data.tick_size);
d.t1_third.estimated_settle = round_to_nearest(
d.t1_third.estimated_settle,me->data.tick_size);
d.t1_front.estimated_settle = compute_t1_front_settle(d);
d.t1_second.estimated_settle = compute_t1_second_settle(d);
d.t1_third.estimated_settle = compute_t1_third_settle(d);
}
// the decision to send an order can be caused by
+1 -1
View File
@@ -1 +1 @@
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/FIXSim.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/ParseHistoricalFIX.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/Web.cpp;dspark/mirage/quick_queue/quick_queue.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/MultipartFormDecoder.cpp;http/WebServer.cpp;http/tests/MultiPartFormTests.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.cpp;md/platforms/ice_onixs/ICEListener.cpp;md/platforms/ice_onixs/ICEMain.cpp;md/platforms/ice_onixs/tests/ice_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;position/IPositionObserver.cpp;position/MarginServiceCore.cpp;position/MarginServiceServer.cpp;position/PositionAggregator.cpp;position/PositionMap.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/import/BasePositionServiceServer.cpp;position/import/DownloadCommonStateMachine.cpp;position/import/DownloadWindow.cpp;position/import/MoneyFileServiceServer.cpp;position/import/parsing/gmi_parser.cpp;position/import/parsing/money_parser.cpp;position/import/parsing/parser_common.cpp;position/tests/PositionTests.cpp;position/tests/position_tests_internal.cpp;subprocess/SubProcessRunner.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/FIXSim.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/ParseHistoricalFIX.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/TestCeloxica.cpp;cmds/Web.cpp;dspark/mirage/quick_queue/quick_queue.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/MultipartFormDecoder.cpp;http/WebServer.cpp;http/tests/MultiPartFormTests.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.cpp;md/platforms/celoxica/CXListener.cpp;md/platforms/celoxica/CXMain.cpp;md/platforms/celoxica/tests/cx_tests.cpp;md/platforms/ice_onixs/ICEListener.cpp;md/platforms/ice_onixs/ICEMain.cpp;md/platforms/ice_onixs/tests/ice_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;position/IPositionObserver.cpp;position/MarginServiceCore.cpp;position/MarginServiceServer.cpp;position/PositionAggregator.cpp;position/PositionMap.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/import/BasePositionServiceServer.cpp;position/import/DownloadCommonStateMachine.cpp;position/import/DownloadWindow.cpp;position/import/MoneyFileServiceServer.cpp;position/import/parsing/gmi_parser.cpp;position/import/parsing/money_parser.cpp;position/import/parsing/parser_common.cpp;position/tests/PositionTests.cpp;position/tests/position_tests_internal.cpp;subprocess/SubProcessRunner.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
+26
View File
@@ -0,0 +1,26 @@
#include "TestCeloxica.h"
#include "server/sparkd/md/platforms/celoxica/CXMain.h"
#include "server/sparkd/md/platforms/celoxica/CXListener.h"
namespace bts
{
namespace cmd
{
int TestCeloxica::execute(const std::vector<std::string>&)
{
bts::md::CXMain main;
main.initialize();
return 0;
}
std::string TestCeloxica::help()
{
return "\t test BTS <> Celoxica integration \n";
}
}
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "ICmd.h"
namespace bts
{
namespace cmd
{
class TestCeloxica : public ICmd
{
public:
int execute(std::vector<std::string> const &args);
std::string help();
};
}
}
+2
View File
@@ -19,6 +19,7 @@
#include "server/sparkd/cmds/RunSim.h"
#include "server/sparkd/cmds/Span.h"
#include "server/sparkd/cmds/Test.h"
#include "server/sparkd/cmds/TestCeloxica.h"
#include "server/sparkd/cmds/Web.h"
using namespace bts;
@@ -31,6 +32,7 @@ int main(int argc, char **argv) {
REGISTER(Help);
REGISTER(Run);
REGISTER(Md);
REGISTER(TestCeloxica);
REGISTER(ConfigCheck);
REGISTER(KeyCode);
REGISTER(RunSim);
@@ -0,0 +1,88 @@
#include "CXListener.h"
#include "lib/utils/defer.h"
#include "lib/utils/CallTimer.h"
#include "lib/utils/helper_macros.h"
#include <tuple>
namespace celoxica { namespace gmac {} }
#include "celoxica.h"
using namespace celoxica::gmac;
using namespace std;
namespace bts
{
namespace md
{
CXListener::CXListener(CXSecurityIndex& index, MDProducer& producer, SecurityIDRecorder& recorder,
co2::SettlementRecorder& settles, std::vector<MDProducerChannel> channels,
utils::Dispatcher& disp) :
_index(index),
_producer(producer),
_packet_arrived(),
_latest_sequence(0),
_security_recorder(recorder),
_settlement_recorder(settles),
_symbol_filter_storage(),
_channels(channels),
_background(disp),
_security_recorder_input_timer(disp, 900, [&]() { dequeue_securities(); }),
_security_recorder_input(),
_concurrency_monitor(0),
_book_updates()
{
_book_updates.reserve(32);
_security_recorder_input_timer.start();
}
CXListener::~CXListener()
{
_security_recorder_input_timer.stop();
}
void CXListener::add_software_filter(const std::string& symbol)
{
_symbol_filter_storage.push_back(symbol);
//_symbol_filter.clear();
//for (const auto& str : _symbol_filter_storage) {
// _symbol_filter.insert(onixs::StringRef(str));
//}
}
void CXListener::on_GMACMessageOrderDepthV3(const GMACMessageOrderDepthV3*)
{
}
void CXListener::dequeue_securities()
{
}
void CXListener::flush_books()
{
// This is called at the end of the packet
// Write all book updates to shared memory and reset for the next packet
//for (auto& entry : _book_updates) {
// // bbo first
// uint16_t sequence = (_latest_sequence & 0xFFFF);
// entry.handle.update_bbo(_packet_arrived, entry.bids[0], entry.asks[0], sequence);
// // Perf Note: the remainder of the book update could be done (carefully) with a memcpy
// for (size_t x = 1; x < COCONUT_MAX_DEPTH; ++x) {
// entry.handle.update_bid(_packet_arrived, entry.bids[x], x);
// entry.handle.update_ask(_packet_arrived, entry.asks[x], x);
// }
//}
//// Reset pending updates
//_book_updates.clear();
}
}
md::CXListener::BookData::BookData(MDProducerHandle & handle) : handle(handle), bids(), asks()
{
}
}//namespace
@@ -0,0 +1,61 @@
#pragma once
#include "lib/utils/bts_no_copy.h"
#include "lib/md/coconut2.h"
#include "lib/utils/Dispatcher.h"
#include "server/sparkd/md/platforms/celoxica/CXSecurityIndex.h"
#include <tbb/concurrent_queue.h>
#include <vector>
#include <atomic>
namespace bts
{
namespace md
{
struct GMACMessageOrderDepthV3;
class CXListener
{
public:
CXListener(CXSecurityIndex&, MDProducer&, SecurityIDRecorder&,
co2::SettlementRecorder&, std::vector<MDProducerChannel>, utils::Dispatcher&);
~CXListener();
void add_software_filter(const std::string& symbol);
void on_GMACMessageOrderDepthV3(const GMACMessageOrderDepthV3*);
private:
void flush_books();
void dequeue_securities();
private:
BTS_NO_COPY(CXListener);
CXSecurityIndex& _index;
MDProducer& _producer;
utils::TimeStamp _packet_arrived;
uint64_t _latest_sequence;
SecurityIDRecorder& _security_recorder;
co2::SettlementRecorder& _settlement_recorder;
std::vector<std::string> _symbol_filter_storage;
std::vector<MDProducerChannel> _channels;
utils::Dispatcher& _background;
utils::DispatcherTimer _security_recorder_input_timer;
tbb::concurrent_queue<CXSecurityData*> _security_recorder_input;
std::atomic<int> _concurrency_monitor;
struct BookData {
BookData(MDProducerHandle& handle);
MDProducerHandle handle;
PriceSize bids[10];
PriceSize asks[10];
};
std::vector<BookData> _book_updates;
};
}}//namespace
@@ -0,0 +1,83 @@
#include "CXMain.h"
#include "lib/md/cme/MDP3Utils.h"
#include "lib/config/config.h"
#include "lib/md/coconut2.h"
#include "lib/os/os.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/proto_helper.h"
#include "lib/utils/helper_macros.h"
#include "lib/config/protos/MarketData.pb.h"
#include "lib/config/protos/system.pb.h"
namespace celoxica { namespace gmac {} }
#include "celoxica.h"
extern "C"
{
#define __retcode_start__
#define __retcode_end__
}
#include <memory>
#include <set>
using namespace std;
using namespace celoxica::gmac;
namespace bts
{
namespace md
{
void run_cx_server(UserSymbol* psyms, size_t symcount)
{
CXMain main;
vector<UserSymbol> symbols;
for(size_t x = 0; x < symcount; ++x) {
symbols.push_back(psyms[x]);
}
main.run(symbols);
}
CXMain::CXMain() :
_producer(co2::PLATFORM_CELOXICA),
_security_recorder({}),
_settlement_recorder(co2::PLATFORM_CELOXICA),
_listeners()
{
}
CXMain::~CXMain() {}
void CXMain::initialize()
{
//celoxica::gmac::GMACIdentify(stderr);
//const auto& options = config::get<CMEOptions>();
}
void CXMain::run(std::vector<UserSymbol>& symbols)
{
bts::md::await_cme_config();
initialize();
parse_channels(symbols);
// const auto& channels = parse_channels(symbols);
}
std::unique_ptr<CXListener> CXMain::prepare_listener(const std::string& channel, const CMEChannel&)
{
UNUSED(channel);
return std::unique_ptr<CXListener>();
}
}
}
@@ -0,0 +1,49 @@
#pragma once
#include "lib/utils/bts_no_copy.h"
#include "lib/md/coconut2.h"
#include "server/sparkd/md/platforms/celoxica/CXListener.h"
#include "server/sparkd/md/platforms/celoxica/CXSecurityIndex.h"
#include <memory>
#include <vector>
#include <map>
namespace bts
{
namespace md
{
void run_cx_server(UserSymbol* sym, size_t symcount);
struct CMEChannel;
class CXListener;
/*
* The CX provider is very simple. 1 CXMain maps to 1 Celoxica Queue, which maps to 1 thread and one bound CPU.
* Control starts in CXMain which starts the thread, binds to a cpu, provides a (currently unused) list of all the symbols we need
* prepare_handler configures an exchange channel to be owned by this queue, and prepare_listener sets up thing that sees
* all callbacks which maps the exchange id to the MDProducerHandle and writes to shared mem
*/
class CXMain
{
public:
CXMain();
~CXMain();
void run(std::vector<UserSymbol>&); //call initialize, start the pump
void initialize(); //set up channels, etc
std::unique_ptr<CXListener> prepare_listener(const std::string& channel, const CMEChannel&);
private:
BTS_NO_COPY(CXMain);
CXSecurityIndex _index;
MDProducer _producer;
SecurityIDRecorder _security_recorder;
co2::SettlementRecorder _settlement_recorder;
std::vector<std::unique_ptr<CXListener>> _listeners;
};
}}//namespace
@@ -0,0 +1,21 @@
#pragma once
#include "lib/md/SecurityIndex.h"
#include <string>
namespace bts
{
namespace md
{
struct CXSecurityData
{
CXSecurityData(uint32_t id, MDProducerHandle& h) : id(id), handle(h) {}
uint32_t id; // exchange id
MDProducerHandle handle;
};
using CXSecurityIndex = ConcurrentSecurityIndex<uint32_t, CXSecurityData>;
}}//namespace
@@ -0,0 +1,15 @@
#pragma once
namespace celoxica
{
namespace gmac
{
#include "celoxica/gmac.h"
#include "celoxica/gmac_template_v3.h"
#include "celoxica/gmac_info.h"
}
}
@@ -0,0 +1,210 @@
// #include "catch/catch.hpp"
// #include "lib/config/config.h"
// #include "lib/config/protos/MarketData.pb.h"
// #include <vector>
// #include <string>
// #include <functional>
// #include <stdarg.h>
// #include "../MDP3Utils.h"
// #include "../onixs.h"
// using namespace bts::md;
// using namespace std;
// namespace bts {
// namespace md {
// map<string, onixs::FeedEngineSettings> create_feed_engine_settings(const std::vector<CMEChannel>& channels);
// const CMEOptions::CMEChannel channel_settings_for(const string& id);
// namespace test {
// vector<CMEChannel> test_channels(int count, ...) {
// // Doing this the C way because I still haven't updated VS
// vector<CMEChannel> result;
// va_list ap;
// va_start(ap, count);
// for (int x = 0; x < count; ++x) {
// const char* val = va_arg(ap, char*);
// CMEChannel temp;
// temp.id = val;
// result.push_back(temp);
// }
// va_end(ap);
// return result;
// }
// TEST_CASE("mdp3_tests","[md]") {
// SECTION("test_defaults") {
// config::internal::initialize_for_test("CMEOptions {}");
// vector<CMEChannel> channels = test_channels(1, "310");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// channels = test_channels(2, "310", "311");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// channels = test_channels(4, "310", "311", "400", "412");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// REQUIRE(!channel_settings_for("310").has_feed_engine());
// REQUIRE(!channel_settings_for("311").has_feed_engine());
// REQUIRE(!channel_settings_for("315").has_feed_engine());
// }
// SECTION("test_segregate_futures") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " feed_engine {\n"
// " name: 'default'\n"
// " }\n"
// " feed_engine {\n"
// " name: 'futures'\n"
// " num_threads: 4\n"
// " }\n"
// " # Futures\n"
// " channel {\n"
// " id: '310'\n"
// " id: '312'\n"
// " feed_engine: 'futures'\n"
// " }\n"
// " # Options\n"
// " channel {\n"
// " id: '311'\n"
// " id: '313'\n"
// " id: '315'\n"
// " feed_engine: 'default'\n"
// " }\n"
// " }\n"
// "}");
// vector<CMEChannel> channels = test_channels(1, "310");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result.begin()->first == "futures");
// REQUIRE(result.begin()->second.threadsCount() == 4);
// channels = test_channels(1, "311");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result.begin()->first == "default");
// REQUIRE(result.begin()->second.threadsCount() == 1);
// channels = test_channels(2, "311", "310");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 2);
// REQUIRE(result["default"].threadsCount() == 1);
// REQUIRE(result["futures"].threadsCount() == 4);
// // Test channels not listed in config
// channels = test_channels(2, "317", "318");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// }
// SECTION("test_no_default") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " feed_engine {\n"
// " name: 'futures'\n"
// " num_threads: 4\n"
// " }\n"
// " # any channel not listed will use the default feed engine\n"
// " # Futures\n"
// " channel {\n"
// " id: '310'\n"
// " id: '312'\n"
// " feed_engine: 'futures'\n"
// " }\n"
// " # Options\n"
// " channel {\n"
// " id: '311'\n"
// " id: '313'\n"
// " id: '315'\n"
// " use_tcp_replay: false\n"
// " }\n"
// " }\n"
// "}");
// vector<CMEChannel> channels = test_channels(1, "310");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result.begin()->first == "futures");
// REQUIRE(result.begin()->second.threadsCount() == 4);
// channels = test_channels(1, "311");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// channels = test_channels(2, "311", "310");
// result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// REQUIRE(result["futures"].threadsCount() == 4);
// REQUIRE(channel_settings_for("310").use_tcp_replay() == true);
// REQUIRE(channel_settings_for("311").use_tcp_replay() == false);
// REQUIRE(channel_settings_for("315").use_tcp_replay() == false);
// }
// SECTION("test_one_thread_each") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " channel {\n"
// " id: '*'\n"
// " }\n"
// " }\n"
// "}\n");
// vector<CMEChannel> channels = test_channels(1, "310", "311");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 0);
// const auto& settings = channel_settings_for("311");
// REQUIRE(!settings.has_feed_engine());
// }
// SECTION("test_common_futures_thread") {
// config::internal::initialize_for_test(
// "CMEOptions {\n"
// " value {\n"
// " feed_engine {\n"
// " name: 'futures'\n"
// " num_threads: 4\n"
// " }\n"
// " channel {\n"
// " id: '310'\n"
// " feed_engine: 'futures'\n"
// " udp_socket_buffer_size: 1234\n"
// " }\n"
// " channel {\n"
// " id: '*'\n"
// " }\n"
// " }\n"
// "}\n");
// vector<CMEChannel> channels = test_channels(6, "310", "311", "315", "100", "10", "42");
// auto result = create_feed_engine_settings(channels);
// REQUIRE(result.size() == 1);
// result = create_feed_engine_settings(test_channels(1, "315"));
// REQUIRE(result.size() == 0);
// REQUIRE(!channel_settings_for("311").has_feed_engine());
// REQUIRE(channel_settings_for("310").has_feed_engine());
// REQUIRE(channel_settings_for("310").udp_socket_buffer_size() == 1234);
// REQUIRE(channel_settings_for("311").has_udp_socket_buffer_size() == false);
// }
// }
// }}}//namespace
@@ -1,451 +1,421 @@
#include "ICEListener.h"
#include "lib/utils/datetime.h"
#include <vector>
#include "lib/utils/helper_macros.h"
#include "lib/config/protos/MarketData.pb.h"
#include "lib/md/protos/co2.pb.h"
using namespace std;
using namespace bts::co2;
using namespace bts::md::ice;
namespace bts {
namespace md {
namespace ice {
template <>
double double_of_price<onixs_ice::Rational>(const onixs_ice::Rational& price, int8_t denominator)
{
UNUSED(denominator);
double dbl = price.convertTo<double>();
return dbl;
}
template<>
void ICEListener::set_security_specific<onixs_ice::FuturesProductDefinition>(const onixs_ice::FuturesProductDefinition& def,
FixedSecurity& fsec)
{
//fsec.cash_per_point = double_of_price(def.tickValue, fsec.ice_price_denominator);
//fsec.cash_per_point = def.tickValue;
fsec.cash_per_point = def.unitQuantity;
}
ICEListener::ICEListener(ICESecurityIndex& index, MDProducer& producer, const std::vector<MDProducerChannel> channels) :
_index(index),
_producer(producer),
_symbol_filter(),
_channels(channels),
_concurrency_monitor(0)
{
}
ICEListener::~ICEListener() {}
std::string ICEListener::parse_contract_symbol(const std::string& sym)
{
auto end = sym.find(' ');
if(end == std::string::npos)
return sym;
else
return sym.substr(0, end);
}
//
//State change
void ICEListener::onHandlerStateChange(const onixs_ice::HandlerStateChange&)
{
}
void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , const onixs_ice::MessageInfo&)
{
}
//
// Warnings/Errors
void ICEListener::onError(const onixs_ice::Error& error)
{
std::string error_desc(error.description());
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
}
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
{
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
}
void ICEListener::onWarning(const onixs_ice::Warning& warning)
{
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
}
//
// Security discovery
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
{
if(msg.isSpread)
on_future_strip(msg, is_interested);
else
on_future(msg, is_interested);
LOG(md,Info,"ICE_Future,{},{},{},{},{},{},{},{},{},{},{},{},{}",
is_interested,
msg.productName, // NG Swing GDD Futures,
msg.hubAlias, // Henry
msg.marketDesc, // NG Swing GDD Futures - Henry - 10 Jul 20,
msg.stripName, // 10 Jul 20
msg.contractSymbol, // CGS SDN1020.N1020,
msg.productId,
msg.marketId,
msg.requestMarketType,
msg.securitySubType,
msg.contractSymbolExtra,
msg.clearedAlias,
msg.isSpread);
}
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
{
on_future(msg, is_interested);
}
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& msg, bool& is_interested)
{
on_uds_future_spread(msg, is_interested);
LOG(md,Info,"ICE_Strategy,{},{},{},{},{},{},{},{},{},{},{},{},{},{}",
is_interested,
msg.productName,
msg.hubAlias,
msg.marketDesc,
msg.stripName,
msg.contractSymbol,
msg.productId,
msg.marketId,
msg.requestMarketType,
msg.securitySubType,
log::CharPtrWrapper {""},
msg.clearedAlias,
msg.productName,
0);
}
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
{
on_uds_future_spread(msg, is_interested);
}
void ICEListener::onOptionsProductDefinition(const onixs_ice::OptionsProductDefinition& msg, bool& is_interested)
{
UNUSED(msg);
UNUSED(is_interested);
}
void ICEListener::onNewOptionsMarketDefinition(const onixs_ice::NewOptionsMarketDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg);
UNUSED(is_interested);
UNUSED(msg_info);
}
void ICEListener::onOptionsStrategyDefinition(const onixs_ice::OptionsStrategyDefinition& msg, bool& is_interested)
{
UNUSED(msg);
UNUSED(is_interested);
}
void ICEListener::onNewOptionsStrategyDefinition(const onixs_ice::NewOptionsStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg);
UNUSED(is_interested);
UNUSED(msg_info);
}
//
void ICEListener::onMarketSnapshot(const onixs_ice::MarketSnapshot& msg, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg_info);
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
auto ts = utils::TimeStamp::now();
auto settle_denominator = data->handle.security().ice_settle_denominator;
if (msg.settlePriceDateTime > 0) {
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(msg.settlePriceDateTime) );
}
LOG(md,Diag,"OnixSICE Got Market Snap : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
auto deal_denominator = data->handle.security().ice_deal_denominator;
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), datetime::today() );
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
data->handle.set_last_trade( ts, pricesize_of_price_size(msg.lastTradePrice, deal_denominator, msg.lastTradeQuantity),
ll_of_timestamp(msg.lastTradeDateTime), msg.volume );
}
}
void ICEListener::onMarketStatistics(const onixs_ice::MarketStatistics& msg, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg_info);
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
data->handle.set_total_volume(msg.volume);
}
}
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
void ICEListener::onMarketSnapshotOrder(const onixs_ice::MarketSnapshotOrder& msg, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg);
UNUSED(msg_info);
}
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPriceLevel& msg, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg);
UNUSED(msg_info);
}
//
// Book
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
{
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
auto data = _index.get_security(onixs_book.marketId());
if (data == nullptr)
return;
bts_assert(data->id == onixs_book.marketId());
auto denominator = data->handle.security().ice_price_denominator;
// Overwrite all entries every time
size_t bids_size = onixs_book.bids().size();
size_t asks_size = onixs_book.offers().size();
if (!data->handle.has_depth())
{
bids_size = std::min(bids_size, size_t { 1 });
asks_size = std::min(asks_size, size_t { 1 });
}
std::array<PriceSize,COCONUT_MAX_DEPTH> dbids; dbids.fill(NO_MARKET);
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> ibids; ibids.fill(NO_MARKET);
std::array<PriceSize,COCONUT_MAX_DEPTH> dasks; dasks.fill(NO_MARKET);
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> iasks; iasks.fill(NO_MARKET);
for (size_t x = 0; x < bids_size; ++x)
{
auto& bid = onixs_book.bids()[x];
if(x < COCONUT_MAX_DEPTH)
dbids[x] = pricesize_of_price_size(bid.price, denominator, bid.quantity - bid.impliedQuantity);
if(x < COCONUT_MAX_IMPLIED_DEPTH)
ibids[x] = pricesize_of_price_size(bid.price, denominator, bid.impliedQuantity);
}
for (size_t x = 0; x < asks_size; ++x)
{
auto& ask = onixs_book.offers()[x];
if(x < COCONUT_MAX_DEPTH)
dasks[x] = pricesize_of_price_size(ask.price, denominator, ask.quantity - ask.impliedQuantity);
if(x < COCONUT_MAX_IMPLIED_DEPTH)
iasks[x] = pricesize_of_price_size(ask.price, denominator, ask.impliedQuantity);
}
data->handle.update_books(TimeStamp::now(), dbids, dasks, ibids, iasks, (uint32_t) msg_info.sequenceNumber);
}
//
// Trades
AggressorSide map_aggressor(onixs_ice::AggressorSide::Enum agside)
{
switch(agside) {
default:
case onixs_ice::AggressorSide::NoAggressor:
return AGGRESSOR_SIDE_NONE;
case onixs_ice::AggressorSide::Buy:
return AGGRESSOR_SIDE_BUY;
case onixs_ice::AggressorSide::Sell:
return AGGRESSOR_SIDE_SELL;
}
}
void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size,
onixs_ice::AggressorSide::Enum agside, const onixs_ice::DateTime& onixs_date, bool from_spread)
{
auto ts = utils::TimeStamp::now();
PriceSize ps = pricesize_of_price_size(price, data->handle.security().ice_deal_denominator, size);
auto date = ll_of_timestamp(onixs_date);
data->handle.add_trade(ts, ps, date, map_aggressor(agside), 0u, from_spread);
}
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != NULL)
{
bts_assert(data->id == msg.marketId);
auto size = msg.quantity;
auto price = msg.price;
onixs_ice::AggressorSide::Enum agside = msg.aggressorSide;
on_trade(data, price, size, agside, msg.transactDateTime, msg.isSystemPricedLeg);
}
}
//
// RFQ
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
auto ts = utils::TimeStamp::now();
co2::QuoteRequestMessage message;
message.set_key(data->handle.key());
auto time = ll_of_timestamp(msg.messageTimestamp);
message.set_time(time);
long long rfqid = msg.rfqSystemId;
message.set_request_id(std::to_string(rfqid));
//https://ref.onixs.biz/cpp-ice-impact-handler-guide/struct_onix_s_1_1_i_c_e_1_1i_mpact_1_1_market_data_1_1_side.html#a8150b7776c2a1749101acf22e868d091
switch(msg.side)
{
case ' ':
message.set_side(co2::QuoteRequestMessage::UNDEFINED);
break;
case '1':
message.set_side(co2::QuoteRequestMessage::BUY);
break;
case '2':
message.set_side(co2::QuoteRequestMessage::SELL);
break;
}
message.set_size(msg.quantity);
data->handle.add_rfq(ts, message);
}
}
//
// Misc
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
auto ts = utils::TimeStamp::now();
auto price = msg.settlementPrice;
auto time = msg.dateTime;
auto denominator = data->handle.security().ice_settle_denominator;
LOG(md,Diag,"OnixSICE Got Settlement: {} - {}", std::to_string(data->id), std::to_string(price));
data->handle.update_settlement(ts, convert_price(price, denominator), date_of_timestamp(time));
}
}
void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs_ice::MessageInfo &msg_info)
{
UNUSED(msg);
UNUSED(msg_info);
}
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
onixs_ice::Price price = msg.openPrice;
onixs_ice::DateTime time = msg.dateTime;
auto denominator = data->handle.security().ice_deal_denominator;
data->handle.update_opening( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
}
}
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
onixs_ice::Price price = msg.closePrice;
onixs_ice::DateTime time = msg.dateTime;
auto denominator = data->handle.security().ice_deal_denominator;
data->handle.update_closing( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
}
}
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
onixs_ice::DateTime time = msg.dateTime;
auto ts = utils::TimeStamp::now();
auto settle_denominator = data->handle.security().ice_settle_denominator;
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(time) );
LOG(md,Warn,"OnixSICE Got EOD Market : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
auto deal_denominator = data->handle.security().ice_deal_denominator;
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), date_of_timestamp(time) );
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
}
}
//
// Feed
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
{
UNUSED(feedId);
}
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
{
UNUSED(feedId);
UNUSED(marketIds);
}
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
{}
void ICEListener::onFeedStopped(onixs_ice::FeedId)
{}
void ICEListener::onSessionNumberChange(onixs_ice::FeedId , short , short )
{}
//
// Non-onixs calls
void ICEListener::add_filter_symbol(const std::string& symbol)
{
LOG(md, Info, "ICEListener::add_filter_symbol: adding {}", symbol);
_symbol_filter.insert(symbol);
}
void ICEListener::heartbeat()
{
for (auto& c : _channels) { c.heartbeat(); }
}
//
// Private functions
void ICEListener::add_security(const FixedSecurity& security)
{
// Set up the shared memory for this security
auto handle = _producer.add_security(security);
bts_assert(security.numeric_id != 0);
// Add it to the index so it can receive messages
_index.add_security(security.numeric_id, handle);
}
void ICEListener::update_all_status(CoconutSecurityStatus status)
{
_index.walk_security_list([=](ICESecurityData& data)
{
data.handle.set_status(status);
});
}
}}} //namespace
#include "ICEListener.h"
#include "lib/utils/datetime.h"
#include <vector>
#include "lib/utils/helper_macros.h"
#include "lib/config/protos/MarketData.pb.h"
#include "lib/md/protos/co2.pb.h"
using namespace std;
using namespace bts::co2;
using namespace bts::md::ice;
namespace bts {
namespace md {
namespace ice {
template <>
double double_of_price<onixs_ice::Rational>(const onixs_ice::Rational& price, int8_t denominator)
{
UNUSED(denominator);
double dbl = price.convertTo<double>();
return dbl;
}
template<>
void ICEListener::set_security_specific<onixs_ice::FuturesProductDefinition>(const onixs_ice::FuturesProductDefinition& def,
FixedSecurity& fsec)
{
//fsec.cash_per_point = double_of_price(def.tickValue, fsec.ice_price_denominator);
//fsec.cash_per_point = def.tickValue;
fsec.cash_per_point = def.unitQuantity;
}
ICEListener::ICEListener(ICESecurityIndex& index, MDProducer& producer, const std::vector<MDProducerChannel> channels) :
_index(index),
_producer(producer),
_symbol_filter(),
_channels(channels),
_concurrency_monitor(0)
{
}
ICEListener::~ICEListener() {}
std::string ICEListener::parse_contract_symbol(const std::string& sym)
{
auto end = sym.find(' ');
if(end == std::string::npos)
return sym;
else
return sym.substr(0, end);
}
//
//State change
void ICEListener::onHandlerStateChange(const onixs_ice::HandlerStateChange&)
{
}
void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , const onixs_ice::MessageInfo&)
{
}
//
// Warnings/Errors
void ICEListener::onError(const onixs_ice::Error& error)
{
std::string error_desc(error.description());
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
}
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
{
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
}
void ICEListener::onWarning(const onixs_ice::Warning& warning)
{
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
}
//
// Security discovery
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
{
if(msg.isSpread)
on_future_strip(msg, is_interested);
else
on_future(msg, is_interested);
}
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
{
on_future(msg, is_interested);
}
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& def, bool& is_interested)
{
on_uds_future_spread(def, is_interested);
}
//This is only for UDS
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
{
on_uds_future_spread(msg, is_interested);
}
void ICEListener::onOptionsProductDefinition(const onixs_ice::OptionsProductDefinition& msg, bool& is_interested)
{
UNUSED(msg);
UNUSED(is_interested);
}
void ICEListener::onNewOptionsMarketDefinition(const onixs_ice::NewOptionsMarketDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg);
UNUSED(is_interested);
UNUSED(msg_info);
}
void ICEListener::onOptionsStrategyDefinition(const onixs_ice::OptionsStrategyDefinition& msg, bool& is_interested)
{
UNUSED(msg);
UNUSED(is_interested);
}
void ICEListener::onNewOptionsStrategyDefinition(const onixs_ice::NewOptionsStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg);
UNUSED(is_interested);
UNUSED(msg_info);
}
//
void ICEListener::onMarketSnapshot(const onixs_ice::MarketSnapshot& msg, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg_info);
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
auto ts = utils::TimeStamp::now();
auto settle_denominator = data->handle.security().ice_settle_denominator;
if (msg.settlePriceDateTime > 0) {
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(msg.settlePriceDateTime) );
}
LOG(md,Diag,"OnixSICE Got Market Snap : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
auto deal_denominator = data->handle.security().ice_deal_denominator;
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), datetime::today() );
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
data->handle.set_last_trade( ts, pricesize_of_price_size(msg.lastTradePrice, deal_denominator, msg.lastTradeQuantity),
ll_of_timestamp(msg.lastTradeDateTime), msg.volume );
}
}
void ICEListener::onMarketStatistics(const onixs_ice::MarketStatistics& msg, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg_info);
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
data->handle.set_total_volume(msg.volume);
}
}
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
void ICEListener::onMarketSnapshotOrder(const onixs_ice::MarketSnapshotOrder& msg, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg);
UNUSED(msg_info);
}
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPriceLevel& msg, const onixs_ice::MessageInfo& msg_info)
{
UNUSED(msg);
UNUSED(msg_info);
}
//
// Book
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
{
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
auto data = _index.get_security(onixs_book.marketId());
if (data == nullptr)
return;
bts_assert(data->id == onixs_book.marketId());
auto denominator = data->handle.security().ice_price_denominator;
// Overwrite all entries every time
size_t bids_size = onixs_book.bids().size();
size_t asks_size = onixs_book.offers().size();
if (!data->handle.has_depth())
{
bids_size = std::min(bids_size, size_t { 1 });
asks_size = std::min(asks_size, size_t { 1 });
}
std::array<PriceSize,COCONUT_MAX_DEPTH> dbids; dbids.fill(NO_MARKET);
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> ibids; ibids.fill(NO_MARKET);
std::array<PriceSize,COCONUT_MAX_DEPTH> dasks; dasks.fill(NO_MARKET);
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> iasks; iasks.fill(NO_MARKET);
for (size_t x = 0; x < bids_size; ++x)
{
auto& bid = onixs_book.bids()[x];
if(x < COCONUT_MAX_DEPTH)
dbids[x] = pricesize_of_price_size(bid.price, denominator, bid.quantity - bid.impliedQuantity);
if(x < COCONUT_MAX_IMPLIED_DEPTH)
ibids[x] = pricesize_of_price_size(bid.price, denominator, bid.impliedQuantity);
}
for (size_t x = 0; x < asks_size; ++x)
{
auto& ask = onixs_book.offers()[x];
if(x < COCONUT_MAX_DEPTH)
dasks[x] = pricesize_of_price_size(ask.price, denominator, ask.quantity - ask.impliedQuantity);
if(x < COCONUT_MAX_IMPLIED_DEPTH)
iasks[x] = pricesize_of_price_size(ask.price, denominator, ask.impliedQuantity);
}
data->handle.update_books(TimeStamp::now(), dbids, dasks, ibids, iasks, (uint32_t) msg_info.sequenceNumber);
}
//
// Trades
AggressorSide map_aggressor(onixs_ice::AggressorSide::Enum agside)
{
switch(agside) {
default:
case onixs_ice::AggressorSide::NoAggressor:
return AGGRESSOR_SIDE_NONE;
case onixs_ice::AggressorSide::Buy:
return AGGRESSOR_SIDE_BUY;
case onixs_ice::AggressorSide::Sell:
return AGGRESSOR_SIDE_SELL;
}
}
void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size,
onixs_ice::AggressorSide::Enum agside, const onixs_ice::DateTime& onixs_date, bool from_spread)
{
auto ts = utils::TimeStamp::now();
PriceSize ps = pricesize_of_price_size(price, data->handle.security().ice_deal_denominator, size);
auto date = ll_of_timestamp(onixs_date);
data->handle.add_trade(ts, ps, date, map_aggressor(agside), 0u, from_spread);
}
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != NULL)
{
bts_assert(data->id == msg.marketId);
auto size = msg.quantity;
auto price = msg.price;
onixs_ice::AggressorSide::Enum agside = msg.aggressorSide;
on_trade(data, price, size, agside, msg.transactDateTime, msg.isSystemPricedLeg);
}
}
//
// RFQ
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
auto ts = utils::TimeStamp::now();
co2::QuoteRequestMessage message;
message.set_key(data->handle.key());
auto time = ll_of_timestamp(msg.messageTimestamp);
message.set_time(time);
long long rfqid = msg.rfqSystemId;
message.set_request_id(std::to_string(rfqid));
//https://ref.onixs.biz/cpp-ice-impact-handler-guide/struct_onix_s_1_1_i_c_e_1_1i_mpact_1_1_market_data_1_1_side.html#a8150b7776c2a1749101acf22e868d091
switch(msg.side)
{
case ' ':
message.set_side(co2::QuoteRequestMessage::UNDEFINED);
break;
case '1':
message.set_side(co2::QuoteRequestMessage::BUY);
break;
case '2':
message.set_side(co2::QuoteRequestMessage::SELL);
break;
}
message.set_size(msg.quantity);
data->handle.add_rfq(ts, message);
}
}
//
// Misc
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
auto ts = utils::TimeStamp::now();
auto price = msg.settlementPrice;
auto time = msg.dateTime;
auto denominator = data->handle.security().ice_settle_denominator;
LOG(md,Diag,"OnixSICE Got Settlement: {} - {}", std::to_string(data->id), std::to_string(price));
data->handle.update_settlement(ts, convert_price(price, denominator), date_of_timestamp(time));
}
}
void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs_ice::MessageInfo &msg_info)
{
UNUSED(msg);
UNUSED(msg_info);
}
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
onixs_ice::Price price = msg.openPrice;
onixs_ice::DateTime time = msg.dateTime;
auto denominator = data->handle.security().ice_deal_denominator;
data->handle.update_opening( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
}
}
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
onixs_ice::Price price = msg.closePrice;
onixs_ice::DateTime time = msg.dateTime;
auto denominator = data->handle.security().ice_deal_denominator;
data->handle.update_closing( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
}
}
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
{
auto* data = _index.get_security(msg.marketId);
if (data != nullptr)
{
bts_assert(data->id == msg.marketId);
onixs_ice::DateTime time = msg.dateTime;
auto ts = utils::TimeStamp::now();
auto settle_denominator = data->handle.security().ice_settle_denominator;
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(time) );
LOG(md,Warn,"OnixSICE Got EOD Market : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
auto deal_denominator = data->handle.security().ice_deal_denominator;
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), date_of_timestamp(time) );
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
}
}
//
// Feed
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
{
UNUSED(feedId);
}
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
{
UNUSED(feedId);
UNUSED(marketIds);
}
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
{}
void ICEListener::onFeedStopped(onixs_ice::FeedId)
{}
void ICEListener::onSessionNumberChange(onixs_ice::FeedId , short , short )
{}
//
// Non-onixs calls
void ICEListener::add_filter_symbol(const std::string& symbol)
{
_symbol_filter.insert(symbol);
}
void ICEListener::heartbeat()
{
for (auto& c : _channels) { c.heartbeat(); }
}
//
// Private functions
void ICEListener::add_security(const FixedSecurity& security)
{
// Set up the shared memory for this security
auto handle = _producer.add_security(security);
bts_assert(security.numeric_id != 0);
// Add it to the index so it can receive messages
_index.add_security(security.numeric_id, handle);
}
void ICEListener::update_all_status(CoconutSecurityStatus status)
{
_index.walk_security_list([=](ICESecurityData& data)
{
data.handle.set_status(status);
});
}
}}} //namespace
@@ -119,20 +119,7 @@ private:
enum SecurityFilterAction { ADD, SKIP, DELETE };
std::string parse_contract_symbol(const std::string& sym);
template<typename Definition>
std::string get_contract_symbol(const Definition& def){
const auto& sym = def.contractSymbol;
// TODO : this is only exception right now but for HHD sstrips
// def.contractSymbol is empty !
if (sym.size() == 0 && def.marketDesc.rfind("NG Swing GDD Futures - Henry", 0) == 0)
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)
{
@@ -143,7 +130,7 @@ private:
}
if(_symbol_filter.count("*") > 0)
return ADD;
return _symbol_filter.count(get_contract_symbol(def)) > 0 ? ADD : SKIP;
return _symbol_filter.count( parse_contract_symbol(def.contractSymbol) ) > 0 ? ADD : SKIP;
}
void flush_books();
void update_all_status(co2::CoconutSecurityStatus status);
@@ -159,7 +146,7 @@ template<typename Definition>
void ICEListener::set_security_common(const Definition& def, FixedSecurity& fsec)
{
fsec.numeric_id = def.marketId;
std::string sym = get_contract_symbol(def);
std::string sym = parse_contract_symbol(def.contractSymbol);
if(!sym.empty())
{
fsec.symbol( sym );
@@ -178,7 +165,7 @@ void ICEListener::set_security_common(const Definition& def, FixedSecurity& fsec
fsec.ice_settle_denominator = (int8_t)(def.settlePriceDenominator - '0');
double min_tick = double_of_price(def.incrementPrice, fsec.ice_price_denominator);
fsec.low_tick = fsec.high_tick = min_tick;
fsec.price_break = 0.0;
fsec.price_break = 0.0;
fsec.ice_market_description(def.marketDesc);
fsec.ice_cleared_alias(def.clearedAlias);
@@ -220,12 +207,12 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
// auto expiration = date_of_maturity(sec);
// if (expiration < datetime::today()) return;
FixedSpreadSecurityXL fsec;
FixedSpreadSecurityL fsec;
set_security_common(def, fsec);
fsec.alt_symbol(def.strategySymbol);
const auto& legs = def.legs;
if (legs.size() > 0 && legs.size() <= 512) {
const auto& legs = def.legs;
if (legs.size() > 0) {
fsec.set_leg_count(legs.size());
for(size_t x = 0; x < fsec.leg_count(); ++x)
{
@@ -251,8 +238,6 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
add_security(fsec);
is_interested = true;
}
else
LOG(md, Error, "Ignoring security {} with {} legs", def.marketDesc, legs.size());
}
@@ -263,7 +248,7 @@ void ICEListener::on_future_strip(const FutureDefinition& def, bool& is_interest
auto exchange_id = def.marketId;
if (filter_security(exchange_id, def) != ADD) return;
FixedSpreadSecurityXL fsec;
FixedSpreadSecurityXS fsec;
set_security_common(def, fsec);
fsec.alt_symbol(def.stripName);
@@ -73,17 +73,31 @@ onixs_ice::MarketSubscriptions market_subcription_for_channel(const ICEChannel&
onixs_ice::MarketSubscriptions subs;
for(const auto& x : chan.market_types)
{
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
subs.push_back(futures);
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
subs.push_back(spreads);
//onixs_ice::MarketSubType::NonImplied
if(x == 26)
{
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
subs.push_back(futures);
}
else
{
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::FullImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
subs.push_back(futures);
}
}
for(const auto& x : chan.uds_market_types)
{
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
subs.push_back(spreads);
}
if(x == 26)
{
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
subs.push_back(spreads);
}
else
{
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::FullImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
subs.push_back(spreads);
}
}
return subs;
}
@@ -138,7 +138,6 @@ struct PositionServicePNLHelper::PImpl {
void PositionServicePNLHelper::compute_pnl(
const std::map<int, PositionUnit const*>& in_pos,
const std::map<std::string,uint32_t>& account_map,
const std::map<uint32_t,double>& cpp_override_map,
std::vector<PNLUnit>& out_pnl,
std::vector<AccountPNL>& out_account_pnl){
out_account_pnl.clear();
@@ -146,8 +145,6 @@ void PositionServicePNLHelper::compute_pnl(
const PositionUnit& pu = *(p.second);
uint32_t sec_key = pu.security_id();
double cpp = me->handle_index.get_cpp(SecurityKey(sec_key));
auto it = cpp_override_map.find(sec_key);
cpp = (it != cpp_override_map.end()) ? it->second : cpp;
double bid, ask, last, settle;
me->handle_index.get_md(SecurityKey(sec_key),bid,ask,last,settle);
PNLUnit pnlu;
@@ -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,7 +29,6 @@ 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,7 +28,6 @@ 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;
@@ -38,7 +37,6 @@ 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;
@@ -49,7 +47,6 @@ 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,
@@ -67,8 +64,6 @@ 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),
@@ -78,9 +73,7 @@ struct PositionServiceServer::PImpl {
// admin
admin_service(context.locate<bts::api::admin::AdminService>()),
admin_data(),
account_map(),
cpp_override_map()
{}
account_map() {}
};
PositionServiceServer::PositionServiceServer(bts::api::ServiceContext &context,
@@ -203,27 +196,6 @@ 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(
@@ -322,30 +294,6 @@ 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() {
@@ -353,8 +301,7 @@ 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, me->cpp_override_map,
pnl, account_pnl);
me->pnl_helper.compute_pnl(in_pos, me->account_map, pnl, account_pnl);
for (const auto &u : pnl)
me->pnl_publisher.add_or_update(u);
for (const auto &u : account_pnl)
@@ -52,7 +52,6 @@ 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 &);
@@ -75,11 +74,6 @@ 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();
};
+10 -1
View File
@@ -2,6 +2,11 @@
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export PATH="$PATH:/root/.dotnet/tools"
# install npx
if ! [ -x "$(command -v npx)" ]; then
npm install -g npx
fi
export OUT=${BUILD_DIR:-../../../build}/web
MODE=manual
case "$OUT" in
@@ -34,6 +39,9 @@ PROTOC=../../../externals/linux/bin/protoc
ORANGE='\033[0;33m'
NOCOLOR='\033[0m'
npm install
# Run pbjs to produce protobuf module
echo -e \* ${ORANGE} pbjs ${NOCOLOR}
npx --no-install pbjs -t json-module -w commonjs -o $OUT/api/proto.js $PROTO_LIST
@@ -71,10 +79,11 @@ if [ "$FULL" == "1" ]; then
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
echo -e \* ${ORANGE} fable ${NOCOLOR}
cd ../ui/
npm install
cd src
mkdir -p $OUT/ui/
cp -r ../public/* $OUT/ui/
dotnet restore -s ../../../../externals/nuget/packages
dotnet restore
if [ "$MODE" == "release" ]; then
dotnet fable webpack -- --env.BUILD_PATH=$OUT -p
else
+22
View File
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.5)
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libbase.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libutils.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libfix_lib.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclxlog.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libade_version.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libade.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclx.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libgmac_version.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libgmac.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libcputimestamp.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclxconfig.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libconfigfile.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libac.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/librc.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/liblldt.so")
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libregister.so")
file(COPY ${CELOXICA_LIB} DESTINATION ${CMAKE_BINARY_DIR}/src/server/sparkd)
+1 -1
View File
@@ -7,7 +7,7 @@ if not exist build\src\lib\libspark.vcxproj (
)
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools\VsDevCmd.bat"
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln /p:Configuration=release
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore spark_client.sln /p:Configuration=release
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR_AND_END
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary spark_client.sln /p:Configuration=release
+4 -3
View File
@@ -1,8 +1,9 @@
#!/bin/bash
BUILD=clang_debug
BUILD_LOC=/home/ppinto/temp/spark_build
if [ -f /.dockerenv ]; then
(cd `readlink -f .` && ninja)
cd $BUILD_LOC/$BUILD && ninja
else
cid=`docker ps | grep -P "emacs_ide|bluetradingsystems/linux_docker" | awk '{print $1}'`
docker exec $cid /bin/bash -c "(cd `readlink -f .` && ninja)"
docker exec $cid /bin/bash -c "cd $BUILD_LOC/$BUILD && ninja"
fi
+2 -2
View File
@@ -2,6 +2,6 @@
setlocal
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\Tools\VsDevCmd.bat" > nul
cd /d %~dp0/../
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore spark_client.sln
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore spark_client.sln
-5
View File
@@ -4,11 +4,6 @@ SRC_ROOT=$DIR/../
mkdir -p $SRC_ROOT/build/
BUILD_ROOT=`readlink -f $SRC_ROOT/build`
rm -f $SRC_ROOT/src/web/ui/node_modules
rm -f $SRC_ROOT/src/web/api/node_modules
ln -s $SRC_ROOT/externals/npm/node_modules $SRC_ROOT/src/web/ui/node_modules
ln -s $SRC_ROOT/externals/npm/node_modules $SRC_ROOT/src/web/api/node_modules
rm -rf $BUILD_ROOT/clang_debug
mkdir $BUILD_ROOT/clang_debug
cd $BUILD_ROOT/clang_debug
Executable → Regular
View File
File mode changed.