Compare commits
35
Commits
2.15.10
...
20.0.0_rc0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3230fb9e48 | ||
|
|
888b4e22c0 | ||
|
|
534ce5a8ec | ||
|
|
c2b9ac57dc | ||
|
|
2783361325 | ||
|
|
46d35131d7 | ||
|
|
3b95154c04 | ||
|
|
1a6b918853 | ||
|
|
bc22605a25 | ||
|
|
f3d28add1c | ||
|
|
63d260a6f8 | ||
|
|
9b4a63b4a8 | ||
|
|
a502c365e5 | ||
|
|
fdacd8b543 | ||
|
|
3d1de9a0c7 | ||
|
|
b2b3fa5949 | ||
|
|
86ecf69820 | ||
|
|
8f537a8fda | ||
|
|
01b9ff6b47 | ||
|
|
2609663d3e | ||
|
|
fd89241611 | ||
|
|
a69118c5d3 | ||
|
|
5accd0e2b4 | ||
|
|
df5ad61cd2 | ||
|
|
4a7674eb29 | ||
|
|
5a458287f4 | ||
|
|
5921f9f6b5 | ||
|
|
4c73a63ae8 | ||
|
|
42b1c921a8 | ||
|
|
fab3334b1e | ||
|
|
7239dd3ade | ||
|
|
dd4c8eb649 | ||
|
|
76a29bdc8a | ||
|
|
06d708f3ea | ||
|
|
e2f70cbc19 |
No files matched your search
+1
-1
@@ -55,7 +55,7 @@ else ()
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0 ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||
COMMENT "Creating server runtime layout"
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
+++
|
||||
+++
|
||||
title = "Release notes"
|
||||
weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.16.2
|
||||
### User Visible Change
|
||||
- Synthetic creation dialog has a "fractional" checkbox next to the tick settings. Users may uncheck this box to enter integer tick sizes, so the synthetic security's prices will be displayed without decimals, or fractional units.
|
||||
## 2.16.1
|
||||
### Bug Fixes
|
||||
- OrderBook's algo state parameters (in the panel on the right side) are sorted alphabetically
|
||||
## 2.16.0
|
||||
### Bug Fixes
|
||||
- Solved an issue that could result in incorrect ladder levels
|
||||
- Solved an issue that could lead to incorrect routing of orders in the event of certain MSGW changes
|
||||
### API Changes
|
||||
- Add C# client side API samples for order entry, position monitoring and market data observation
|
||||
|
||||
## 2.15.10
|
||||
### Bug Fixes
|
||||
- Solved an issue that could cause the user interface to fail when playing some alert sounds
|
||||
@@ -715,3 +728,4 @@ insert_anchor_links = "right"
|
||||
- Start, Day and Total PnL columns are painted with a red or green background color based on if the pnl value is negative or positive respectively
|
||||
- Corrected cash per point values, used in pnl calculations, for a number of products
|
||||
- PnL report will now remember any customizations you make to filtering and/or sorting criteria
|
||||
|
||||
+1
-1
Submodule docker/linux updated: 0afd5e92de...296f277d40.
+1
-1
Submodule externals updated: 38945c5f49...2ea5e355a6.
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "2.1.508",
|
||||
"rollForward": "latestMajor"
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,10 @@
|
||||
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(VCTargetsPath)'==''">
|
||||
<VCTargetsPath>C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\VC\VCTargets</VCTargetsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,370 +1,372 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM {
|
||||
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AnimationManager _animation = new AnimationManager();
|
||||
readonly ObservableCollection<OrderRecordImmutable> _orders = new ObservableCollection<OrderRecordImmutable>();
|
||||
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<bool> _last_price_from_spread;
|
||||
readonly CellProperty<int> _position;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
readonly CellProperty<bool> _inside_bids, _inside_asks;
|
||||
readonly CellProperty<int> _working_bid_volume, _working_ask_volume;
|
||||
readonly CellProperty<double> _working_best_bid, _working_best_ask;
|
||||
readonly CellProperty<string> _working_bids_text, _working_asks_text;
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
double _raw_day_position;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_security_text = smd.security.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
string format_price(double price) => security.ContractSpec.format(price);
|
||||
_position = new CellProperty<int>(this, nameof(position), 0);
|
||||
_bid_price = backing(nameof(bid_price), double.NaN);
|
||||
_ask_price = backing(nameof(ask_price), double.NaN);
|
||||
_last_price = backing(nameof(last_price), double.NaN);
|
||||
_last_size = new CellProperty<uint>(this, nameof(last_size), 0);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
|
||||
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
|
||||
_inside_asks = new CellProperty<bool>(this, nameof(inside_asks));
|
||||
_working_bid_volume = new CellProperty<int>(this, nameof(working_bid_volume));
|
||||
_working_ask_volume = new CellProperty<int>(this, nameof(working_ask_volume));
|
||||
//
|
||||
_bid_direct_size = backing(nameof(bid_direct_size), default(uint));
|
||||
_bid_merged_size = backing(nameof(bid_merged_size), default(uint));
|
||||
_ask_direct_size = backing(nameof(ask_direct_size), default(uint));
|
||||
_ask_merged_size = backing(nameof(ask_merged_size), default(uint));
|
||||
//
|
||||
_working_orders = new ObservableFilterCollection<OrderRecordImmutable>(_orders, o => o.is_active());
|
||||
_working_bids = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Buy);
|
||||
_working_asks = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Sell);
|
||||
_working_best_bid = new CellProperty<double>(this, nameof(working_best_bid), format_price, double.NaN);
|
||||
_working_best_ask = new CellProperty<double>(this, nameof(working_best_ask), format_price, double.NaN);
|
||||
_working_bids_text = new CellProperty<string>(this, nameof(working_bids_text), string.Empty);
|
||||
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
|
||||
//
|
||||
default_command = new DelegateCommand(_show_ladder);
|
||||
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
|
||||
bid_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidMergedSize));
|
||||
bid_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidDirectSize));
|
||||
bid_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidImpliedSize));
|
||||
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids));
|
||||
ask_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Ask));
|
||||
ask_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskMergedSize));
|
||||
ask_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskDirectSize));
|
||||
ask_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskImpliedSize));
|
||||
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks));
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
|
||||
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
|
||||
raise_invalidated();
|
||||
}),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
|
||||
);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
#region Bound Properties
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public double last_price => _last_price.Value;
|
||||
public bool last_price_from_spread => _last_price_from_spread.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public double bid_price => _bid_price.Value;
|
||||
public uint bid_direct_size => _bid_direct_size.Value;
|
||||
public uint bid_implied_size => _bid_merged_size.Value - _bid_direct_size.Value;
|
||||
public uint bid_merged_size => _bid_merged_size.Value;
|
||||
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size.Value, bid_implied_size));
|
||||
public double ask_price => _ask_price.Value;
|
||||
public uint ask_direct_size => _ask_direct_size.Value;
|
||||
public uint ask_implied_size => _ask_merged_size.Value - _ask_direct_size.Value;
|
||||
public uint ask_merged_size => _ask_merged_size.Value;
|
||||
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size.Value, ask_implied_size));
|
||||
public ulong volume => _volume.value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double day_change => _day_change.Value;
|
||||
public double session_low => _session_low.Value;
|
||||
public double session_high => _session_high.Value;
|
||||
public int working_bid_volume => _working_bid_volume.value;
|
||||
public int working_ask_volume => _working_ask_volume.value;
|
||||
public string working_bids_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_bid, _order_summarizer.best_working_bid_qty, _order_summarizer.working_bid_qty, security.ContractSpec);
|
||||
public string working_asks_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_ask, _order_summarizer.best_working_ask_qty, _order_summarizer.working_ask_qty, security.ContractSpec);
|
||||
public double working_best_bid => _working_best_bid.value;
|
||||
public double working_best_ask => _working_best_ask.value;
|
||||
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
|
||||
public ICommand bid_price_click { get; }
|
||||
public ICommand bid_merged_size_click { get; }
|
||||
public ICommand bid_direct_size_click { get; }
|
||||
public ICommand bid_implied_size_click { get; }
|
||||
public ICommand working_bids_click { get; }
|
||||
public ICommand ask_price_click { get; }
|
||||
public ICommand ask_merged_size_click { get; }
|
||||
public ICommand ask_direct_size_click { get; }
|
||||
public ICommand ask_implied_size_click { get; }
|
||||
public ICommand working_asks_click { get; }
|
||||
public ICommand default_command { get; }
|
||||
public string market_description {get;}
|
||||
public string cleared_alias { get; }
|
||||
public string product_description { get; }
|
||||
public string hub_alias { get; }
|
||||
public string strip_name{ get; }
|
||||
public string mic_code { get; }
|
||||
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM {
|
||||
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AnimationManager _animation = new AnimationManager();
|
||||
readonly ObservableCollection<OrderRecordImmutable> _orders = new ObservableCollection<OrderRecordImmutable>();
|
||||
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<bool> _last_price_from_spread;
|
||||
readonly CellProperty<int> _position;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
readonly CellProperty<bool> _inside_bids, _inside_asks;
|
||||
readonly CellProperty<int> _working_bid_volume, _working_ask_volume;
|
||||
readonly CellProperty<double> _working_best_bid, _working_best_ask;
|
||||
readonly CellProperty<string> _working_bids_text, _working_asks_text;
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
double _raw_day_position;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_security_text = smd.security.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
string format_price(double price) => security.ContractSpec.format(price);
|
||||
_position = new CellProperty<int>(this, nameof(position), 0);
|
||||
_bid_price = backing(nameof(bid_price), double.NaN);
|
||||
_ask_price = backing(nameof(ask_price), double.NaN);
|
||||
_last_price = backing(nameof(last_price), double.NaN);
|
||||
_last_size = new CellProperty<uint>(this, nameof(last_size), 0);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
|
||||
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
|
||||
_inside_asks = new CellProperty<bool>(this, nameof(inside_asks));
|
||||
_working_bid_volume = new CellProperty<int>(this, nameof(working_bid_volume));
|
||||
_working_ask_volume = new CellProperty<int>(this, nameof(working_ask_volume));
|
||||
//
|
||||
_bid_direct_size = backing(nameof(bid_direct_size), default(uint));
|
||||
_bid_merged_size = backing(nameof(bid_merged_size), default(uint));
|
||||
_ask_direct_size = backing(nameof(ask_direct_size), default(uint));
|
||||
_ask_merged_size = backing(nameof(ask_merged_size), default(uint));
|
||||
//
|
||||
_working_orders = new ObservableFilterCollection<OrderRecordImmutable>(_orders, o => o.is_active());
|
||||
_working_bids = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Buy);
|
||||
_working_asks = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Sell);
|
||||
_working_best_bid = new CellProperty<double>(this, nameof(working_best_bid), format_price, double.NaN);
|
||||
_working_best_ask = new CellProperty<double>(this, nameof(working_best_ask), format_price, double.NaN);
|
||||
_working_bids_text = new CellProperty<string>(this, nameof(working_bids_text), string.Empty);
|
||||
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
|
||||
//
|
||||
default_command = new DelegateCommand(_show_ladder);
|
||||
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
|
||||
bid_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidMergedSize));
|
||||
bid_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidDirectSize));
|
||||
bid_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidImpliedSize));
|
||||
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids));
|
||||
ask_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Ask));
|
||||
ask_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskMergedSize));
|
||||
ask_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskDirectSize));
|
||||
ask_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskImpliedSize));
|
||||
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks));
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
|
||||
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
|
||||
raise_invalidated();
|
||||
}),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
|
||||
);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
#region Bound Properties
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public double last_price => _last_price.Value;
|
||||
public bool last_price_from_spread => _last_price_from_spread.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public double bid_price => _bid_price.Value;
|
||||
public uint bid_direct_size => _bid_direct_size.Value;
|
||||
public uint bid_implied_size => _bid_merged_size.Value - _bid_direct_size.Value;
|
||||
public uint bid_merged_size => _bid_merged_size.Value;
|
||||
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size.Value, bid_implied_size));
|
||||
public double ask_price => _ask_price.Value;
|
||||
public uint ask_direct_size => _ask_direct_size.Value;
|
||||
public uint ask_implied_size => _ask_merged_size.Value - _ask_direct_size.Value;
|
||||
public uint ask_merged_size => _ask_merged_size.Value;
|
||||
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size.Value, ask_implied_size));
|
||||
public ulong volume => _volume.value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double day_change => _day_change.Value;
|
||||
public double session_low => _session_low.Value;
|
||||
public double session_high => _session_high.Value;
|
||||
public int working_bid_volume => _working_bid_volume.value;
|
||||
public int working_ask_volume => _working_ask_volume.value;
|
||||
public string working_bids_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_bid, _order_summarizer.best_working_bid_qty, _order_summarizer.working_bid_qty, security.ContractSpec);
|
||||
public string working_asks_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_ask, _order_summarizer.best_working_ask_qty, _order_summarizer.working_ask_qty, security.ContractSpec);
|
||||
public double working_best_bid => _working_best_bid.value;
|
||||
public double working_best_ask => _working_best_ask.value;
|
||||
// ice props
|
||||
public string market_description => security.market_description();
|
||||
public string cleared_alias => security.cleared_alias();
|
||||
public string product_description => security.product_name();
|
||||
public string hub_alias => security.hub_alias();
|
||||
public string strip_name => security.strip_name();
|
||||
public string mic_code => security.mic_code();
|
||||
// animations
|
||||
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
#endregion
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public void refresh() {
|
||||
bool dirty = false;
|
||||
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
_bid_price.set(b.price);
|
||||
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
_ask_price.set(a.price);
|
||||
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
|
||||
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
|
||||
}
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_price_from_spread.set(from_spread);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
dirty |= _settlement.set_new(md.md.settlement_price);
|
||||
dirty |= _session_low.set_new(md.md.session_low);
|
||||
dirty |= _session_high.set_new(md.md.session_high);
|
||||
dirty |= _day_change.set_new(last_price - settlement);
|
||||
_volume.on_next_distinct(md.md.total_volume, ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
void refresh_size(ref NativePriceSize nps, Property<uint> direct, Property<uint> merged, ref string text, string text_prop) {
|
||||
if (nps.size != merged.Value || nps.direct_size != direct.Value) {
|
||||
merged.set(nps.size);
|
||||
direct.set(nps.direct_size);
|
||||
text = null;
|
||||
dirty = true;
|
||||
RaisePropertyChanged(text_prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(Row other) =>
|
||||
other != null &&
|
||||
other.security_key == security_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as Row);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
security_key.GetHashCode();
|
||||
|
||||
#region Show Windows
|
||||
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book(MarketViewView.Columns col) {
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.WorkingBids: _show_order_book(Side.Buy); break;
|
||||
case MarketViewView.Columns.WorkingAsks: _show_order_book(Side.Sell); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_order_book(Side side) {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
void _show_order_ticket(MarketViewView.Columns col) {
|
||||
var order = _orders_model.factory.order(security);
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.Bid:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.BidMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Ask:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.AskMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, ask_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Last:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.LastSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, last_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Callbacks
|
||||
void _on_orders(ITableUpdateTransform<OrderRecordImmutable, uint> orders) {
|
||||
foreach (var o in orders.removed)
|
||||
_order_summarizer.remove_order(o);
|
||||
foreach (var o in orders.added)
|
||||
_order_summarizer.add_order(o);
|
||||
bool dirty = false;
|
||||
_refresh_orders(ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
}
|
||||
|
||||
void _refresh_orders(ref bool dirty) {
|
||||
_position.on_next_distinct(_order_summarizer.position, ref dirty);
|
||||
_working_bid_volume.on_next_distinct(_order_summarizer.working_bid_qty, ref dirty);
|
||||
_working_ask_volume.on_next_distinct(_order_summarizer.working_ask_qty, ref dirty);
|
||||
_working_best_bid.on_next_distinct(_order_summarizer.best_working_bid, ref dirty);
|
||||
_working_best_ask.on_next_distinct(_order_summarizer.best_working_ask, ref dirty);
|
||||
_inside_bids.on_next_distinct(_order_summarizer.best_working_bid >= bid_price, ref dirty);
|
||||
_inside_asks.on_next_distinct(_order_summarizer.best_working_ask <= ask_price, ref dirty);
|
||||
|
||||
RaisePropertyChanged(
|
||||
// Implied might not be ever set, so it probably does not RaisePropertyChanged either
|
||||
// This might be part of the filter funk?
|
||||
//nameof(bid_implied_size),
|
||||
//nameof(ask_implied_size),
|
||||
nameof(working_bids_text),
|
||||
nameof(working_asks_text)
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
|
||||
class MyOrderSummarizer {
|
||||
SortedSetMap<double, OrderRecordImmutable> _bids, _asks;
|
||||
int _best_bid_working_qty, _best_ask_working_qty, _position, _working_bid_qty, _working_ask_qty;
|
||||
|
||||
public MyOrderSummarizer() {
|
||||
_bids = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.bid_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
_asks = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.ask_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
}
|
||||
|
||||
// bid
|
||||
public double best_working_bid => _bids.Count > 0 ? _bids.at_index(0).Key : double.NaN;
|
||||
public int best_working_bid_qty => _best_bid_working_qty;
|
||||
public int working_bid_qty => _working_bid_qty;
|
||||
// ask
|
||||
public double best_working_ask => _asks.Count > 0 ? _asks.at_index(0).Key : double.NaN;
|
||||
public int best_working_ask_qty => _best_ask_working_qty;
|
||||
public int working_ask_qty => _working_ask_qty;
|
||||
// pos
|
||||
public int position => _position;
|
||||
|
||||
public void add_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty += o.working_quantity();
|
||||
_bids.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty += o.working_quantity();
|
||||
_asks.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
public void remove_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty -= o.working_quantity();
|
||||
_bids.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty -= o.working_quantity();
|
||||
_asks.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
void _refresh() {
|
||||
_best_bid_working_qty = _bids.Count > 0 ? _bids.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
_best_ask_working_qty = _asks.Count > 0 ? _asks.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// commands
|
||||
public ICommand bid_price_click { get; }
|
||||
public ICommand bid_merged_size_click { get; }
|
||||
public ICommand bid_direct_size_click { get; }
|
||||
public ICommand bid_implied_size_click { get; }
|
||||
public ICommand working_bids_click { get; }
|
||||
public ICommand ask_price_click { get; }
|
||||
public ICommand ask_merged_size_click { get; }
|
||||
public ICommand ask_direct_size_click { get; }
|
||||
public ICommand ask_implied_size_click { get; }
|
||||
public ICommand working_asks_click { get; }
|
||||
public ICommand default_command { get; }
|
||||
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
#endregion
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public void refresh() {
|
||||
bool dirty = false;
|
||||
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
_bid_price.set(b.price);
|
||||
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
_ask_price.set(a.price);
|
||||
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
|
||||
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
|
||||
}
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_price_from_spread.set(from_spread);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
dirty |= _settlement.set_new(md.md.settlement_price);
|
||||
dirty |= _session_low.set_new(md.md.session_low);
|
||||
dirty |= _session_high.set_new(md.md.session_high);
|
||||
dirty |= _day_change.set_new(last_price - settlement);
|
||||
_volume.on_next_distinct(md.md.total_volume, ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
void refresh_size(ref NativePriceSize nps, Property<uint> direct, Property<uint> merged, ref string text, string text_prop) {
|
||||
if (nps.size != merged.Value || nps.direct_size != direct.Value) {
|
||||
merged.set(nps.size);
|
||||
direct.set(nps.direct_size);
|
||||
text = null;
|
||||
dirty = true;
|
||||
RaisePropertyChanged(text_prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(Row other) =>
|
||||
other != null &&
|
||||
other.security_key == security_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as Row);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
security_key.GetHashCode();
|
||||
|
||||
#region Show Windows
|
||||
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book(MarketViewView.Columns col) {
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.WorkingBids: _show_order_book(Side.Buy); break;
|
||||
case MarketViewView.Columns.WorkingAsks: _show_order_book(Side.Sell); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_order_book(Side side) {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
void _show_order_ticket(MarketViewView.Columns col) {
|
||||
var order = _orders_model.factory.order(security);
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.Bid:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.BidMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Ask:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.AskMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, ask_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Last:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.LastSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, last_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Callbacks
|
||||
void _on_orders(ITableUpdateTransform<OrderRecordImmutable, uint> orders) {
|
||||
foreach (var o in orders.removed)
|
||||
_order_summarizer.remove_order(o);
|
||||
foreach (var o in orders.added)
|
||||
_order_summarizer.add_order(o);
|
||||
bool dirty = false;
|
||||
_refresh_orders(ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
}
|
||||
|
||||
void _refresh_orders(ref bool dirty) {
|
||||
_position.on_next_distinct(_order_summarizer.position, ref dirty);
|
||||
_working_bid_volume.on_next_distinct(_order_summarizer.working_bid_qty, ref dirty);
|
||||
_working_ask_volume.on_next_distinct(_order_summarizer.working_ask_qty, ref dirty);
|
||||
_working_best_bid.on_next_distinct(_order_summarizer.best_working_bid, ref dirty);
|
||||
_working_best_ask.on_next_distinct(_order_summarizer.best_working_ask, ref dirty);
|
||||
_inside_bids.on_next_distinct(_order_summarizer.best_working_bid >= bid_price, ref dirty);
|
||||
_inside_asks.on_next_distinct(_order_summarizer.best_working_ask <= ask_price, ref dirty);
|
||||
|
||||
RaisePropertyChanged(
|
||||
// Implied might not be ever set, so it probably does not RaisePropertyChanged either
|
||||
// This might be part of the filter funk?
|
||||
//nameof(bid_implied_size),
|
||||
//nameof(ask_implied_size),
|
||||
nameof(working_bids_text),
|
||||
nameof(working_asks_text)
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
|
||||
class MyOrderSummarizer {
|
||||
SortedSetMap<double, OrderRecordImmutable> _bids, _asks;
|
||||
int _best_bid_working_qty, _best_ask_working_qty, _position, _working_bid_qty, _working_ask_qty;
|
||||
|
||||
public MyOrderSummarizer() {
|
||||
_bids = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.bid_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
_asks = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.ask_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
}
|
||||
|
||||
// bid
|
||||
public double best_working_bid => _bids.Count > 0 ? _bids.at_index(0).Key : double.NaN;
|
||||
public int best_working_bid_qty => _best_bid_working_qty;
|
||||
public int working_bid_qty => _working_bid_qty;
|
||||
// ask
|
||||
public double best_working_ask => _asks.Count > 0 ? _asks.at_index(0).Key : double.NaN;
|
||||
public int best_working_ask_qty => _best_ask_working_qty;
|
||||
public int working_ask_qty => _working_ask_qty;
|
||||
// pos
|
||||
public int position => _position;
|
||||
|
||||
public void add_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty += o.working_quantity();
|
||||
_bids.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty += o.working_quantity();
|
||||
_asks.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
public void remove_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty -= o.working_quantity();
|
||||
_bids.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty -= o.working_quantity();
|
||||
_asks.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
void _refresh() {
|
||||
_best_bid_working_qty = _bids.Count > 0 ? _bids.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
_best_ask_working_qty = _asks.Count > 0 ? _asks.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,286 +1,294 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
|
||||
|
||||
// securities_connectable should NOT be connected already.
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
_settings_manager = settings_manager;
|
||||
// Columns
|
||||
const string merged_size_col_header = "Qty", direct_size_col_header = "Direct", implied_size_col_header = "Implied", working_col_header = "Working";
|
||||
const double working_orders_col_width = 105;
|
||||
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
|
||||
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.security.cleared_alias(), nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ProductDescription, "Product Description", r => r.security.product_name(), nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
Width = width,
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
|
||||
font_color_override = font_color,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
BindingBase price_binding(string prop) {
|
||||
var mb = new MultiBinding { Converter = price_converter };
|
||||
mb.Bindings.Add(new Binding(prop) { Mode = BindingMode.OneWay });
|
||||
mb.Bindings.Add(new Binding($"{nameof(Row.security)}.{nameof(SecurityImmutable.ContractSpec)}") { Mode = BindingMode.OneWay });
|
||||
return mb;
|
||||
}
|
||||
//
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
if (s.grid != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
if (s.filters is FilterStates filters) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
|
||||
}
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
|
||||
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public static IComparer<Row> get_column_comparer(Columns column) {
|
||||
switch (column) {
|
||||
case Columns.Security: return wrap(r => r.security);
|
||||
case Columns.SecurityKey: return wrap(r => r.security.Id);
|
||||
case Columns.Volume: return wrap(r => r.volume);
|
||||
case Columns.Last: return wrap(r => r.last_price);
|
||||
case Columns.LastSize: return wrap(r => r.last_size);
|
||||
case Columns.Position: return wrap(r => r.position);
|
||||
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
|
||||
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
|
||||
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
|
||||
case Columns.BidMergedSize: return wrap(r => r.bid_merged_size);
|
||||
case Columns.Bid: return wrap(r => r.bid_price);
|
||||
case Columns.Ask: return wrap(r => r.ask_price);
|
||||
case Columns.AskMergedSize: return wrap(r => r.ask_merged_size);
|
||||
case Columns.AskDirectSize: return wrap(r => r.ask_direct_size);
|
||||
case Columns.AskImpliedSize: return wrap(r => r.ask_implied_size);
|
||||
case Columns.WorkingAsks: return wrap(r => r.working_ask_volume);
|
||||
case Columns.Settlement: return wrap(r => r.settlement);
|
||||
case Columns.DayChange: return wrap(r => r.day_change);
|
||||
case Columns.SessionLow: return wrap(r => r.session_low);
|
||||
case Columns.SessionHigh: return wrap(r => r.session_high);
|
||||
case Columns.MarketDescription: return wrap(r => r.market_description);
|
||||
case Columns.ClearedAlias: return wrap(r => r.security.cleared_alias());
|
||||
case Columns.ProductDescription: return wrap(r => r.security.product_name());
|
||||
case Columns.HubAlias: return wrap(r => r.security.hub_alias());
|
||||
case Columns.StripName: return wrap(r => r.security.strip_name());
|
||||
case Columns.MicCode: return wrap(r => r.security.mic_code());
|
||||
default: return null;
|
||||
}
|
||||
IComparer<Row> wrap<T>(Func<Row, T> selector) =>
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
|
||||
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_merged_size = bid_merged_size_filter.get_state(),
|
||||
bid_direct_size = bid_direct_size_filter.get_state(),
|
||||
bid_implied_size = bid_implied_size_filter.get_state(),
|
||||
ask_price = ask_price_filter.get_state(),
|
||||
ask_merged_size = ask_merged_size_filter.get_state(),
|
||||
ask_direct_size = ask_direct_size_filter.get_state(),
|
||||
ask_implied_size = ask_implied_size_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
last_price_from_spread = last_price_from_spread_filter.get_state(),
|
||||
last_size = last_size_filter.get_state(),
|
||||
day_change = day_change_filter.get_state(),
|
||||
settlement = settlement_filter.get_state(),
|
||||
session_low = session_low_filter.get_state(),
|
||||
session_high = session_high_filter.get_state(),
|
||||
position = position_filter.get_state(),
|
||||
raw_day_position = raw_day_position_filter.get_state(),
|
||||
working_bid_volume = working_bid_volume_filter.get_state(),
|
||||
working_ask_volume = working_ask_volume_filter.get_state(),
|
||||
volume = volume_filter.get_state(),
|
||||
security_key = security_key_filter.get_state(),
|
||||
market_description = market_description_filter.get_state(),
|
||||
cleared_alias = cleared_alias_filter.get_state(),
|
||||
product_description = product_description_filter.get_state(),
|
||||
hub_alias = hub_alias_filter.get_state(),
|
||||
strip_name = strip_name_filter.get_state(),
|
||||
mic_code = mic_code_filter.get_state(),
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
|
||||
|
||||
void _refresh() {
|
||||
foreach (var r in _row_vms) r.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
|
||||
static IReadOnlyList<Func<MarketViewVM, IDataGridColumnFilter<Row>>> _filter_getters =
|
||||
ObjectSnapshot.property_delegates_of_type<MarketViewVM, IDataGridColumnFilter<Row>>();
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
|
||||
|
||||
// securities_connectable should NOT be connected already.
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
_settings_manager = settings_manager;
|
||||
// Columns
|
||||
const string merged_size_col_header = "Qty", direct_size_col_header = "Direct", implied_size_col_header = "Implied", working_col_header = "Working";
|
||||
const double working_orders_col_width = 105;
|
||||
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
|
||||
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.security.cleared_alias(), nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ProductDescription, "Product Description", r => r.security.product_name(), nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
Width = width,
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
|
||||
font_color_override = font_color,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
BindingBase price_binding(string prop) {
|
||||
var mb = new MultiBinding { Converter = price_converter };
|
||||
mb.Bindings.Add(new Binding(prop) { Mode = BindingMode.OneWay });
|
||||
mb.Bindings.Add(new Binding($"{nameof(Row.security)}.{nameof(SecurityImmutable.ContractSpec)}") { Mode = BindingMode.OneWay });
|
||||
return mb;
|
||||
}
|
||||
//
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
if (s.grid != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
if (s.filters is FilterStates filters) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
market_description_filter.load_state(filters.market_description);
|
||||
cleared_alias_filter.load_state(filters.cleared_alias);
|
||||
product_description_filter.load_state(filters.product_description);
|
||||
hub_alias_filter.load_state(filters.hub_alias);
|
||||
strip_name_filter.load_state(filters.strip_name);
|
||||
mic_code_filter.load_state(filters.mic_code);
|
||||
}
|
||||
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
|
||||
}
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, _filters),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public static IComparer<Row> get_column_comparer(Columns column) {
|
||||
switch (column) {
|
||||
case Columns.Security: return wrap(r => r.security);
|
||||
case Columns.SecurityKey: return wrap(r => r.security.Id);
|
||||
case Columns.Volume: return wrap(r => r.volume);
|
||||
case Columns.Last: return wrap(r => r.last_price);
|
||||
case Columns.LastSize: return wrap(r => r.last_size);
|
||||
case Columns.Position: return wrap(r => r.position);
|
||||
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
|
||||
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
|
||||
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
|
||||
case Columns.BidMergedSize: return wrap(r => r.bid_merged_size);
|
||||
case Columns.Bid: return wrap(r => r.bid_price);
|
||||
case Columns.Ask: return wrap(r => r.ask_price);
|
||||
case Columns.AskMergedSize: return wrap(r => r.ask_merged_size);
|
||||
case Columns.AskDirectSize: return wrap(r => r.ask_direct_size);
|
||||
case Columns.AskImpliedSize: return wrap(r => r.ask_implied_size);
|
||||
case Columns.WorkingAsks: return wrap(r => r.working_ask_volume);
|
||||
case Columns.Settlement: return wrap(r => r.settlement);
|
||||
case Columns.DayChange: return wrap(r => r.day_change);
|
||||
case Columns.SessionLow: return wrap(r => r.session_low);
|
||||
case Columns.SessionHigh: return wrap(r => r.session_high);
|
||||
case Columns.MarketDescription: return wrap(r => r.market_description);
|
||||
case Columns.ClearedAlias: return wrap(r => r.security.cleared_alias());
|
||||
case Columns.ProductDescription: return wrap(r => r.security.product_name());
|
||||
case Columns.HubAlias: return wrap(r => r.security.hub_alias());
|
||||
case Columns.StripName: return wrap(r => r.security.strip_name());
|
||||
case Columns.MicCode: return wrap(r => r.security.mic_code());
|
||||
default: return null;
|
||||
}
|
||||
IComparer<Row> wrap<T>(Func<Row, T> selector) =>
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.market_description, nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.cleared_alias, nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.product_description, nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.hub_alias, nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.strip_name, nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.mic_code, nameof(Row.mic_code));
|
||||
|
||||
IEnumerable<IDataGridColumnFilter<Row>> _filters =>
|
||||
_filter_getters.Select(fn => fn(this));
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_merged_size = bid_merged_size_filter.get_state(),
|
||||
bid_direct_size = bid_direct_size_filter.get_state(),
|
||||
bid_implied_size = bid_implied_size_filter.get_state(),
|
||||
ask_price = ask_price_filter.get_state(),
|
||||
ask_merged_size = ask_merged_size_filter.get_state(),
|
||||
ask_direct_size = ask_direct_size_filter.get_state(),
|
||||
ask_implied_size = ask_implied_size_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
last_price_from_spread = last_price_from_spread_filter.get_state(),
|
||||
last_size = last_size_filter.get_state(),
|
||||
day_change = day_change_filter.get_state(),
|
||||
settlement = settlement_filter.get_state(),
|
||||
session_low = session_low_filter.get_state(),
|
||||
session_high = session_high_filter.get_state(),
|
||||
position = position_filter.get_state(),
|
||||
raw_day_position = raw_day_position_filter.get_state(),
|
||||
working_bid_volume = working_bid_volume_filter.get_state(),
|
||||
working_ask_volume = working_ask_volume_filter.get_state(),
|
||||
volume = volume_filter.get_state(),
|
||||
security_key = security_key_filter.get_state(),
|
||||
market_description = market_description_filter.get_state(),
|
||||
cleared_alias = cleared_alias_filter.get_state(),
|
||||
product_description = product_description_filter.get_state(),
|
||||
hub_alias = hub_alias_filter.get_state(),
|
||||
strip_name = strip_name_filter.get_state(),
|
||||
mic_code = mic_code_filter.get_state(),
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
|
||||
|
||||
void _refresh() {
|
||||
foreach (var r in _row_vms) r.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -549,24 +549,48 @@
|
||||
</u:BindingUpdateGroupCollection>
|
||||
</u:Behavior.BindingUpdateGroups>
|
||||
</TextBox>
|
||||
<TextBlock
|
||||
<StackPanel
|
||||
x:Name="PART_TickFactors_Label"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Ticks"
|
||||
IsEnabled="True"
|
||||
Orientation="Horizontal"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<c:TickFactorsPicker
|
||||
>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Ticks ("
|
||||
/>
|
||||
<CheckBox
|
||||
Margin="1,0,0,0"
|
||||
IsChecked="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Padding="0"
|
||||
>
|
||||
fractional
|
||||
</CheckBox>
|
||||
<TextBlock VerticalAlignment="Center" Margin="1,0,0,0">)</TextBlock>
|
||||
</StackPanel>
|
||||
<Grid
|
||||
x:Name="PART_TickFactors_Picker"
|
||||
Grid.Column="3"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="True"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
>
|
||||
<c:TickFactorsPicker
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static cv:BooleanToVisibilityConverter.HiddenInstance}}"
|
||||
/>
|
||||
<c:TickUpDownInt
|
||||
HorizontalAlignment="Stretch"
|
||||
Minimum="1"
|
||||
Value="{Binding IntegerTickSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource FalseVisibilityConverter}}"
|
||||
/>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
x:Name="PART_Pricing_Label"
|
||||
Grid.Column="4"
|
||||
|
||||
@@ -52,6 +52,26 @@ namespace mbts.ui.spark {
|
||||
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region UseFractionalTicks
|
||||
public static readonly DependencyProperty UseFractionalTicksProperty = WPFHelper.create_dp<bool, ExecutionStrategyEditor>(
|
||||
nameof(UseFractionalTicks), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, true);
|
||||
|
||||
public bool UseFractionalTicks {
|
||||
get => (bool)GetValue(UseFractionalTicksProperty);
|
||||
set => SetValue(UseFractionalTicksProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IntegerTickSize
|
||||
public static readonly DependencyProperty IntegerTickSizeProperty = WPFHelper.create_dp<int, ExecutionStrategyEditor>(
|
||||
nameof(IntegerTickSize), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 1);
|
||||
|
||||
public int IntegerTickSize {
|
||||
get => (int)GetValue(IntegerTickSizeProperty);
|
||||
set => SetValue(IntegerTickSizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IsNew
|
||||
public static readonly DependencyProperty IsNewProperty =
|
||||
DependencyProperty.Register(
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
UseFractionalTicks="{Binding use_fractional_ticks}"
|
||||
IntegerTickSize="{Binding integer_tick_size}"
|
||||
/>
|
||||
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
|
||||
</Grid>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -30,8 +29,16 @@ namespace mbts.spark {
|
||||
readonly Property<bool> _has_working_orders, _can_add_leg;
|
||||
|
||||
Security _synthetic;
|
||||
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
|
||||
bool
|
||||
_set_name_automatically,
|
||||
_set_ticks_automatically,
|
||||
_set_quantities_automatically,
|
||||
_set_price_mults_automatically,
|
||||
_is_setting_quantities,
|
||||
_is_setting_price_mults,
|
||||
_use_fractional_ticks = true;
|
||||
TickFactors _tick_factors;
|
||||
int _integer_tick_size = 1;
|
||||
ExecutionStrategyImmutable _strategy;
|
||||
HedgePricing _hedge_pricing;
|
||||
|
||||
@@ -44,6 +51,10 @@ namespace mbts.spark {
|
||||
: this(synthetic, strategy, false) {
|
||||
ExceptionAssert.InvalidOperation.That(synthetic.Id != 0, "This constructor may only be used for already-existing synthetic securities (id must be non-zero).");
|
||||
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
|
||||
if (TickUtils.is_integer_tick(synthetic.ContractSpec)) {
|
||||
_use_fractional_ticks = false;
|
||||
_integer_tick_size = DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0;
|
||||
}
|
||||
title = $"Edit '{synthetic.Synthetic.Name}'";
|
||||
icon = Icons.ModifySynthetic;
|
||||
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
|
||||
@@ -131,14 +142,14 @@ namespace mbts.spark {
|
||||
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
if (_tick_factors == null || !_tick_factors.is_valid()) {
|
||||
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
|
||||
if (use_fractional_ticks ? (_tick_factors == null || !_tick_factors.is_valid()) : integer_tick_size == 0) {
|
||||
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, use_fractional_ticks ? nameof(tick_factors) : nameof(integer_tick_size)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
if (valid) {
|
||||
_synthetic.Synthetic.Legs.SetItems(leg_tuples.Select(l => l.leg));
|
||||
_synthetic.Symbol = _deduce_symbol(_synthetic);
|
||||
_synthetic.ContractSpec = _tick_factors.to_spec();
|
||||
_synthetic.ContractSpec = use_fractional_ticks ? _tick_factors.to_spec() : TickUtils.create_integer_tick_spec(integer_tick_size);
|
||||
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
@@ -295,6 +306,20 @@ namespace mbts.spark {
|
||||
public bool has_working_orders => _has_working_orders.Value;
|
||||
public bool is_new { get; }
|
||||
public bool is_new_leg_popup_open { get; set; }
|
||||
public bool use_fractional_ticks {
|
||||
get => _use_fractional_ticks;
|
||||
set {
|
||||
if (Set(ref _use_fractional_ticks, value))
|
||||
_set_ticks_automatically = false;
|
||||
}
|
||||
}
|
||||
public int integer_tick_size {
|
||||
get => _integer_tick_size;
|
||||
set {
|
||||
if (Set(ref _integer_tick_size, value))
|
||||
_set_ticks_automatically = false;
|
||||
}
|
||||
}
|
||||
public bool can_add_leg => _can_add_leg.Value;
|
||||
public SyntheticContract.Types.PriceCalc price_calc { get => _synthetic.Synthetic.PriceCalc; set => _synthetic.Synthetic.PriceCalc = value; }
|
||||
public ObservableCollection<LegSpreaderParamsTuple> leg_tuples { get; } = new ObservableCollection<LegSpreaderParamsTuple>();
|
||||
@@ -323,6 +348,16 @@ namespace mbts.spark {
|
||||
_strategy = strategy;
|
||||
hedge_pricing = strategy.HedgePricing;
|
||||
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
|
||||
if (synthetic.HasContractSpec) {
|
||||
/// assign the fields <see cref="_use_fractional_ticks"/> and <see cref="_integer_tick_size"/>,
|
||||
/// rather than the properties <see cref="use_fractional_ticks"/> and <see cref="integer_tick_size"/>
|
||||
/// because the setters of the former flip <see cref="_set_ticks_automatically"/> to false if the value changes.
|
||||
/// This behavior causes the ticks to be automatically deduced as legs are added/removed,
|
||||
/// until the user explicitly enters a different value (as the properties are bound to tick input controls in the view).
|
||||
Set(ref _use_fractional_ticks, !TickUtils.is_integer_tick(synthetic.ContractSpec), nameof(use_fractional_ticks));
|
||||
if (!use_fractional_ticks)
|
||||
Set(ref _integer_tick_size, DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0, nameof(integer_tick_size));
|
||||
}
|
||||
RaisePropertyChanged(nameof(name), nameof(tick_factors));
|
||||
}
|
||||
|
||||
|
||||
@@ -787,7 +787,7 @@ namespace mbts.spark {
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (var p in dcs)
|
||||
foreach (var p in dcs.empty_if_null().OrderBy(kv => kv.Key))
|
||||
_vm.current_algo_custom_state.Add(new NamedValueVM(p.Key, DynamicOrderHelper.fmt_param(p.Value)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<RuntimeIdentifiers>win7-x64</RuntimeIdentifiers>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
|
||||
<LanguageTargets Condition="'$(VisualStudioVersion)'=='16.0'">$(MSBuildToolsPath)\Microsoft.CSharp.targets</LanguageTargets>
|
||||
<ApplicationIcon>logo.ico</ApplicationIcon>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<StartupObject>mbts.ui.shell.App</StartupObject>
|
||||
|
||||
@@ -63,5 +63,20 @@ namespace mbts {
|
||||
|
||||
public static double? nan_nullable(double value) =>
|
||||
double.IsNaN(value) ? (double?)null : value;
|
||||
|
||||
public static int? to_optional_int32(double value) {
|
||||
if (double.IsNaN(value) || double.IsInfinity(value))
|
||||
return null;
|
||||
if (value > 0) {
|
||||
value = Math.Floor(value);
|
||||
if (value <= int.MaxValue)
|
||||
return (int)value;
|
||||
} else if (value < 0) {
|
||||
value = Math.Ceiling(value);
|
||||
if (value >= int.MinValue)
|
||||
return (int)value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,43 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
static class RegexFilterUtils {
|
||||
public static Regex build_regex(string str) {
|
||||
if (str == null)
|
||||
return null;
|
||||
str = str.Trim();
|
||||
if (str == string.Empty)
|
||||
return null;
|
||||
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
}
|
||||
}
|
||||
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
|
||||
string _text = string.Empty;
|
||||
Regex _regex;
|
||||
|
||||
public RegexFilterVM(Func<TRow, string> get_row_val, string prop_name = null) : base(get_row_val, prop_name) { }
|
||||
|
||||
public string text {
|
||||
get => _text;
|
||||
set => _regex = RegexFilterUtils.build_regex(_text = (value ?? string.Empty));
|
||||
}
|
||||
|
||||
public override string get_state() => _text ?? string.Empty;
|
||||
|
||||
protected override bool _get_active() => _regex != null;
|
||||
protected override bool _filter(string value) =>
|
||||
_regex != null && (
|
||||
_regex.IsMatch(value) ||
|
||||
(!string.IsNullOrEmpty(value) && _regex.IsMatch(value.Replace(" ", ""))));
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
static class RegexFilterUtils {
|
||||
public static Regex build_regex(string str) {
|
||||
if (str == null)
|
||||
return null;
|
||||
str = str.Trim();
|
||||
if (str == string.Empty)
|
||||
return null;
|
||||
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
}
|
||||
}
|
||||
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
|
||||
string _text = string.Empty;
|
||||
Regex _regex;
|
||||
|
||||
public RegexFilterVM(Func<TRow, string> get_row_val, string prop_name = null) : base(get_row_val, prop_name) { }
|
||||
|
||||
public string text {
|
||||
get => _text;
|
||||
set => _regex = RegexFilterUtils.build_regex(_text = (value ?? string.Empty));
|
||||
}
|
||||
|
||||
public override string get_state() => _text ?? string.Empty;
|
||||
|
||||
protected override bool _get_active() => _regex != null;
|
||||
protected override bool _filter(string value) => _regex != null && _regex.IsMatch(value);
|
||||
protected override void _load_state_internal(string state) => text = state;
|
||||
protected override void _clear() {
|
||||
_close();
|
||||
_text = string.Empty;
|
||||
_regex = null;
|
||||
RaisePropertyChanged(nameof(text));
|
||||
_raise_filter_changed();
|
||||
}
|
||||
}
|
||||
protected override void _load_state_internal(string state) => text = state;
|
||||
protected override void _clear() {
|
||||
_close();
|
||||
_text = string.Empty;
|
||||
_regex = null;
|
||||
RaisePropertyChanged(nameof(text));
|
||||
_raise_filter_changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1005,6 +1005,8 @@
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
UseFractionalTicks="{Binding use_fractional_ticks}"
|
||||
IntegerTickSize="{Binding integer_tick_size}"
|
||||
/>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="-4" VerticalAlignment="Top" RightItems="{Binding skirt_items}"/>
|
||||
</Grid>
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace mbts.ui.converters
|
||||
public Visibility VisibilityForFalse { get { return _visfalse; } set { _visfalse = value; } }
|
||||
|
||||
public static BooleanToVisibilityConverter Instance => new BooleanToVisibilityConverter();
|
||||
public static BooleanToVisibilityConverter HiddenInstance => new BooleanToVisibilityConverter {
|
||||
VisibilityForFalse = Visibility.Hidden
|
||||
};
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
|
||||
bool flag = false;
|
||||
|
||||
@@ -49,6 +49,28 @@ namespace mbts.spark {
|
||||
public static double parse(this ContractSpecificationImmutable? spec, string text, double display_multiplier, string nan_text) =>
|
||||
spec.spec_or_default().parse(text, display_multiplier, nan_text);
|
||||
|
||||
public static bool is_integer_tick(ContractSpecificationImmutable spec) =>
|
||||
spec.MainFraction == 0 &&
|
||||
spec.PriceDisplayFormat == 0 &&
|
||||
spec.LowTick > 0 &&
|
||||
MathUtils.is_int(spec.LowTick) &&
|
||||
spec.HighTick > 0 &&
|
||||
MathUtils.is_int(spec.HighTick);
|
||||
|
||||
public static ContractSpecification create_integer_tick_spec(int low_tick) =>
|
||||
create_integer_tick_spec(low_tick, low_tick);
|
||||
|
||||
public static ContractSpecification create_integer_tick_spec(int low_tick, int high_tick) {
|
||||
var rv = SecurityDataHelper.Defaults.ContractSpecification;
|
||||
rv.MainFraction = 0;
|
||||
rv.SubFraction = 0;
|
||||
rv.PriceDisplayFormat = 0;
|
||||
rv.PriceBreak = 0;
|
||||
rv.LowTick = low_tick;
|
||||
rv.HighTick = high_tick;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static double low_tick(NativePriceSize[] bids, NativePriceSize[] asks) {
|
||||
double low = bids[bids.Length - 1].price;
|
||||
if (double.IsNaN(low))
|
||||
@@ -326,7 +348,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
static bool _check_tick_size(double tick_size) =>
|
||||
(tick_size > 0 && tick_size <= 0.5) || tick_size == 1;
|
||||
tick_size > 0 && (tick_size <= 0.5 || MathUtils.is_int(tick_size));
|
||||
|
||||
struct SpecKey : IEquatable<SpecKey> {
|
||||
public readonly uint denominator, display_digits;
|
||||
|
||||
@@ -39,13 +39,18 @@ namespace bts.api {
|
||||
|
||||
static string _format(SecurityImmutable? security) {
|
||||
switch (security?.ContractCase) {
|
||||
case ContractOneofCase.Future: return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
|
||||
case ContractOneofCase.Future:
|
||||
if(security?.HasIce == true)
|
||||
return _format_ICE(security);
|
||||
return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
|
||||
case ContractOneofCase.Spread:
|
||||
if (security?.HasGlbx == true) {
|
||||
// In case we ever bring it back, pretty printed spread names can be found here:
|
||||
// GlbxSecurityFormatter.FormatSpread(security, out string result)
|
||||
return security?.Glbx.SecurityDesc;
|
||||
}
|
||||
else if(security?.HasIce == true)
|
||||
return _format_ICE(security);
|
||||
return string.Join("", _format_future_legs(SecurityHelper.get_flat_future_legs(security?.Spread.Legs)));
|
||||
case ContractOneofCase.Synthetic: return security?.Synthetic.Name;
|
||||
default: return "Unknown-security";
|
||||
@@ -79,5 +84,28 @@ 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<server name="default">
|
||||
<ip>63.247.113.163</ip>
|
||||
<port>3000</port>
|
||||
<userName>bluetrd-ps</userName>
|
||||
<userName>katas2f-ps</userName>
|
||||
<password>Starts123</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
@@ -24,8 +24,8 @@
|
||||
<server name="endex">
|
||||
<ip>63.247.113.77</ip>
|
||||
<port>3000</port>
|
||||
<userName>Value from ICE Support</userName>
|
||||
<password>Value from ICE Support</password>
|
||||
<userName>katas2f-ps</userName>
|
||||
<password>Starts123</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
<localNetworkInterface></localNetworkInterface>
|
||||
|
||||
@@ -105,14 +105,35 @@ message BasePositionBatch {
|
||||
google.protobuf.Timestamp current_as_of = 10; // UTC
|
||||
}
|
||||
|
||||
// security overrides /////////////////////////////////////
|
||||
message CppOverride{
|
||||
double value = 1;
|
||||
}
|
||||
|
||||
message SecurityParameters{
|
||||
uint32 security_key = 1;
|
||||
CppOverride cpp_override = 2;
|
||||
// other overrides
|
||||
}
|
||||
|
||||
message SecurityParametersBatchOp{
|
||||
repeated SecurityParameters op_params = 1;
|
||||
}
|
||||
|
||||
message SecurityParametersBatchOpResponse {
|
||||
bool ok = 1;
|
||||
string msg = 2;
|
||||
}
|
||||
|
||||
service PositionService {
|
||||
option (bts.api.service_description) = "Provides Position and PNL, per security, per account";
|
||||
|
||||
rpc create_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
|
||||
rpc update_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
|
||||
rpc remove_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
|
||||
rpc set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
|
||||
|
||||
rpc set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
|
||||
rpc update_security_parameters (SecurityParametersBatchOp) returns (SecurityParametersBatchOpResponse);
|
||||
|
||||
// subscription
|
||||
rpc manual_fills(bts.api.VoidMessage) returns (stream ManualFill);
|
||||
rpc position_data(bts.api.VoidMessage) returns (stream PositionUnit);
|
||||
|
||||
@@ -1,136 +1,130 @@
|
||||
#
|
||||
# Common Market data options
|
||||
#
|
||||
|
||||
MarketDataConfig {
|
||||
rule: "DeploymentType.type == 'PROD'"
|
||||
value {
|
||||
use_sim: false
|
||||
}
|
||||
}
|
||||
|
||||
PublisherOptions {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
disable_heartbeat: true
|
||||
}
|
||||
}
|
||||
|
||||
PlatformMap {
|
||||
value {
|
||||
glbx_platform: "PLATFORM_MDP3ONIXS"
|
||||
ice_platform: "PLATFORM_ICEONIXS"
|
||||
}
|
||||
}
|
||||
|
||||
PublisherNamespace {
|
||||
value {
|
||||
shared_memory_namespace: @System.os_user
|
||||
}
|
||||
}
|
||||
|
||||
CMEOptions {
|
||||
value {
|
||||
feed_engine {
|
||||
name: "mdp3feed"
|
||||
# udp_socket_buffer_size: 425984
|
||||
# channels_per_thread: 1
|
||||
spin_time: 0
|
||||
}
|
||||
channel {
|
||||
id: "*"
|
||||
feed_engine: "mdp3feed"
|
||||
use_tcp_replay: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ICEPublisherOptions {
|
||||
value {
|
||||
network_interface: "eth1"
|
||||
}
|
||||
}
|
||||
|
||||
#to make this work in dev clone the operations repo
|
||||
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
|
||||
#modify the easyTunnelProxy.properties file to replace
|
||||
#tunnel.multicast.interface=10.96.18.37 with your local ip
|
||||
#run easyTunnelProxy.bat BRENT-FI for Brent Full implied
|
||||
#you should see ice data
|
||||
|
||||
ICEOptions {
|
||||
value {
|
||||
channel {
|
||||
name: "oil" #these names are arbitrary and are for debugging
|
||||
market_type: 3
|
||||
uds_market_type: 3
|
||||
market_type: 4 #ICE IPE Gas Oil Futures
|
||||
uds_market_type: 4
|
||||
market_type: 5 #ICE Futures Brent, IPE Brent Futures
|
||||
uds_market_type: 5
|
||||
market_type: 9 #ICE Futures WTI, ICE WTI Crude Futures
|
||||
uds_market_type: 9
|
||||
market_type: 13 #ICE Heating Oil Futures
|
||||
uds_market_type: 13
|
||||
market_type: 15 #ICE NYH (RBOB) Gasoline Futures
|
||||
uds_market_type: 15
|
||||
##market_type: 21 #ICE Futures US Agricultures, Coffee C
|
||||
##uds_market_type: 21
|
||||
##market_type: 23 #ICE Futures US Agricultures, FCOJ A
|
||||
##uds_market_type: 23
|
||||
|
||||
##market_type: 26 #ICE Futures US Financials DX
|
||||
##uds_market_type: 26
|
||||
|
||||
uds_market_type: 11 #ICE WTI-Brent spreads
|
||||
uds_market_type: 14 #ICE Heating Oil/WTI Futures Crack
|
||||
uds_market_type: 16 #ICE NYH (RBOB) Gasoline/WTI Futures Crack
|
||||
uds_market_type: 58 #ICE Futures UI Heating Oil / Brent
|
||||
uds_market_type: 59 #ICE NYH (RBOB) Gasoline/Brent Futures Crack
|
||||
uds_market_type: 66 #ICE NYH (RBOB) Gasoline/Heating Oil Spread
|
||||
uds_market_type: 70 #ICE Heating Oil/LS Gasoil Futures Spread
|
||||
uds_market_type: 71 #ICE LS Gasoil/Brent Futures Crack
|
||||
uds_market_type: 72 #ICE NYH (RBOB) Gasoline/LS Gasoil Futures Spread
|
||||
|
||||
#onixs_log_mode: Debug
|
||||
}
|
||||
# channel {
|
||||
# name: "juice"
|
||||
# market_type: 26 #ICE Futures US Agricultures, FCOJ A
|
||||
# }
|
||||
ice_config_file: "ConnectivityConfiguration.APITest.xml"
|
||||
#ice_config_file: "ConnectivityConfiguration.PerfTest.xml"
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
delay_seconds: 0
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
value {
|
||||
delay_seconds: 120
|
||||
standard {
|
||||
start: "16:30"
|
||||
end: "16:40"
|
||||
}
|
||||
sunday {
|
||||
start: "14:00"
|
||||
end: "14:10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayFactorOverride {
|
||||
value {
|
||||
override {
|
||||
symbol: "SOM"
|
||||
exchange: "GLBX"
|
||||
type: "SPREAD"
|
||||
price_multiplier: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
#
|
||||
# Common Market data options
|
||||
#
|
||||
|
||||
MarketDataConfig {
|
||||
rule: "DeploymentType.type == 'PROD'"
|
||||
value {
|
||||
use_sim: false
|
||||
}
|
||||
}
|
||||
|
||||
PublisherOptions {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
disable_heartbeat: true
|
||||
}
|
||||
}
|
||||
|
||||
PlatformMap {
|
||||
value {
|
||||
glbx_platform: "PLATFORM_MDP3ONIXS"
|
||||
ice_platform: "PLATFORM_ICEONIXS"
|
||||
}
|
||||
}
|
||||
|
||||
PublisherNamespace {
|
||||
value {
|
||||
shared_memory_namespace: @System.os_user
|
||||
}
|
||||
}
|
||||
|
||||
CMEOptions {
|
||||
value {
|
||||
feed_engine {
|
||||
name: "mdp3feed"
|
||||
# udp_socket_buffer_size: 425984
|
||||
# channels_per_thread: 1
|
||||
spin_time: 0
|
||||
}
|
||||
channel {
|
||||
id: "*"
|
||||
feed_engine: "mdp3feed"
|
||||
use_tcp_replay: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#to make this work in dev clone the operations repo
|
||||
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
|
||||
#modify the easyTunnelProxy.properties file to replace
|
||||
#tunnel.multicast.interface=10.96.18.37 with your local ip
|
||||
#run easyTunnelProxy.bat BRENT-FI for Brent Full implied
|
||||
#you should see ice data
|
||||
|
||||
ICEOptions {
|
||||
value {
|
||||
channel {
|
||||
name: "oil" #these names are arbitrary and are for debugging
|
||||
market_type: 3
|
||||
uds_market_type: 3
|
||||
market_type: 4 #ICE IPE Gas Oil Futures
|
||||
uds_market_type: 4
|
||||
market_type: 5 #ICE Futures Brent, IPE Brent Futures
|
||||
uds_market_type: 5
|
||||
market_type: 9 #ICE Futures WTI, ICE WTI Crude Futures
|
||||
uds_market_type: 9
|
||||
market_type: 13 #ICE Heating Oil Futures
|
||||
uds_market_type: 13
|
||||
market_type: 15 #ICE NYH (RBOB) Gasoline Futures
|
||||
uds_market_type: 15
|
||||
##market_type: 21 #ICE Futures US Agricultures, Coffee C
|
||||
##uds_market_type: 21
|
||||
##market_type: 23 #ICE Futures US Agricultures, FCOJ A
|
||||
##uds_market_type: 23
|
||||
|
||||
##market_type: 26 #ICE Futures US Financials DX
|
||||
##uds_market_type: 26
|
||||
|
||||
uds_market_type: 11 #ICE WTI-Brent spreads
|
||||
uds_market_type: 14 #ICE Heating Oil/WTI Futures Crack
|
||||
uds_market_type: 16 #ICE NYH (RBOB) Gasoline/WTI Futures Crack
|
||||
uds_market_type: 58 #ICE Futures UI Heating Oil / Brent
|
||||
uds_market_type: 59 #ICE NYH (RBOB) Gasoline/Brent Futures Crack
|
||||
uds_market_type: 66 #ICE NYH (RBOB) Gasoline/Heating Oil Spread
|
||||
uds_market_type: 70 #ICE Heating Oil/LS Gasoil Futures Spread
|
||||
uds_market_type: 71 #ICE LS Gasoil/Brent Futures Crack
|
||||
uds_market_type: 72 #ICE NYH (RBOB) Gasoline/LS Gasoil Futures Spread
|
||||
|
||||
#onixs_log_mode: Debug
|
||||
}
|
||||
# channel {
|
||||
# name: "juice"
|
||||
# market_type: 26 #ICE Futures US Agricultures, FCOJ A
|
||||
# }
|
||||
ice_config_file: "ConnectivityConfiguration.APITest.xml"
|
||||
#ice_config_file: "ConnectivityConfiguration.PerfTest.xml"
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
delay_seconds: 0
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
value {
|
||||
delay_seconds: 120
|
||||
standard {
|
||||
start: "16:30"
|
||||
end: "16:40"
|
||||
}
|
||||
sunday {
|
||||
start: "14:00"
|
||||
end: "14:10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayFactorOverride {
|
||||
value {
|
||||
override {
|
||||
symbol: "SOM"
|
||||
exchange: "GLBX"
|
||||
type: "SPREAD"
|
||||
price_multiplier: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,10 @@ message EnsureOnScreenWithWorkAreaRect {
|
||||
optional bool enabled = 1 [default = false];
|
||||
}
|
||||
|
||||
message CustomFormatIceSecurities{
|
||||
optional bool enabled = 1 [default = true];
|
||||
}
|
||||
|
||||
message IgnoreNansInDayPnL {
|
||||
optional bool enabled = 1 [default = true];
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
|
||||
me->sec = serv.sec_db.resolve(SecurityKey(def.security_key()));
|
||||
me->previous_working_position = Quantity(0);
|
||||
|
||||
auto const &native_state = get_native_state(me->state);
|
||||
auto &native_state = get_native_state(me->state);
|
||||
switch (me->state.status()) {
|
||||
|
||||
case ORDER_STATUS_WORKING:
|
||||
@@ -202,6 +202,8 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
|
||||
}
|
||||
// if we made it this far we are good.
|
||||
me->routing_record = rq.routing_record;
|
||||
*native_state.mutable_routing_record() = rq.routing_record;
|
||||
|
||||
if (me->state.status() != ORDER_STATUS_PENDING_WORKING)
|
||||
me->exchange_order_id = native_state.exchange_order_id();
|
||||
init_fix();
|
||||
|
||||
@@ -215,13 +215,6 @@ api::orderman::OrderDefinition
|
||||
ICENativeOrder::on_init_from_exe_report(const ExecutionReport& er, const PreActivationOrderServices& os)
|
||||
{
|
||||
auto k = er.order_key;
|
||||
auto nextid = me->services->gen_id();
|
||||
if(k.value() < nextid)
|
||||
{
|
||||
k = OrderKey(nextid);
|
||||
LOG(OrderMan, Error, "Updating recovered order id with newly generated id {}", k.value());
|
||||
}
|
||||
|
||||
OrderRecord orecord;
|
||||
orecord.set_key(k.value());
|
||||
orecord.set_parent_key(null_order_key().value());
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import sys
|
||||
f = open(sys.argv[1], 'rb+')
|
||||
license = '5E50A1DACD94D7C1BECD316EEAE5AB02'
|
||||
license = '1C9210495AA3C9C884B7A37A4C7AD8F9'
|
||||
data = f.read()
|
||||
match = data.find(license)
|
||||
if match > 0:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<CSharpAPIRoot>$(MSBuildThisFileDirectory)</CSharpAPIRoot>
|
||||
<Configuration Condition="'$(configuration)'==''">Debug</Configuration>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Output">
|
||||
<BaseOutputPath>$(CSharpAPIRoot)\build\$(Configuration)</BaseOutputPath>
|
||||
<OutputPath>$(BaseOutputPath)\bin</OutputPath>
|
||||
<BaseIntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<IntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</IntermediateOutputPath>
|
||||
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30104.148
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "bts.api.csharp", "src/bts.api.csharp.csproj", "{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2B9FECC6-16F4-4B85-92BF-62AAA2B7652D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FC6CE0CA-B0CB-44E3-8176-1D759D05AAD4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="spark_sdk" Version="2.15.10" />
|
||||
<PackageReference Include="System.Reactive" Version="4.4.1" />
|
||||
<PackageReference Include="System.Reactive.Core" Version="4.4.1" />
|
||||
<PackageReference Include="System.Reactive.Linq" Version="4.4.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace bts.api.csharp {
|
||||
readonly struct NameIDPair {
|
||||
public NameIDPair(string name, uint id) {
|
||||
this.name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public string name { get; }
|
||||
public uint id { get; }
|
||||
}
|
||||
|
||||
static class Users {
|
||||
public static NameIDPair Default =>
|
||||
new NameIDPair("bts", 1);
|
||||
}
|
||||
|
||||
static class Accounts {
|
||||
public static NameIDPair Default =>
|
||||
new NameIDPair("TEST", 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using bts.api.core;
|
||||
using bts.api.csharp.examples;
|
||||
using bts.api.orderman;
|
||||
using bts.api.position;
|
||||
using CommandLine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace bts.api.csharp {
|
||||
class Program {
|
||||
static void Main(string[] args) =>
|
||||
Parser.Default.ParseArguments<Options>(args).WithParsed(_run);
|
||||
|
||||
static void _run(Options options) {
|
||||
var exit_event = new ManualResetEvent(false);
|
||||
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
|
||||
var service_context = new ServiceContext();
|
||||
var login = service_context.login("bts", "");
|
||||
login.Wait();
|
||||
var examples = new List<IDisposable>();
|
||||
if (options.subscribe_securities ||! options.any_enabled) {
|
||||
examples.Add(new SecurityExample(service_context.locate<MarketDataService>()));
|
||||
}
|
||||
if (options.subscribe_positions || !options.any_enabled) {
|
||||
examples.Add(new PositionExample(service_context.locate<PositionService>()));
|
||||
}
|
||||
if (options.subscribe_orders || !options.any_enabled) {
|
||||
examples.Add(new OrderExample(service_context.locate<OrderService>()));
|
||||
}
|
||||
if (options.subscribe_book || !options.any_enabled) {
|
||||
examples.Add(new BookExample(service_context.locate<MarketDataService>()));
|
||||
}
|
||||
if (options.send_orders || !options.any_enabled) {
|
||||
examples.Add(SendOrderLoop.init_loop(service_context.locate<OrderService>(), service_context.locate<MarketDataService>()));
|
||||
}
|
||||
Console.CancelKeyPress += on_cancel_key_press;
|
||||
exit_event.WaitOne();
|
||||
foreach (var ex in examples)
|
||||
ex.Dispose();
|
||||
//
|
||||
void on_cancel_key_press(object sender, ConsoleCancelEventArgs e) {
|
||||
exit_event.Set();
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
class Options {
|
||||
[Option("subscribe-securities", Required = false, HelpText = "run security-subscription example.")]
|
||||
public bool subscribe_securities { get; set; }
|
||||
|
||||
[Option("subscribe-orders", Required = false, HelpText = "run order-subscription example.")]
|
||||
public bool subscribe_orders { get; set; }
|
||||
|
||||
[Option("subscribe-positions", Required = false, HelpText = "run position-subscription example.")]
|
||||
public bool subscribe_positions { get; set; }
|
||||
|
||||
[Option("subscribe-books", Required = false, HelpText = "run book-subscription example.")]
|
||||
public bool subscribe_book { get; set; }
|
||||
|
||||
[Option("send-orders", Required = false, HelpText = "run send-orders example.")]
|
||||
public bool send_orders { get; set; }
|
||||
|
||||
// if none are enabled, all options will be flipped on,
|
||||
// so the user need not specify a single argument to see the examples run.
|
||||
public bool any_enabled =>
|
||||
subscribe_securities ||
|
||||
subscribe_orders ||
|
||||
subscribe_positions ||
|
||||
subscribe_book ||
|
||||
send_orders;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using bts.api.core;
|
||||
using bts.api.csharp.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using SecurityKey = System.UInt32;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="MarketDataService"/> api to observe book updates
|
||||
/// for security in the system, and organize them into a Dictionary.
|
||||
/// </summary>
|
||||
class BookExample : IDisposable {
|
||||
readonly Dictionary<SecurityKey, BookEntryImmutable> _books = new Dictionary<SecurityKey, BookEntryImmutable>();
|
||||
readonly IDisposable _subscription;
|
||||
|
||||
public BookExample(MarketDataService market_data_service) {
|
||||
_subscription = market_data_service.subscribei_book()
|
||||
.ObserveOn(SynchronizationContext.Current)
|
||||
.Subscribe(_on_book_update);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_subscription.Dispose();
|
||||
}
|
||||
|
||||
void _on_book_update(Update<BookEntryImmutable, uint> update) {
|
||||
foreach (var new_book in update.added) {
|
||||
// of course, we shouldn't have entries for any books in update.added yet
|
||||
Debug.Assert(!_books.ContainsKey(new_book.Key));
|
||||
// display bbo in the console window
|
||||
_print_book_info(new_book.Key, $"book received for the first time, bbo is: {BookUtils.fmt_bbo(new_book)}");
|
||||
// add the new book to our map
|
||||
_books.Add(new_book.Key, new_book);
|
||||
}
|
||||
foreach (var change in update.changes) {
|
||||
// of course, the security key shouldn't change
|
||||
Debug.Assert(change.Old.Key == change.New.Key);
|
||||
// our book entry for this security should be identitical to change.Old
|
||||
Debug.Assert(change.Old.Bid.SequenceEqual(_books[change.Old.Key].Bid));
|
||||
Debug.Assert(change.Old.Ask.SequenceEqual(_books[change.Old.Key].Ask));
|
||||
// display the current book in the console window
|
||||
_print_book_info(change.New.Key, $"book changed, now bbo is: {BookUtils.fmt_bbo(change.New)}");
|
||||
// update our map with the changed book
|
||||
_books[change.New.Key] = change.New;
|
||||
}
|
||||
foreach (var old_book in update.removed) {
|
||||
// of course, we should have entries for all books in update.removed
|
||||
Debug.Assert(_books.Remove(old_book.Key));
|
||||
}
|
||||
}
|
||||
|
||||
static void _print_book_info(SecurityKey key, string message) {
|
||||
Console.WriteLine($"Security {key}: {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using AccountKey = System.UInt32;
|
||||
using OrderKey = System.UInt32;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="OrderService"/> to observe order updates,
|
||||
/// and group them by account.
|
||||
/// </summary>
|
||||
class OrderExample : IDisposable {
|
||||
readonly Dictionary<AccountKey, Dictionary<OrderKey, OrderRecordImmutable>> _data = new Dictionary<AccountKey, Dictionary<AccountKey, OrderRecordImmutable>>();
|
||||
IDisposable _subscription;
|
||||
|
||||
public OrderExample(OrderService order_service) {
|
||||
/// 'OrderService' has 2 methods which can be used to subscribe to order updates.
|
||||
/// -------------------------------------------------------------------------------------
|
||||
/// 1) <see cref="OrderService.subscribe_orders(string)"/>.
|
||||
/// 2) <see cref="OrderService.subscribei_orders(string)"/>.
|
||||
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
|
||||
/// --------------------------------------------------------------------------------------
|
||||
_subscription = order_service.subscribei_orders()
|
||||
// the 'ObserveOn' call below ensures that the
|
||||
// callback will be invoked this thread.
|
||||
// Be sure to do this if you don't want to worry
|
||||
// about multi-threading issues (locking, etc).
|
||||
.ObserveOn(SynchronizationContext.Current)
|
||||
.Subscribe(_on_update);
|
||||
|
||||
}
|
||||
|
||||
public event Action<AccountKey> account_orders_changed;
|
||||
|
||||
public void Dispose() {
|
||||
_subscription.Dispose();
|
||||
}
|
||||
|
||||
public IEnumerable<OrderRecordImmutable> get_account_orders(AccountKey account) {
|
||||
if (_data.TryGetValue(account, out var account_orders)) {
|
||||
return account_orders.Values;
|
||||
}
|
||||
return Enumerable.Empty<OrderRecordImmutable>();
|
||||
}
|
||||
|
||||
void _on_order_added(OrderRecordImmutable new_order) {
|
||||
if (!_data.TryGetValue(new_order.Def.AccountId, out var account_orders)) {
|
||||
// this is the first order for this account.
|
||||
// initialize the account orders dictionary, and add it to our data.
|
||||
account_orders = new Dictionary<AccountKey, OrderRecordImmutable>();
|
||||
_data.Add(new_order.Def.AccountId, account_orders);
|
||||
}
|
||||
// now add the order to account_orders
|
||||
account_orders.Add(new_order.Key, new_order);
|
||||
}
|
||||
|
||||
void _on_order_changed(Change<OrderRecordImmutable> change) {
|
||||
// ch.New and ch.Old have the same key.
|
||||
Debug.Assert(change.New.Key == change.Old.Key);
|
||||
var key = change.New.Key;
|
||||
// react to a changed order
|
||||
if (change.New.State.FilledQuantity != change.Old.State.FilledQuantity) {
|
||||
// Display new fills in the console window.
|
||||
var num_new_fills = change.New.State.FilledQuantity - change.Old.State.FilledQuantity;
|
||||
_print_order_info(key, $"received {num_new_fills} new fills.");
|
||||
// Be mindful that orders can be overfilled.
|
||||
if (change.New.State.FilledQuantity > change.New.State.Quantity) {
|
||||
// Display overfills in the console window.
|
||||
var num_over_fills = change.New.State.FilledQuantity - change.New.State.Quantity;
|
||||
_print_order_info(key, $"overfilled by {num_over_fills}");
|
||||
} else {
|
||||
// Display working qty in the console window.
|
||||
var working_qty = change.New.State.Quantity - change.New.State.FilledQuantity;
|
||||
_print_order_info(key, $"still working {working_qty}.");
|
||||
}
|
||||
}
|
||||
// Display order status changes in the console window.
|
||||
if (change.New.State.Status != change.Old.State.Status) {
|
||||
_print_order_info(key, $"status changed from {change.Old.State.Status} to {change.New.State.Status}.");
|
||||
}
|
||||
// Account will not change, so our dictionary will definitely have a key for this account.
|
||||
Debug.Assert(_data.ContainsKey(change.New.Def.AccountId));
|
||||
var account_orders = _data[change.New.Def.AccountId];
|
||||
// The value for this key in account_orders should equal ch.Old.
|
||||
Debug.Assert(account_orders[key].Equals(change.Old));
|
||||
// update account orders with the changed order.
|
||||
account_orders[key] = change.New;
|
||||
}
|
||||
|
||||
void _on_order_removed(OrderRecordImmutable old_val) {
|
||||
// An entry for this order should be present in our data,
|
||||
// as it is in the 'removed' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(_data.ContainsKey(old_val.Def.AccountId));
|
||||
var account_orders = _data[old_val.Def.AccountId];
|
||||
// the removed value should match the one currently in our data.
|
||||
// Assert this is the case, then remove the order.
|
||||
Debug.Assert(account_orders[old_val.Key].Equals(old_val));
|
||||
account_orders.Remove(old_val.Key);
|
||||
_print_order_info(old_val.Key, "has been removed.");
|
||||
}
|
||||
|
||||
void _on_update(Update<OrderRecordImmutable, OrderKey> update) {
|
||||
// record which accounts experienced an order update, then
|
||||
// at the end of this method, fire the account_orders_changed event for each
|
||||
var accounts_with_order_updates = new HashSet<AccountKey>();
|
||||
foreach (OrderRecordImmutable new_order in update.added) {
|
||||
_on_order_added(new_order);
|
||||
accounts_with_order_updates.Add(new_order.Def.AccountId);
|
||||
}
|
||||
foreach (Change<OrderRecordImmutable> change in update.changes) {
|
||||
_on_order_changed(change);
|
||||
accounts_with_order_updates.Add(change.New.Def.AccountId);
|
||||
}
|
||||
foreach (OrderRecordImmutable old_order in update.removed) {
|
||||
_on_order_removed(old_order);
|
||||
accounts_with_order_updates.Add(old_order.Def.AccountId);
|
||||
}
|
||||
// display all accounts with order updates in the Console window.
|
||||
Console.WriteLine("The following accounts experienced order changes: ");
|
||||
foreach (var account in accounts_with_order_updates) {
|
||||
Console.WriteLine("\n\t" + account);
|
||||
}
|
||||
// lastly, fire the account_orders_changed event for each account experienced an order update.
|
||||
foreach (var account in accounts_with_order_updates) {
|
||||
account_orders_changed?.Invoke(account);
|
||||
}
|
||||
}
|
||||
|
||||
void _print_order_info(OrderKey order_key, string message) {
|
||||
Console.WriteLine($"Order {order_key}: {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
/// <summary>
|
||||
/// A pair of security, and account id,
|
||||
/// which uniquely identifies each PositionUnit.
|
||||
/// </summary>
|
||||
readonly struct PositionKey {
|
||||
public PositionKey(uint security_id, uint account_id) {
|
||||
this.security_id = security_id;
|
||||
this.account_id = account_id;
|
||||
}
|
||||
|
||||
public uint security_id { get; }
|
||||
public uint account_id { get; }
|
||||
|
||||
public static PositionKey from_position(PositionUnitImmutable p) =>
|
||||
new PositionKey(p.SecurityId, p.AccountId);
|
||||
|
||||
public override string ToString() =>
|
||||
$"Sec {security_id}, Acc {account_id}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the <see cref="PositionService"/> api to monitor positions
|
||||
/// for every account/security pair, and organize them into a Dictionary.
|
||||
/// </summary>
|
||||
class PositionExample : IDisposable {
|
||||
readonly Dictionary<PositionKey, PositionUnitImmutable> _data = new Dictionary<PositionKey, PositionUnitImmutable>();
|
||||
IDisposable _subscription;
|
||||
|
||||
public PositionExample(PositionService position_service) {
|
||||
/// 'PositionService' has 2 methods which can be used to subscribe to position updates.
|
||||
/// -------------------------------------------------------------------------------------
|
||||
/// 1) <see cref="PositionService.subscribe_position_data(string)"/>.
|
||||
/// 2) <see cref="PositionService.subscribei_position_data(string)"/>.
|
||||
/// This method (no. 2) is typically preferrable because the data it provides is immutable.
|
||||
/// --------------------------------------------------------------------------------------
|
||||
_subscription = position_service.subscribei_position_data()
|
||||
// the 'ObserveOn' call below ensures that the
|
||||
// callback will be invoked this thread.
|
||||
// Be sure to do this if you don't want to worry
|
||||
// about multi-threading issues (locking, etc).
|
||||
.ObserveOn(SynchronizationContext.Current)
|
||||
.Subscribe(_on_update);
|
||||
}
|
||||
|
||||
public event Action data_changed;
|
||||
|
||||
public IReadOnlyDictionary<PositionKey, PositionUnitImmutable> data => _data;
|
||||
|
||||
public void Dispose() =>
|
||||
_subscription.Dispose();
|
||||
|
||||
void _on_position_added(PositionUnitImmutable new_val) {
|
||||
var k = PositionKey.from_position(new_val);
|
||||
// this entry should NOT be present in our _data, as it is in the 'added' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(!_data.ContainsKey(k));
|
||||
_print_position_info(k, $"initial position received. Day: Long {new_val.DayLongPos}, Short {new_val.DayShortPos}, Net {new_val.DayLongPos - new_val.DayShortPos}.");
|
||||
// add the new position unit to _data
|
||||
_data.Add(k, new_val);
|
||||
}
|
||||
|
||||
void _on_position_changed(Change<PositionUnitImmutable> change) {
|
||||
var k = PositionKey.from_position(change.New);
|
||||
// ch.New and ch.Old have the same key (i.e., the Account/Security pair),
|
||||
Debug.Assert(change.New.AccountId == change.Old.AccountId);
|
||||
Debug.Assert(change.New.SecurityId == change.Old.SecurityId);
|
||||
// of course, the value for this key in our dictionary should equal ch.Old.
|
||||
Debug.Assert(_data[k].Equals(change.Old));
|
||||
// Display changes to the day position in the console window.
|
||||
// 1) check if the day long changed
|
||||
if (change.New.DayLongPos != change.Old.DayLongPos) {
|
||||
Console.WriteLine($"[{k}] day long changed from {change.Old.DayLongPos} to {change.New.DayLongPos}.");
|
||||
}
|
||||
// 2) check if day short changed
|
||||
if (change.New.DayShortPos != change.Old.DayShortPos) {
|
||||
Console.WriteLine($"[{k}] day short changed from {change.Old.DayShortPos} to {change.New.DayShortPos}.");
|
||||
}
|
||||
// 3) check if net day position changed
|
||||
var new_net_day_pos = change.New.DayLongPos - change.New.DayShortPos;
|
||||
var old_net_day_pos = change.Old.DayLongPos - change.Old.DayShortPos;
|
||||
if (new_net_day_pos != old_net_day_pos) {
|
||||
Console.WriteLine($"[{k}] day net changed from {old_net_day_pos} to {new_net_day_pos}.");
|
||||
}
|
||||
// update _data with the changed position.
|
||||
_data[k] = change.New;
|
||||
}
|
||||
|
||||
void _on_position_removed(PositionUnitImmutable old_val) {
|
||||
var k = PositionKey.from_position(old_val);
|
||||
// this entry should be present in our _data, as it is in the 'removed' items.
|
||||
// Assert this is the case to illustrate the point.
|
||||
Debug.Assert(_data.ContainsKey(k));
|
||||
// this should match our data. Assert this is the case.
|
||||
Debug.Assert(_data[k].Equals(old_val));
|
||||
_data.Remove(k);
|
||||
Console.WriteLine($"[{k}] position has been removed.");
|
||||
}
|
||||
|
||||
void _on_update(Update<PositionUnitImmutable, int> update) {
|
||||
foreach (PositionUnitImmutable p in update.added) {
|
||||
_on_position_added(p);
|
||||
}
|
||||
foreach (Change<PositionUnitImmutable> ch in update.changes) {
|
||||
_on_position_changed(ch);
|
||||
}
|
||||
foreach (PositionUnitImmutable p in update.removed) {
|
||||
_on_position_removed(p);
|
||||
}
|
||||
data_changed?.Invoke();
|
||||
}
|
||||
|
||||
void _print_position_info(PositionKey position_key, string message) {
|
||||
Console.WriteLine($"Position {position_key}: {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
using bts.api.core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using SecurityKey = System.UInt32;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
/// <summary>
|
||||
/// Uses the <see cref="MarketDataService"/> api to observe the
|
||||
/// state of securities in the system.
|
||||
/// </summary>
|
||||
class SecurityExample : IDisposable {
|
||||
readonly IDisposable _subscription;
|
||||
readonly Dictionary<SecurityKey, SecurityImmutable> _securities = new Dictionary<SecurityKey, SecurityImmutable>();
|
||||
|
||||
public SecurityExample(MarketDataService market_data_service) {
|
||||
_subscription = market_data_service.subscribei_securities()
|
||||
.ObserveOn(SynchronizationContext.Current)
|
||||
.Subscribe(_on_securities_update);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_subscription.Dispose();
|
||||
}
|
||||
|
||||
void _on_securities_update(Update<SecurityImmutable, uint> update) {
|
||||
foreach (var new_security in update.added) {
|
||||
// of course, we shouldn't have entries for any books in update.added yet
|
||||
Debug.Assert(!_securities.ContainsKey(new_security.Id));
|
||||
// display bbo in the console window
|
||||
Console.WriteLine($"received new security: {new_security}.");
|
||||
// add the new security to our map
|
||||
_securities.Add(new_security.Id, new_security);
|
||||
}
|
||||
// As you may have noticed, all of the subcribe methods in the api
|
||||
// return an IObservable<Update<T>>. It is worth noting that for security updates,
|
||||
// 'changes' will always be empty.
|
||||
Debug.Assert(!update.changes.Any());
|
||||
foreach (var old_security in update.removed) {
|
||||
// of course, we should have entries for all books in update.removed
|
||||
Debug.Assert(_securities.Remove(old_security.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
using bts.api.csharp.utils;
|
||||
using bts.api.orderman;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
static class OrderTemplates {
|
||||
public static CustomOrderDefinition limit() => new CustomOrderDefinition { Limit = new LimitOrderDefinition() };
|
||||
public static CustomOrderDefinition market() => new CustomOrderDefinition { Market = new MarketOrderDefinition() };
|
||||
public static CustomOrderDefinition iceberg(double show_quantity) => iceberg(show_quantity, limit());
|
||||
public static CustomOrderDefinition iceberg(double show_quantity, CustomOrderDefinition child) =>
|
||||
new CustomOrderDefinition {
|
||||
Iceberg = new IcebergOrderDefinition {
|
||||
ShowQuantity = show_quantity,
|
||||
Child = child
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exemplifies how the <see cref="OrderService"/> api may be used to
|
||||
/// send several common order types (limit, market, and iceberg).
|
||||
/// </summary>
|
||||
class SendOrderModel {
|
||||
readonly OrderService _service;
|
||||
|
||||
public SendOrderModel(OrderService service) {
|
||||
_service = service;
|
||||
}
|
||||
|
||||
public OrderDefinition create_order_definition(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_def) {
|
||||
return new OrderDefinition {
|
||||
Account = account.name,
|
||||
AccountId = account.id,
|
||||
User = user.name,
|
||||
UserId = user.id,
|
||||
SecurityKey = security_key,
|
||||
CustomDef = custom_def
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a limit order (asynchronous).
|
||||
/// </summary>
|
||||
/// <returns>The order id if the submission was successful, otherwise null.</returns>
|
||||
public Task<uint?> send_limit_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity, double price) {
|
||||
return send_order_async(user, account, security_key, OrderTemplates.limit(), quantity, price);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a market order (asynchronous). Note no price parameter is required for market orders.
|
||||
/// </summary>
|
||||
/// <returns>The order id if the submission was successful, otherwise null.</returns>
|
||||
public Task<uint?> send_market_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity) {
|
||||
return send_order_async(user, account, security_key, OrderTemplates.market(), quantity, 0.0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an iceberg of limit orders (asynchronous).
|
||||
/// </summary>
|
||||
/// <returns>The order id if the submission was successful, otherwise null.</returns>
|
||||
public Task<uint?> send_iceberg_order_async(NameIDPair user, NameIDPair account, uint security_key, uint quantity, double price, double show_quantity) {
|
||||
return send_order_async(user, account, security_key, OrderTemplates.iceberg(show_quantity), quantity, price);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an order of the type specified by <paramref name="custom_order_definition"/> (asynchronous).
|
||||
/// </summary>
|
||||
/// <returns>The order id if the submission was successful, otherwise null.</returns>
|
||||
public async Task<uint?> send_order_async(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_order_definition, uint quantity, double price) {
|
||||
Console.WriteLine($"submitting order for user ({user}), account ({account}): {OrderUtils.fmt_order(quantity, price, custom_order_definition)}.");
|
||||
var reply = await _service.create_order_async(new Create {
|
||||
Quantity = quantity,
|
||||
Price = price,
|
||||
NewOrder = create_order_definition(user, account, security_key, custom_order_definition)
|
||||
});
|
||||
if (reply.success) {
|
||||
Console.WriteLine($"received ack that order has been submitted. Order id is {reply.value.OrderKey}.");
|
||||
return reply.value.OrderKey;
|
||||
} else {
|
||||
Console.WriteLine($"order submission was unsuccessful: [{reply.value.ErrorCode}] {reply.value.Msg}.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void send_order(NameIDPair user, NameIDPair account, uint security_key, CustomOrderDefinition custom_order_definition, uint quantity, double price) {
|
||||
/// Note, the thread will be blocked until a response is received from the server.
|
||||
/// For this reason, <see cref="OrderService.create_order_async(Create)"/> is usually preferred.
|
||||
/// An example of how to use that method is provided in <see cref="send_order_async(NameIDPair, NameIDPair, CustomOrderDefinition, uint, double)"/>.
|
||||
Console.WriteLine($"Creating order {OrderUtils.fmt_order(quantity, price, custom_order_definition)}");
|
||||
var response = _service.create_order(new Create {
|
||||
Quantity = quantity,
|
||||
Price = price,
|
||||
NewOrder = create_order_definition(user, account, security_key, custom_order_definition)
|
||||
});
|
||||
switch (response.ErrorCode) {
|
||||
case OrderError.Ok:
|
||||
_print_order_info(response.OrderKey, "successfully created.");
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine($"Failed to create order: [{response.ErrorCode}] {response.Msg}.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void _print_order_info(uint order_key, string message) {
|
||||
Console.WriteLine($"Order {order_key}: {message}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.api.csharp.examples {
|
||||
/// <summary>
|
||||
/// The code in this example is a bit more advanced than the others,
|
||||
/// as it is designed to send orders, asynchronously, in a continuous loop.
|
||||
/// For straightforward examples of how to send orders with the api,
|
||||
/// check out <see cref="SendOrderModel"/>.
|
||||
/// </summary>
|
||||
static class SendOrderLoop {
|
||||
/// <summary>
|
||||
/// As a security key is required to send an order, this method
|
||||
/// uses the <see cref="MarketDataService"/> to observe the securities stream,
|
||||
/// then on the first update, initializes the send-order-loop <see cref="LoopImpl"/>
|
||||
/// with one of the newly received security keys.
|
||||
/// </summary>
|
||||
public static IDisposable init_loop(OrderService order_service, MarketDataService market_data_service) {
|
||||
// If you need to familiarize yourself with the IObservable pattern, check out the official docs:
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.iobservable-1?view=netcore-3.1
|
||||
|
||||
var single_security_stream = market_data_service.subscribei_securities()
|
||||
.Where(u => u.all.Any())
|
||||
.Take(1)
|
||||
.Select(u => u.all.FirstOrDefault());
|
||||
|
||||
return init_loop(single_security_stream, create_loop);
|
||||
IDisposable create_loop(SecurityImmutable sec) {
|
||||
return new LoopImpl(order_service, sec.Id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Observes the security stream and creates a new send-order-loop
|
||||
/// whenever a new security arrives. The previous send-order-loop
|
||||
/// is disposed when the next security arrives, so only one send-order-loop
|
||||
/// shall be active at a time.
|
||||
/// </summary>
|
||||
/// <param name="security">The security stream used to initialize send-order-loops.</param>
|
||||
/// <returns>An IDisposable that unsubsribes from the securities stream, and disposes the active send-order-loop.</returns>
|
||||
public static IDisposable init_loop(IObservable<SecurityImmutable> security, Func<SecurityImmutable, IDisposable> factory) {
|
||||
IDisposable active_loop = null;
|
||||
var security_sub = security.Subscribe(s =>
|
||||
Interlocked.Exchange(ref active_loop, factory(s))?.Dispose());
|
||||
return Disposable.Create(dispose);
|
||||
void dispose() {
|
||||
security_sub.Dispose();
|
||||
active_loop?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class LoopImpl : IDisposable {
|
||||
bool _is_disposed;
|
||||
|
||||
public LoopImpl(OrderService service, uint security_key)
|
||||
: this(service, Users.Default, Accounts.Default, security_key, 10, TimeSpan.FromSeconds(3)) { }
|
||||
|
||||
public LoopImpl(OrderService service, NameIDPair user, NameIDPair account, uint security_key, int max_orders, TimeSpan delay_between_orders) {
|
||||
var model = new SendOrderModel(service);
|
||||
send_orders_continuously();
|
||||
|
||||
async void send_orders_continuously() {
|
||||
// Periodically send an order with one of the CustomOrderDefinitions below.
|
||||
// If you are unfamiliar with async/await, this will also serve as a simple example
|
||||
// of how those keywords may be used. However, there is no substite for the official docs:
|
||||
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
|
||||
var order_templates = new CustomOrderDefinitionImmutable[] {
|
||||
new CustomOrderDefinitionImmutable(OrderTemplates.limit()),
|
||||
new CustomOrderDefinitionImmutable(OrderTemplates.market()),
|
||||
new CustomOrderDefinitionImmutable(OrderTemplates.iceberg(5))
|
||||
};
|
||||
for (int num_orders_sent = 0; num_orders_sent < max_orders && !_is_disposed; num_orders_sent++) {
|
||||
int template_index = num_orders_sent % order_templates.Length;
|
||||
CustomOrderDefinitionImmutable templatei = order_templates[template_index];
|
||||
// The order templates we created are immutable, however the api uses the mutable form.
|
||||
// Not to worry tho, there is a nifty method called 'CloneToMutable.'
|
||||
// Note that all of the non-primitive immutable parameters in the api methods have a 'CloneToMutable' method,
|
||||
// it is not unique to CustomOrderDefinitionImmutable.
|
||||
CustomOrderDefinition template = templatei.CloneToMutable();
|
||||
await model.send_order_async(user, account, security_key, template, 12, 100);
|
||||
// pause for a moment before sending the next order
|
||||
await Task.Delay(delay_between_orders);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// flip _is_disposed to true, so the 'send_orders_continuously' loop
|
||||
// knows it's time to stop.
|
||||
_is_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class BookUtils {
|
||||
/// <summary>
|
||||
/// Get a display string of the bbo.
|
||||
/// </summary>
|
||||
public static string fmt_bbo(BookEntryImmutable book) {
|
||||
return fmt_bbo(get_bbo(book));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a display string of the bbo.
|
||||
/// </summary>
|
||||
public static string fmt_bbo((PriceEntryImmutable? bid, PriceEntryImmutable? ask) bbo) {
|
||||
return $"{fmt_price_entry(bbo.bid)} <--> {fmt_price_entry(bbo.ask)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a display string of a <see cref="PriceEntryImmutable"/>.
|
||||
/// </summary>
|
||||
public static string fmt_price_entry(PriceEntryImmutable? price_entry) {
|
||||
return price_entry.HasValue ? fmt_price_entry(price_entry.Value) : "N/A";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a display string of a <see cref="PriceEntryImmutable"/>.
|
||||
/// </summary>
|
||||
public static string fmt_price_entry(PriceEntryImmutable price_entry) {
|
||||
return $"{price_entry}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a display string of the bbo.
|
||||
/// </summary>
|
||||
public static (PriceEntryImmutable? best_bid, PriceEntryImmutable? best_ask) get_bbo(BookEntryImmutable book) {
|
||||
return (get_top_of_book(book.Bid), get_top_of_book(book.Ask));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first element in <paramref name="depth"/>,
|
||||
/// as this represents the top price level
|
||||
/// of a particular side of the market (bid or ask).
|
||||
/// </summary>
|
||||
public static PriceEntryImmutable? get_top_of_book(IEnumerable<PriceEntryImmutable> depth) {
|
||||
foreach (var d in depth)
|
||||
return d;
|
||||
// there are no elements (the book is empty for this side), so return null.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using bts.api.orderman;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class OrderUtils {
|
||||
public static string fmt_order(uint quantity, double price, CustomOrderDefinition custom_order_definition) {
|
||||
return $"{quantity} @ {price}: {custom_order_definition}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace bts.api.csharp.utils {
|
||||
static class SecurityUtils {
|
||||
}
|
||||
}
|
||||
@@ -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/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)
|
||||
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)
|
||||
@@ -1,26 +0,0 @@
|
||||
#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";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#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();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#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;
|
||||
@@ -32,7 +31,6 @@ int main(int argc, char **argv) {
|
||||
REGISTER(Help);
|
||||
REGISTER(Run);
|
||||
REGISTER(Md);
|
||||
REGISTER(TestCeloxica);
|
||||
REGISTER(ConfigCheck);
|
||||
REGISTER(KeyCode);
|
||||
REGISTER(RunSim);
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
#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
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#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
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
#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>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#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
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#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
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace celoxica
|
||||
{
|
||||
namespace gmac
|
||||
{
|
||||
|
||||
#include "celoxica/gmac.h"
|
||||
#include "celoxica/gmac_template_v3.h"
|
||||
#include "celoxica/gmac_info.h"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
// #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,421 +1,451 @@
|
||||
#include "ICEListener.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <vector>
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
using namespace bts::md::ice;
|
||||
|
||||
namespace bts {
|
||||
namespace md {
|
||||
namespace ice {
|
||||
|
||||
template <>
|
||||
double double_of_price<onixs_ice::Rational>(const onixs_ice::Rational& price, int8_t denominator)
|
||||
{
|
||||
UNUSED(denominator);
|
||||
double dbl = price.convertTo<double>();
|
||||
return dbl;
|
||||
}
|
||||
|
||||
template<>
|
||||
void ICEListener::set_security_specific<onixs_ice::FuturesProductDefinition>(const onixs_ice::FuturesProductDefinition& def,
|
||||
FixedSecurity& fsec)
|
||||
{
|
||||
//fsec.cash_per_point = double_of_price(def.tickValue, fsec.ice_price_denominator);
|
||||
//fsec.cash_per_point = def.tickValue;
|
||||
fsec.cash_per_point = def.unitQuantity;
|
||||
}
|
||||
|
||||
ICEListener::ICEListener(ICESecurityIndex& index, MDProducer& producer, const std::vector<MDProducerChannel> channels) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_symbol_filter(),
|
||||
_channels(channels),
|
||||
_concurrency_monitor(0)
|
||||
{
|
||||
}
|
||||
|
||||
ICEListener::~ICEListener() {}
|
||||
|
||||
std::string ICEListener::parse_contract_symbol(const std::string& sym)
|
||||
{
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
//
|
||||
//State change
|
||||
void ICEListener::onHandlerStateChange(const onixs_ice::HandlerStateChange&)
|
||||
{
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , const onixs_ice::MessageInfo&)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Warnings/Errors
|
||||
void ICEListener::onError(const onixs_ice::Error& error)
|
||||
{
|
||||
std::string error_desc(error.description());
|
||||
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
|
||||
}
|
||||
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
|
||||
}
|
||||
void ICEListener::onWarning(const onixs_ice::Warning& warning)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
|
||||
}
|
||||
|
||||
//
|
||||
// Security discovery
|
||||
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
if(msg.isSpread)
|
||||
on_future_strip(msg, is_interested);
|
||||
else
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& def, bool& is_interested)
|
||||
{
|
||||
on_uds_future_spread(def, is_interested);
|
||||
}
|
||||
//This is only for UDS
|
||||
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onOptionsProductDefinition(const onixs_ice::OptionsProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsMarketDefinition(const onixs_ice::NewOptionsMarketDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
void ICEListener::onOptionsStrategyDefinition(const onixs_ice::OptionsStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsStrategyDefinition(const onixs_ice::NewOptionsStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void ICEListener::onMarketSnapshot(const onixs_ice::MarketSnapshot& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
if (msg.settlePriceDateTime > 0) {
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(msg.settlePriceDateTime) );
|
||||
}
|
||||
|
||||
LOG(md,Diag,"OnixSICE Got Market Snap : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), datetime::today() );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
|
||||
data->handle.set_last_trade( ts, pricesize_of_price_size(msg.lastTradePrice, deal_denominator, msg.lastTradeQuantity),
|
||||
ll_of_timestamp(msg.lastTradeDateTime), msg.volume );
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStatistics(const onixs_ice::MarketStatistics& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
data->handle.set_total_volume(msg.volume);
|
||||
}
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotOrder(const onixs_ice::MarketSnapshotOrder& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPriceLevel& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
// Book
|
||||
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
|
||||
|
||||
auto data = _index.get_security(onixs_book.marketId());
|
||||
if (data == nullptr)
|
||||
return;
|
||||
bts_assert(data->id == onixs_book.marketId());
|
||||
auto denominator = data->handle.security().ice_price_denominator;
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = onixs_book.bids().size();
|
||||
size_t asks_size = onixs_book.offers().size();
|
||||
if (!data->handle.has_depth())
|
||||
{
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dbids; dbids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> ibids; ibids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dasks; dasks.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> iasks; iasks.fill(NO_MARKET);
|
||||
for (size_t x = 0; x < bids_size; ++x)
|
||||
{
|
||||
auto& bid = onixs_book.bids()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dbids[x] = pricesize_of_price_size(bid.price, denominator, bid.quantity - bid.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
ibids[x] = pricesize_of_price_size(bid.price, denominator, bid.impliedQuantity);
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x)
|
||||
{
|
||||
auto& ask = onixs_book.offers()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dasks[x] = pricesize_of_price_size(ask.price, denominator, ask.quantity - ask.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
iasks[x] = pricesize_of_price_size(ask.price, denominator, ask.impliedQuantity);
|
||||
}
|
||||
data->handle.update_books(TimeStamp::now(), dbids, dasks, ibids, iasks, (uint32_t) msg_info.sequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Trades
|
||||
AggressorSide map_aggressor(onixs_ice::AggressorSide::Enum agside)
|
||||
{
|
||||
switch(agside) {
|
||||
default:
|
||||
case onixs_ice::AggressorSide::NoAggressor:
|
||||
return AGGRESSOR_SIDE_NONE;
|
||||
case onixs_ice::AggressorSide::Buy:
|
||||
return AGGRESSOR_SIDE_BUY;
|
||||
case onixs_ice::AggressorSide::Sell:
|
||||
return AGGRESSOR_SIDE_SELL;
|
||||
}
|
||||
}
|
||||
|
||||
void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size,
|
||||
onixs_ice::AggressorSide::Enum agside, const onixs_ice::DateTime& onixs_date, bool from_spread)
|
||||
{
|
||||
auto ts = utils::TimeStamp::now();
|
||||
PriceSize ps = pricesize_of_price_size(price, data->handle.security().ice_deal_denominator, size);
|
||||
auto date = ll_of_timestamp(onixs_date);
|
||||
data->handle.add_trade(ts, ps, date, map_aggressor(agside), 0u, from_spread);
|
||||
}
|
||||
|
||||
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != NULL)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto size = msg.quantity;
|
||||
auto price = msg.price;
|
||||
onixs_ice::AggressorSide::Enum agside = msg.aggressorSide;
|
||||
on_trade(data, price, size, agside, msg.transactDateTime, msg.isSystemPricedLeg);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// RFQ
|
||||
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
co2::QuoteRequestMessage message;
|
||||
message.set_key(data->handle.key());
|
||||
|
||||
auto time = ll_of_timestamp(msg.messageTimestamp);
|
||||
message.set_time(time);
|
||||
|
||||
long long rfqid = msg.rfqSystemId;
|
||||
message.set_request_id(std::to_string(rfqid));
|
||||
|
||||
//https://ref.onixs.biz/cpp-ice-impact-handler-guide/struct_onix_s_1_1_i_c_e_1_1i_mpact_1_1_market_data_1_1_side.html#a8150b7776c2a1749101acf22e868d091
|
||||
switch(msg.side)
|
||||
{
|
||||
case ' ':
|
||||
message.set_side(co2::QuoteRequestMessage::UNDEFINED);
|
||||
break;
|
||||
case '1':
|
||||
message.set_side(co2::QuoteRequestMessage::BUY);
|
||||
break;
|
||||
case '2':
|
||||
message.set_side(co2::QuoteRequestMessage::SELL);
|
||||
break;
|
||||
}
|
||||
message.set_size(msg.quantity);
|
||||
data->handle.add_rfq(ts, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto price = msg.settlementPrice;
|
||||
auto time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_settle_denominator;
|
||||
LOG(md,Diag,"OnixSICE Got Settlement: {} - {}", std::to_string(data->id), std::to_string(price));
|
||||
data->handle.update_settlement(ts, convert_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs_ice::MessageInfo &msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.openPrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.closePrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_closing( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(time) );
|
||||
|
||||
LOG(md,Warn,"OnixSICE Got EOD Market : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), date_of_timestamp(time) );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Feed
|
||||
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
UNUSED(feedId);
|
||||
}
|
||||
|
||||
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
|
||||
{
|
||||
UNUSED(feedId);
|
||||
UNUSED(marketIds);
|
||||
}
|
||||
|
||||
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
|
||||
{}
|
||||
|
||||
void ICEListener::onFeedStopped(onixs_ice::FeedId)
|
||||
{}
|
||||
|
||||
void ICEListener::onSessionNumberChange(onixs_ice::FeedId , short , short )
|
||||
{}
|
||||
|
||||
|
||||
//
|
||||
// Non-onixs calls
|
||||
void ICEListener::add_filter_symbol(const std::string& symbol)
|
||||
{
|
||||
_symbol_filter.insert(symbol);
|
||||
}
|
||||
|
||||
void ICEListener::heartbeat()
|
||||
{
|
||||
for (auto& c : _channels) { c.heartbeat(); }
|
||||
}
|
||||
|
||||
//
|
||||
// Private functions
|
||||
void ICEListener::add_security(const FixedSecurity& security)
|
||||
{
|
||||
// Set up the shared memory for this security
|
||||
auto handle = _producer.add_security(security);
|
||||
bts_assert(security.numeric_id != 0);
|
||||
// Add it to the index so it can receive messages
|
||||
_index.add_security(security.numeric_id, handle);
|
||||
}
|
||||
|
||||
void ICEListener::update_all_status(CoconutSecurityStatus status)
|
||||
{
|
||||
_index.walk_security_list([=](ICESecurityData& data)
|
||||
{
|
||||
data.handle.set_status(status);
|
||||
});
|
||||
}
|
||||
|
||||
}}} //namespace
|
||||
|
||||
|
||||
#include "ICEListener.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <vector>
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
using namespace bts::md::ice;
|
||||
|
||||
namespace bts {
|
||||
namespace md {
|
||||
namespace ice {
|
||||
|
||||
template <>
|
||||
double double_of_price<onixs_ice::Rational>(const onixs_ice::Rational& price, int8_t denominator)
|
||||
{
|
||||
UNUSED(denominator);
|
||||
double dbl = price.convertTo<double>();
|
||||
return dbl;
|
||||
}
|
||||
|
||||
template<>
|
||||
void ICEListener::set_security_specific<onixs_ice::FuturesProductDefinition>(const onixs_ice::FuturesProductDefinition& def,
|
||||
FixedSecurity& fsec)
|
||||
{
|
||||
//fsec.cash_per_point = double_of_price(def.tickValue, fsec.ice_price_denominator);
|
||||
//fsec.cash_per_point = def.tickValue;
|
||||
fsec.cash_per_point = def.unitQuantity;
|
||||
}
|
||||
|
||||
ICEListener::ICEListener(ICESecurityIndex& index, MDProducer& producer, const std::vector<MDProducerChannel> channels) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_symbol_filter(),
|
||||
_channels(channels),
|
||||
_concurrency_monitor(0)
|
||||
{
|
||||
}
|
||||
|
||||
ICEListener::~ICEListener() {}
|
||||
|
||||
std::string ICEListener::parse_contract_symbol(const std::string& sym)
|
||||
{
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
//
|
||||
//State change
|
||||
void ICEListener::onHandlerStateChange(const onixs_ice::HandlerStateChange&)
|
||||
{
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , const onixs_ice::MessageInfo&)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Warnings/Errors
|
||||
void ICEListener::onError(const onixs_ice::Error& error)
|
||||
{
|
||||
std::string error_desc(error.description());
|
||||
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
|
||||
}
|
||||
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
|
||||
}
|
||||
void ICEListener::onWarning(const onixs_ice::Warning& warning)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
|
||||
}
|
||||
|
||||
//
|
||||
// Security discovery
|
||||
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
if(msg.isSpread)
|
||||
on_future_strip(msg, is_interested);
|
||||
else
|
||||
on_future(msg, is_interested);
|
||||
LOG(md,Info,"ICE_Future,{},{},{},{},{},{},{},{},{},{},{},{},{}",
|
||||
is_interested,
|
||||
msg.productName, // NG Swing GDD Futures,
|
||||
msg.hubAlias, // Henry
|
||||
msg.marketDesc, // NG Swing GDD Futures - Henry - 10 Jul 20,
|
||||
msg.stripName, // 10 Jul 20
|
||||
msg.contractSymbol, // CGS SDN1020.N1020,
|
||||
msg.productId,
|
||||
msg.marketId,
|
||||
msg.requestMarketType,
|
||||
msg.securitySubType,
|
||||
msg.contractSymbolExtra,
|
||||
msg.clearedAlias,
|
||||
msg.isSpread);
|
||||
}
|
||||
|
||||
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
LOG(md,Info,"ICE_Strategy,{},{},{},{},{},{},{},{},{},{},{},{},{},{}",
|
||||
is_interested,
|
||||
msg.productName,
|
||||
msg.hubAlias,
|
||||
msg.marketDesc,
|
||||
msg.stripName,
|
||||
msg.contractSymbol,
|
||||
msg.productId,
|
||||
msg.marketId,
|
||||
msg.requestMarketType,
|
||||
msg.securitySubType,
|
||||
log::CharPtrWrapper {""},
|
||||
msg.clearedAlias,
|
||||
msg.productName,
|
||||
0);
|
||||
}
|
||||
|
||||
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onOptionsProductDefinition(const onixs_ice::OptionsProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsMarketDefinition(const onixs_ice::NewOptionsMarketDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
void ICEListener::onOptionsStrategyDefinition(const onixs_ice::OptionsStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsStrategyDefinition(const onixs_ice::NewOptionsStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void ICEListener::onMarketSnapshot(const onixs_ice::MarketSnapshot& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
if (msg.settlePriceDateTime > 0) {
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(msg.settlePriceDateTime) );
|
||||
}
|
||||
|
||||
LOG(md,Diag,"OnixSICE Got Market Snap : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), datetime::today() );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
|
||||
data->handle.set_last_trade( ts, pricesize_of_price_size(msg.lastTradePrice, deal_denominator, msg.lastTradeQuantity),
|
||||
ll_of_timestamp(msg.lastTradeDateTime), msg.volume );
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStatistics(const onixs_ice::MarketStatistics& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
data->handle.set_total_volume(msg.volume);
|
||||
}
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotOrder(const onixs_ice::MarketSnapshotOrder& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPriceLevel& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
// Book
|
||||
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
|
||||
|
||||
auto data = _index.get_security(onixs_book.marketId());
|
||||
if (data == nullptr)
|
||||
return;
|
||||
bts_assert(data->id == onixs_book.marketId());
|
||||
auto denominator = data->handle.security().ice_price_denominator;
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = onixs_book.bids().size();
|
||||
size_t asks_size = onixs_book.offers().size();
|
||||
if (!data->handle.has_depth())
|
||||
{
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dbids; dbids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> ibids; ibids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dasks; dasks.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> iasks; iasks.fill(NO_MARKET);
|
||||
for (size_t x = 0; x < bids_size; ++x)
|
||||
{
|
||||
auto& bid = onixs_book.bids()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dbids[x] = pricesize_of_price_size(bid.price, denominator, bid.quantity - bid.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
ibids[x] = pricesize_of_price_size(bid.price, denominator, bid.impliedQuantity);
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x)
|
||||
{
|
||||
auto& ask = onixs_book.offers()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dasks[x] = pricesize_of_price_size(ask.price, denominator, ask.quantity - ask.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
iasks[x] = pricesize_of_price_size(ask.price, denominator, ask.impliedQuantity);
|
||||
}
|
||||
data->handle.update_books(TimeStamp::now(), dbids, dasks, ibids, iasks, (uint32_t) msg_info.sequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Trades
|
||||
AggressorSide map_aggressor(onixs_ice::AggressorSide::Enum agside)
|
||||
{
|
||||
switch(agside) {
|
||||
default:
|
||||
case onixs_ice::AggressorSide::NoAggressor:
|
||||
return AGGRESSOR_SIDE_NONE;
|
||||
case onixs_ice::AggressorSide::Buy:
|
||||
return AGGRESSOR_SIDE_BUY;
|
||||
case onixs_ice::AggressorSide::Sell:
|
||||
return AGGRESSOR_SIDE_SELL;
|
||||
}
|
||||
}
|
||||
|
||||
void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size,
|
||||
onixs_ice::AggressorSide::Enum agside, const onixs_ice::DateTime& onixs_date, bool from_spread)
|
||||
{
|
||||
auto ts = utils::TimeStamp::now();
|
||||
PriceSize ps = pricesize_of_price_size(price, data->handle.security().ice_deal_denominator, size);
|
||||
auto date = ll_of_timestamp(onixs_date);
|
||||
data->handle.add_trade(ts, ps, date, map_aggressor(agside), 0u, from_spread);
|
||||
}
|
||||
|
||||
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != NULL)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto size = msg.quantity;
|
||||
auto price = msg.price;
|
||||
onixs_ice::AggressorSide::Enum agside = msg.aggressorSide;
|
||||
on_trade(data, price, size, agside, msg.transactDateTime, msg.isSystemPricedLeg);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// RFQ
|
||||
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
co2::QuoteRequestMessage message;
|
||||
message.set_key(data->handle.key());
|
||||
|
||||
auto time = ll_of_timestamp(msg.messageTimestamp);
|
||||
message.set_time(time);
|
||||
|
||||
long long rfqid = msg.rfqSystemId;
|
||||
message.set_request_id(std::to_string(rfqid));
|
||||
|
||||
//https://ref.onixs.biz/cpp-ice-impact-handler-guide/struct_onix_s_1_1_i_c_e_1_1i_mpact_1_1_market_data_1_1_side.html#a8150b7776c2a1749101acf22e868d091
|
||||
switch(msg.side)
|
||||
{
|
||||
case ' ':
|
||||
message.set_side(co2::QuoteRequestMessage::UNDEFINED);
|
||||
break;
|
||||
case '1':
|
||||
message.set_side(co2::QuoteRequestMessage::BUY);
|
||||
break;
|
||||
case '2':
|
||||
message.set_side(co2::QuoteRequestMessage::SELL);
|
||||
break;
|
||||
}
|
||||
message.set_size(msg.quantity);
|
||||
data->handle.add_rfq(ts, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto price = msg.settlementPrice;
|
||||
auto time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_settle_denominator;
|
||||
LOG(md,Diag,"OnixSICE Got Settlement: {} - {}", std::to_string(data->id), std::to_string(price));
|
||||
data->handle.update_settlement(ts, convert_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs_ice::MessageInfo &msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.openPrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.closePrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_closing( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(time) );
|
||||
|
||||
LOG(md,Warn,"OnixSICE Got EOD Market : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), date_of_timestamp(time) );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Feed
|
||||
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
UNUSED(feedId);
|
||||
}
|
||||
|
||||
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
|
||||
{
|
||||
UNUSED(feedId);
|
||||
UNUSED(marketIds);
|
||||
}
|
||||
|
||||
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
|
||||
{}
|
||||
|
||||
void ICEListener::onFeedStopped(onixs_ice::FeedId)
|
||||
{}
|
||||
|
||||
void ICEListener::onSessionNumberChange(onixs_ice::FeedId , short , short )
|
||||
{}
|
||||
|
||||
|
||||
//
|
||||
// Non-onixs calls
|
||||
void ICEListener::add_filter_symbol(const std::string& symbol)
|
||||
{
|
||||
LOG(md, Info, "ICEListener::add_filter_symbol: adding {}", symbol);
|
||||
_symbol_filter.insert(symbol);
|
||||
}
|
||||
|
||||
void ICEListener::heartbeat()
|
||||
{
|
||||
for (auto& c : _channels) { c.heartbeat(); }
|
||||
}
|
||||
|
||||
//
|
||||
// Private functions
|
||||
void ICEListener::add_security(const FixedSecurity& security)
|
||||
{
|
||||
// Set up the shared memory for this security
|
||||
auto handle = _producer.add_security(security);
|
||||
bts_assert(security.numeric_id != 0);
|
||||
// Add it to the index so it can receive messages
|
||||
_index.add_security(security.numeric_id, handle);
|
||||
}
|
||||
|
||||
void ICEListener::update_all_status(CoconutSecurityStatus status)
|
||||
{
|
||||
_index.walk_security_list([=](ICESecurityData& data)
|
||||
{
|
||||
data.handle.set_status(status);
|
||||
});
|
||||
}
|
||||
|
||||
}}} //namespace
|
||||
|
||||
|
||||
@@ -119,7 +119,20 @@ private:
|
||||
enum SecurityFilterAction { ADD, SKIP, DELETE };
|
||||
|
||||
std::string parse_contract_symbol(const std::string& sym);
|
||||
|
||||
template<typename Definition>
|
||||
std::string get_contract_symbol(const Definition& def){
|
||||
const auto& sym = def.contractSymbol;
|
||||
// TODO : this is only exception right now but for HHD sstrips
|
||||
// def.contractSymbol is empty !
|
||||
if (sym.size() == 0 && def.marketDesc.rfind("NG Swing GDD Futures - Henry", 0) == 0)
|
||||
return "HHD";
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
template<typename Definition>
|
||||
SecurityFilterAction filter_security(int exchange_id, const Definition& def)
|
||||
{
|
||||
@@ -130,7 +143,7 @@ private:
|
||||
}
|
||||
if(_symbol_filter.count("*") > 0)
|
||||
return ADD;
|
||||
return _symbol_filter.count( parse_contract_symbol(def.contractSymbol) ) > 0 ? ADD : SKIP;
|
||||
return _symbol_filter.count(get_contract_symbol(def)) > 0 ? ADD : SKIP;
|
||||
}
|
||||
void flush_books();
|
||||
void update_all_status(co2::CoconutSecurityStatus status);
|
||||
@@ -146,7 +159,7 @@ template<typename Definition>
|
||||
void ICEListener::set_security_common(const Definition& def, FixedSecurity& fsec)
|
||||
{
|
||||
fsec.numeric_id = def.marketId;
|
||||
std::string sym = parse_contract_symbol(def.contractSymbol);
|
||||
std::string sym = get_contract_symbol(def);
|
||||
if(!sym.empty())
|
||||
{
|
||||
fsec.symbol( sym );
|
||||
@@ -165,7 +178,7 @@ void ICEListener::set_security_common(const Definition& def, FixedSecurity& fsec
|
||||
fsec.ice_settle_denominator = (int8_t)(def.settlePriceDenominator - '0');
|
||||
double min_tick = double_of_price(def.incrementPrice, fsec.ice_price_denominator);
|
||||
fsec.low_tick = fsec.high_tick = min_tick;
|
||||
fsec.price_break = 0.0;
|
||||
fsec.price_break = 0.0;
|
||||
|
||||
fsec.ice_market_description(def.marketDesc);
|
||||
fsec.ice_cleared_alias(def.clearedAlias);
|
||||
@@ -207,12 +220,12 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
|
||||
// auto expiration = date_of_maturity(sec);
|
||||
// if (expiration < datetime::today()) return;
|
||||
|
||||
FixedSpreadSecurityL fsec;
|
||||
FixedSpreadSecurityXL fsec;
|
||||
set_security_common(def, fsec);
|
||||
fsec.alt_symbol(def.strategySymbol);
|
||||
|
||||
const auto& legs = def.legs;
|
||||
if (legs.size() > 0) {
|
||||
const auto& legs = def.legs;
|
||||
if (legs.size() > 0 && legs.size() <= 512) {
|
||||
fsec.set_leg_count(legs.size());
|
||||
for(size_t x = 0; x < fsec.leg_count(); ++x)
|
||||
{
|
||||
@@ -238,6 +251,8 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
|
||||
add_security(fsec);
|
||||
is_interested = true;
|
||||
}
|
||||
else
|
||||
LOG(md, Error, "Ignoring security {} with {} legs", def.marketDesc, legs.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -248,7 +263,7 @@ void ICEListener::on_future_strip(const FutureDefinition& def, bool& is_interest
|
||||
auto exchange_id = def.marketId;
|
||||
if (filter_security(exchange_id, def) != ADD) return;
|
||||
|
||||
FixedSpreadSecurityXS fsec;
|
||||
FixedSpreadSecurityXL fsec;
|
||||
set_security_common(def, fsec);
|
||||
fsec.alt_symbol(def.stripName);
|
||||
|
||||
|
||||
@@ -73,31 +73,17 @@ onixs_ice::MarketSubscriptions market_subcription_for_channel(const ICEChannel&
|
||||
onixs_ice::MarketSubscriptions subs;
|
||||
for(const auto& x : chan.market_types)
|
||||
{
|
||||
//onixs_ice::MarketSubType::NonImplied
|
||||
if(x == 26)
|
||||
{
|
||||
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(futures);
|
||||
}
|
||||
else
|
||||
{
|
||||
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::FullImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(futures);
|
||||
}
|
||||
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(futures);
|
||||
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
for(const auto& x : chan.uds_market_types)
|
||||
{
|
||||
if(x == 26)
|
||||
{
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
else
|
||||
{
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::FullImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
}
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
return subs;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ struct PositionServicePNLHelper::PImpl {
|
||||
void PositionServicePNLHelper::compute_pnl(
|
||||
const std::map<int, PositionUnit const*>& in_pos,
|
||||
const std::map<std::string,uint32_t>& account_map,
|
||||
const std::map<uint32_t,double>& cpp_override_map,
|
||||
std::vector<PNLUnit>& out_pnl,
|
||||
std::vector<AccountPNL>& out_account_pnl){
|
||||
out_account_pnl.clear();
|
||||
@@ -145,6 +146,8 @@ void PositionServicePNLHelper::compute_pnl(
|
||||
const PositionUnit& pu = *(p.second);
|
||||
uint32_t sec_key = pu.security_id();
|
||||
double cpp = me->handle_index.get_cpp(SecurityKey(sec_key));
|
||||
auto it = cpp_override_map.find(sec_key);
|
||||
cpp = (it != cpp_override_map.end()) ? it->second : cpp;
|
||||
double bid, ask, last, settle;
|
||||
me->handle_index.get_md(SecurityKey(sec_key),bid,ask,last,settle);
|
||||
PNLUnit pnlu;
|
||||
|
||||
@@ -17,7 +17,7 @@ private:
|
||||
std::unique_ptr<PImpl> me;
|
||||
private:
|
||||
double _compute_day_pnl (const bts::api::position::PNLUnit&);
|
||||
double _get_cpp (uint32_t);
|
||||
//double _get_cpp (uint32_t);
|
||||
// aggregates [sec, account]->pnl tp [account]->pnl
|
||||
void _aggregate_account_pnl(
|
||||
const std::vector<api::position::PNLUnit>& in_pnl,
|
||||
@@ -29,6 +29,7 @@ private:
|
||||
~PositionServicePNLHelper();
|
||||
void compute_pnl(const std::map<int, bts::api::position::PositionUnit const*>& in_pos,
|
||||
const std::map<std::string,uint32_t>& account_map,
|
||||
const std::map<uint32_t,double>& cpp_override_map,
|
||||
std::vector<bts::api::position::PNLUnit>& out_pnl,
|
||||
std::vector<bts::api::position::AccountPNL>& out_account_pnl);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ using namespace bts::api::position;
|
||||
using namespace bts::md;
|
||||
|
||||
static auto const manual_fills_ns = "manual_fills";
|
||||
static auto const security_params_ns = "security_parameters";
|
||||
|
||||
struct PositionServiceServer::PImpl {
|
||||
bts::orderman::PositionMap state;
|
||||
@@ -37,6 +38,7 @@ struct PositionServiceServer::PImpl {
|
||||
int id;
|
||||
// manual fills management
|
||||
std::unique_ptr<persistence::IStore> manual_fills_store;
|
||||
std::unique_ptr<persistence::IStore> security_params_store;
|
||||
PositionServiceBase::Manual_fillsStatefulPublisher manual_fills_publisher;
|
||||
// pnl
|
||||
PositionServiceBase::Pnl_dataStatefulPublisher pnl_publisher;
|
||||
@@ -47,6 +49,7 @@ struct PositionServiceServer::PImpl {
|
||||
bts::api::admin::AdminService admin_service;
|
||||
api::admin::AdminData admin_data;
|
||||
std::map<std::string, uint32_t> account_map;
|
||||
std::map<uint32_t, double> cpp_override_map;
|
||||
|
||||
PImpl(
|
||||
bts::api::ServiceContext &context,
|
||||
@@ -64,6 +67,8 @@ struct PositionServiceServer::PImpl {
|
||||
// manual fills
|
||||
manual_fills_store(
|
||||
std::make_unique<persistence::SqliteStore>("manual_fills")),
|
||||
security_params_store(
|
||||
std::make_unique<persistence::SqliteStore>("security_parameters")),
|
||||
manual_fills_publisher(manual_fills_publisher),
|
||||
// pnl
|
||||
pnl_publisher(pnl_publisher),
|
||||
@@ -73,7 +78,9 @@ struct PositionServiceServer::PImpl {
|
||||
// admin
|
||||
admin_service(context.locate<bts::api::admin::AdminService>()),
|
||||
admin_data(),
|
||||
account_map() {}
|
||||
account_map(),
|
||||
cpp_override_map()
|
||||
{}
|
||||
};
|
||||
|
||||
PositionServiceServer::PositionServiceServer(bts::api::ServiceContext &context,
|
||||
@@ -196,6 +203,27 @@ void PositionServiceServer::_clear_and_publish_pos(){
|
||||
me->pos_publisher.add_or_update(*p.second);
|
||||
me->pos_publisher.flush();
|
||||
}
|
||||
|
||||
void PositionServiceServer::_load_security_params(){
|
||||
me->cpp_override_map.clear();
|
||||
auto all = me->security_params_store->retrieve_all()[security_params_ns];
|
||||
SecurityParameters rec;
|
||||
for (auto const &kvp : all)
|
||||
if (utils::pb::from_string<SecurityParameters>(kvp.second, &rec)){
|
||||
if(rec.has_cpp_override()){
|
||||
LOG(OrderMan, Info, "Overriding cpp, key:{} cpp:{}",
|
||||
rec.security_key(),
|
||||
rec.cpp_override().value());
|
||||
me->cpp_override_map[rec.security_key()] = rec.cpp_override().value();
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG(OrderMan, Error,
|
||||
"Could not recover security parameter for security {} from storage. "
|
||||
"This should be investigated",
|
||||
kvp.first);
|
||||
}
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Manual Fills API //////////////////////////////////////////////////////////
|
||||
void PositionServiceServer::create_manual_fill(
|
||||
@@ -294,6 +322,30 @@ void PositionServiceServer::set_base_position(
|
||||
_clear_and_publish_pos();
|
||||
}
|
||||
|
||||
void PositionServiceServer::update_security_parameters(
|
||||
const SecurityParametersBatchOp& req,
|
||||
const api::Response<SecurityParametersBatchOpResponse>&){
|
||||
LOG(persist, Info, "PositionServiceServer::update_security_parameters bp count:{}",
|
||||
req.op_params_size());
|
||||
for (const auto& p : req.op_params()){
|
||||
std::string key_str = std::to_string(p.security_key());
|
||||
auto db_p = me->security_params_store->retrieve_one(security_params_ns, key_str);
|
||||
auto found = db_p.first;
|
||||
if (!found) {
|
||||
me->security_params_store->insert(security_params_ns,
|
||||
key_str,
|
||||
utils::pb::to_string(p));
|
||||
}
|
||||
else {
|
||||
me->security_params_store->insert(security_params_ns,
|
||||
key_str,
|
||||
utils::pb::to_string(p));
|
||||
}
|
||||
}
|
||||
_load_security_params();
|
||||
_compute_pnl_and_publish();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// PNL helpers ///////////////////////////////////////////////////////////////
|
||||
void PositionServiceServer::_compute_pnl_and_publish() {
|
||||
@@ -301,7 +353,8 @@ void PositionServiceServer::_compute_pnl_and_publish() {
|
||||
me->state.get_pos(in_pos);
|
||||
std::vector<PNLUnit> pnl;
|
||||
std::vector<AccountPNL> account_pnl;
|
||||
me->pnl_helper.compute_pnl(in_pos, me->account_map, pnl, account_pnl);
|
||||
me->pnl_helper.compute_pnl(in_pos, me->account_map, me->cpp_override_map,
|
||||
pnl, account_pnl);
|
||||
for (const auto &u : pnl)
|
||||
me->pnl_publisher.add_or_update(u);
|
||||
for (const auto &u : account_pnl)
|
||||
|
||||
@@ -52,6 +52,7 @@ class PositionServiceServer : public bts::api::position::PositionServiceBase {
|
||||
void _compute_pnl_and_publish();
|
||||
void _set_admin_data(const api::admin::AdminData &admin_data);
|
||||
void _clear_and_publish_pos();
|
||||
void _load_security_params();
|
||||
public:
|
||||
PositionServiceServer(bts::api::ServiceContext &context, md::MDClient &);
|
||||
|
||||
@@ -74,6 +75,11 @@ class PositionServiceServer : public bts::api::position::PositionServiceBase {
|
||||
const ::bts::api::position::BasePositionBatch &request,
|
||||
const api::Response<::bts::api::position::SetBasePositionResponse>
|
||||
&reply) override;
|
||||
|
||||
void update_security_parameters(
|
||||
const ::bts::api::position::SecurityParametersBatchOp &request,
|
||||
const api::Response<::bts::api::position::SecurityParametersBatchOpResponse>
|
||||
&reply) override;
|
||||
|
||||
~PositionServiceServer();
|
||||
};
|
||||
|
||||
+1
-10
@@ -2,11 +2,6 @@
|
||||
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
|
||||
@@ -39,9 +34,6 @@ 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
|
||||
@@ -79,11 +71,10 @@ 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
|
||||
dotnet restore -s ../../../../externals/nuget/packages
|
||||
if [ "$MODE" == "release" ]; then
|
||||
dotnet fable webpack -- --env.BUILD_PATH=$OUT -p
|
||||
else
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
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)
|
||||
|
||||
|
||||
@@ -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 spark_client.sln /p:Configuration=release
|
||||
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
|
||||
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
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#!/bin/bash
|
||||
BUILD=clang_debug
|
||||
BUILD_LOC=/home/ppinto/temp/spark_build
|
||||
|
||||
if [ -f /.dockerenv ]; then
|
||||
cd $BUILD_LOC/$BUILD && ninja
|
||||
(cd `readlink -f .` && ninja)
|
||||
else
|
||||
cid=`docker ps | grep -P "emacs_ide|bluetradingsystems/linux_docker" | awk '{print $1}'`
|
||||
docker exec $cid /bin/bash -c "cd $BUILD_LOC/$BUILD && ninja"
|
||||
docker exec $cid /bin/bash -c "(cd `readlink -f .` && ninja)"
|
||||
fi
|
||||
@@ -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 spark_client.sln
|
||||
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore spark_client.sln
|
||||
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
|
||||
|
||||
@@ -4,6 +4,11 @@ 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
|
||||
|
||||
Regular → Executable
File mode changed.
Reference in new issue
Block a user