Compare commits

...
51 Commits
Author SHA1 Message Date
Mohammad Arham 8bfc2d40f9 Update: V2.2.4 pnl working now 2025-02-08 12:43:13 -05:00
Taha c09fd71c94 Fixing PNL for spots 2025-02-08 06:55:07 -05:00
Mohammad Arham f3b037553e Add: Batch subs in binance sub 2025-02-08 06:44:10 +00:00
Taha Afzal d386d93166 SpreaderChildOrder::on_order_udpate, changing an asserrt to an error lgo to monitor condition 2025-02-07 15:06:16 +00:00
Mohammad Arham 5217742c71 Add: SendingTime tag to cboe execution 2025-02-07 12:26:11 +00:00
Taha c59ec95460 Sheets and spreadbox theos can now optionally be set to market mid 2025-02-06 17:42:12 -05:00
Mohammad Arham 70f01583ec ADD: CBOE TIME and SALES working 2025-02-06 20:42:58 +00:00
Mohammad Arham 66d2f9eb63 Add: Quote algo updates, theos removed 2025-02-06 03:08:46 +00:00
John Colvin 4625bed8b8 Change: Update version to v2.2.3-1 2025-02-05 17:01:11 -05:00
John Colvin f18fb332b5 fix (orderbook): Make ColumnKeys enum 64bit
We rely on bit flags to indicate "dirty" columns in the vm. Need to do
this now that we have > 31 columns.
2025-02-05 17:01:11 -05:00
John Colvin 8746a6f5ca fix (orderbook): Refresh Notional Value view when it changes in vm 2025-02-05 16:30:13 -05:00
John Colvin 702d47abb5 fix (orderbook): Fix typos in default settings for Notional Value 2025-02-05 16:29:29 -05:00
John Colvin bf2a54717c ui (orderbook): Add Filled Notional Value column 2025-02-05 16:13:34 -05:00
John Colvin 9db779fb25 fix (hack): Assume synth quantities are always notional values 2025-02-05 16:07:07 -05:00
Mohammad Arham f55ddec708 Add: Vesrion logs to bothserver and client 2025-02-05 09:50:33 -05:00
Mohammad Arham 6e942dd2d4 Change: Updated version to v2.2.3 2025-02-05 08:38:31 -05:00
Mohammad Arham 142310a17a Add: SPXW to default for cboe 2025-02-05 12:16:47 +00:00
John Colvin 7f99a89a95 ui (fills): Add notional value column 2025-02-05 00:00:41 -05:00
John Colvin bfce663b4b fix (orderbook): Hook up notional val filter in vm, persist settings 2025-02-04 23:42:58 -05:00
John Colvin 33c743643d fix (orderbook): Include fractional qty in notional val 2025-02-04 23:34:47 -05:00
Taha Afzal 858e0a389b Fixed the issue where a notioanal quoter would send a 0 qty order 2025-02-05 04:16:53 +00:00
Taha Afzal 9c3bb85674 Notional qty handling in spreader 2025-02-05 04:16:53 +00:00
Taha Afzal f4930e8cd4 Added notional to PriceCalc type for the sprader, added code to compute notional books in SpreaderUtils 2025-02-05 04:16:53 +00:00
John Colvin f21e7f3602 ui (orderbook): Show synth notional value in terms of first leg 2025-02-04 16:54:37 -05:00
John Colvin b56c35aca4 ui (ladder): Show synth notional value in terms of first leg 2025-02-04 16:54:37 -05:00
John Colvin e6ac83196e fix (benign): Actually dispose _vm_sub in LadderGridHost 2025-02-04 16:54:37 -05:00
John Colvin 7b6f74fc4a cleanup: Get rid of 'mimic tt' ladder config
This config was leftover from custom DJ work. We no longer need it.
2025-02-04 16:54:37 -05:00
John Colvin f754953fac cleanup: Remove duplicate equality check 2025-02-04 16:54:37 -05:00
Taha 2dd1143d9f Fixed spx spreadbox theos 2025-02-04 16:31:17 -05:00
Mohammad Arham e1ee3275d8 Add: Filter secs upto n months databento 2025-01-31 21:46:11 +00:00
John Colvin 2549e406f3 fix: Compute notional value correctly when ticks per row > 1
It's the sum of price * qty for each level in the row. Previously, we
were multiplying the total qty by the same price (smallest in the row).
2025-01-29 16:28:28 -05:00
John Colvin d5024f1858 fix build: Don't check if unsigned int < 0 2025-01-28 20:20:06 +00:00
John Colvin 3623ac96db fix: Multiply array size by sizeof(T) 2025-01-28 20:19:51 +00:00
John Colvin 19834625f9 fix: Don't fall through switch case 2025-01-28 19:59:32 +00:00
John Colvin c8fc649b6b fix build: Pass config arg to cmake --build
For Ninja Multi-Config builds (i.e., api server and data streams)
2025-01-28 19:59:09 +00:00
John Colvin 6b1fc1c413 cleanup: Remove unused code 2025-01-28 19:59:09 +00:00
John Colvin 6f086532ac cleanup: Convert co2::TradeMessage in DomainSecurityUtils
Instead of domain::TradeRecord constructor. The domain types should have
minimal dependency on protos.
2025-01-28 19:59:09 +00:00
John Colvin dc66282225 cleanup: Rename UINT32 -> VALUE in qty macro names
Should have done this when we changed qty primitive from uint32 to float
2025-01-28 19:59:09 +00:00
John Colvin e2c74c0bb3 cleanup: Don't redefine CONTAINS macro 2025-01-28 19:59:09 +00:00
John Colvin e89d50eec1 cleanup: Move large fns from header to cpp 2025-01-28 19:59:08 +00:00
John Colvin 8bebbc278d cleanup: Get rid of 'using namespaces'
In headers, this is an objectively bad practice, since the type aliases
leak into dependent code and can cause unexpected naming conflicts. In
source files, it is debatable, but I prefer explicit aliases of the form
'using Foo = Bar'. The intentions are clear, and the resulting code is
naturally resistent to naming conflicts.
2025-01-28 19:59:08 +00:00
John Colvin 2e6d12c9c1 cleanup: Remove unnecessary includes 2025-01-28 19:59:08 +00:00
John Colvin 102a5e23ea vscode: Add Tests Sparkd entries to tasks.json 2025-01-28 19:59:08 +00:00
John Colvin 582dc791db vscode: Add Run Sparkd (Skip Build) entry to tasks.json 2025-01-28 19:59:08 +00:00
John Colvin afb35d8c25 Compute synth book qty in terms of first leg 2025-01-28 19:59:08 +00:00
John Colvin 3a32ed5fe6 Fix BookSide equality operators 2025-01-27 16:35:22 +00:00
John Colvin 4e01697327 Add equality and output operators to BookLevel 2025-01-27 16:25:32 +00:00
John Colvin 22032c1742 Add fmt::formatter for domain::Side
So we can pass it to LOG macros, etc.
2025-01-27 16:25:15 +00:00
John Colvin 0a9bce317c Add contract qty info to security proto (and FixedSecurity)
We should get this kind of security metadata from the exchange rather
than config.
2025-01-25 20:41:47 +00:00
Mohammad Arham 40e44db597 Fix: Added GCHandle in attempt to fix ui crash 2025-01-22 02:15:55 -05:00
Mohammad Arham 4a447e44e1 Add: Version to first log 2025-01-22 02:14:19 -05:00
165 changed files with 3394 additions and 2221 deletions

No files matched your search

+62
View File
@@ -699,6 +699,24 @@
"color": "terminal.ansiGreen",
},
},
{
"label": "Run Sparkd (Skip Build)",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"run"
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "run",
"color": "terminal.ansiGreen",
},
},
{
"label": "Run Sparkd Config Query",
"dependsOn": "Build Sparkd",
@@ -740,6 +758,45 @@
}
},
},
{
"label": "Test Sparkd",
"dependsOn": "Build Sparkd",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"test",
"${input:sparkdTest}",
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "coverage",
"color": "terminal.ansiYellow",
},
},
{
"label": "Test Sparkd (Skip Build)",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"test",
"${input:sparkdTest}",
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "coverage",
"color": "terminal.ansiYellow",
},
},
{
"label": "View DataStreamQueryAnalyzer Logs (Last Run)",
"type": "process",
@@ -864,5 +921,10 @@
"type": "promptString",
"description": "Test to run",
},
{
"id": "sparkdTest",
"type": "promptString",
"description": "Test to run",
},
]
}
+43
View File
@@ -8,6 +8,7 @@ using MathNet.Numerics.Providers.FourierTransform;
using BCG;
using System.Windows.Automation;
using bts.collections;
using mbts.co2;
namespace mbts.spark {
// Publishes theos for all options (on the side, also publishes the theos for TC2).
@@ -114,11 +115,53 @@ namespace mbts.spark {
}
sym_theos.set_data(k, tp);
}
_override_theos(ref rv);
foreach (var kv in rv) {
_bb.post(SparkTags.TC2Tags.LiveTheosTag(kv.Key), kv.Value);
}
}
private void _override_theos(ref Dictionary<(string, Venue), ExpStrikeKeyedData<TheoPoint>> theos) {
if (_compute_midmarket) {
SystemContractData sc_data = _bb.read_state(SparkTags.SystemContractsTag);
foreach (var kv in theos) {
string sym = kv.Key.Item1;
Venue exchange = kv.Key.Item2;
foreach (var t in kv.Value.get_data_linearly()) {
DateTime exp = t.first;
double strike = t.second;
TheoPoint tp = t.third;
var call_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Call);
{
NativePriceSize call_bid, call_ask;
ulong time = 0;
call_sec_md.Value.md.get_bbo(ref time, out call_bid, out call_ask);
double call_mid = (call_bid.price + call_ask.price) / 2;
tp.call = call_mid;
}
{
var put_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Put);
NativePriceSize put_bid, put_ask;
ulong time = 0;
put_sec_md.Value.md.get_bbo(ref time, out put_bid, out put_ask);
double put_mid = (put_bid.price + put_ask.price) / 2;
tp.put = put_mid;
}
}
}
//replace theos with market bid
// get it from the bus and override
} else {
// do nothing, passes unchanged
}
}
private bool _compute_midmarket {
get {
return true;
}
}
private void _interp_and_publish() {
var timer = Stopwatch.StartNew();
+11
View File
@@ -281,5 +281,16 @@ namespace mbts.spark {
else
return tp.put + (tp.pdelta * driver_diff) + (0.5 * tp.pgamma * driver_diff * driver_diff);
}
public static TheoPoint extrapolate(TheoPoint tp, double driver, bool is_call) {
TheoPoint rv = tp.Clone() as TheoPoint;
double h = driver - tp.driver;
if (is_call) {
rv.call = tp.call + h * (tp.cdelta);
} else {
rv.put = tp.put + h * (tp.cdelta);
}
return rv;
}
}
}
@@ -13,7 +13,7 @@ namespace mbts.analytics {
// prices (specified in the vs settings).
// It has public setters that get
// 1. contracts
// 2. vs inout
// 2. vs input
// 3. exp params(drivers, interst rate etc)
// 4. driver prices
// 5. vol time
@@ -237,6 +237,17 @@ namespace mbts.spark {
DefaultColumnWidths.Qty,
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
add_col(
Columns.NotionalValue,
DefaultColumnHeaders.NotionalValue,
r => _get_row(r).security.spec_or_default().format(r.fill.notional_value()),
new DoubleMinMax<FillTuple>(r => r.fill.notional_value()),
DataGridFilterKnownTemplates.DoubleMinMax,
fs => fs.notional_value,
(fs, cf) => fs.notional_value = cf,
DefaultColumnWidths.NotionalValue,
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
add_col(
Columns.Price,
DefaultColumnHeaders.Price,
@@ -21,6 +21,7 @@ namespace mbts.ui.spark {
Side,
Price,
Size,
NotionalValue,
FillKind,
FillStatus,
FillKey,
@@ -61,6 +61,10 @@ namespace bts.shell.fills {
[DIYGridColumnID(nameof(Columns.Size))]
public DoubleMinMaxState size { get; set; }
[ProtoMember(18)]
[DIYGridColumnID(nameof(Columns.NotionalValue))]
public DoubleMinMaxState notional_value { get; set; }
[ProtoMember(11)]
[DIYGridColumnID(nameof(Columns.FillKey))]
public string fill_key { get; set; }
@@ -113,6 +117,7 @@ namespace bts.shell.fills {
pb.add_discrete_values(fs.fill_status, ft => ft.fill.FillStatus);
// numeric
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
pb.add_numeric(fs.notional_value, ft => ft.fill.notional_value());
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
if (fs.exp_date.HasValue) {
@@ -408,18 +408,18 @@ namespace mbts.spark {
[ProtoContract]
class LadderPanelPricePointSettings : IEquatable<LadderPanelPricePointSettings> {
public static LadderPanelPricePointSettings historic_default =>
public static LadderPanelPricePointSettings Default =>
new LadderPanelPricePointSettings {
day_change = new DaySettings {
// (neutral)
font = _default_font,
background_color = Colors.Transparent.to_uint(),
font = _default_font.With(font_color: Colors.Black.to_uint()),
background_color = Color.FromRgb(113, 201, 69).to_uint(),
// negative
negative_font_color = Colors.White.to_uint(),
negative_background_color = Colors.Red.to_uint(),
// positive
positive_font_color = Colors.White.to_uint(),
positive_background_color = Colors.Green.to_uint()
positive_background_color = Colors.Green.to_uint(),
},
session_low = _default_item.With(_default_item.font.With(font_color: BTSColors.MarketData.session_low)),
session_high = _default_item.With(_default_item.font.With(font_color: BTSColors.MarketData.session_high)),
@@ -431,21 +431,9 @@ namespace mbts.spark {
theo = _default_item,
delta = _default_item,
vega = _default_item,
volume = _default_item
volume = _default_item,
};
public static LadderPanelPricePointSettings tt_default {
get {
var rv = historic_default;
rv.day_change.background_color = Color.FromRgb(113, 201, 69).to_uint();
rv.day_change.font.font_color = Colors.Black.to_uint();
return rv;
}
}
public static LadderPanelPricePointSettings Default => LadderSettings.mimic_tt ?
tt_default : historic_default;
[ProtoMember(1)]
public DaySettings day_change { get; set; }
[ProtoMember(2)]
@@ -607,11 +595,6 @@ namespace mbts.spark {
uint _ticks_per_row;
List<FairValueLine> _fair_value_lines;
static LadderSettings() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
mimic_tt = knobs.MimicTt;
}
// Default proto constructor
// Added this so that when clients upgrade to a version with new settings,
// the default values are applied correctly; thx to John for this tip
@@ -643,9 +626,7 @@ namespace mbts.spark {
status_message_visible = true;
}
public static bool mimic_tt { get; }
public static LadderSettings historic_default {
public static LadderSettings Default {
get {
LadderColumnSettings
orders_column = LadderColumnSettings.default_orders,
@@ -713,11 +694,6 @@ namespace mbts.spark {
}
}
public static LadderSettings tt_default => historic_default;
public static LadderSettings Default => mimic_tt ?
tt_default : historic_default;
public enum BadgeStyle { Normal, Alternate }
// TODO: rename to PriceDisplayFormat (today is merge monday, since this isn't a functional change, it is not worth the potential merge headaches right now)
@@ -1039,7 +1015,6 @@ namespace mbts.spark {
a.ladder_order_limits.safe_equals(b.ladder_order_limits) &&
a._ticks_per_row == b._ticks_per_row &&
a.panel_price_point_settings.safe_equals(b.panel_price_point_settings) &&
a._ticks_per_row == b._ticks_per_row &&
EqualityComparer.safe_sequence_equals(a.fair_value_lines, b.fair_value_lines);
public bool Equals(LadderSettings other) =>
@@ -107,6 +107,7 @@ namespace mbts.spark {
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoMetadata> _favorite_algo_metadata_map =
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoMetadata>.Empty;
RegionalTickFactors? _spec;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>(0);
LadderVM(
IBus bus,
@@ -329,7 +330,8 @@ namespace mbts.spark {
}),
WorkLoop.Start($"Ladder '{security}'", _tick),
ObservableAutoWrapper.dispose_on_removed(orders, true),
Disposable.Create(() => _on_disposed_impl?.Invoke(true)));
Disposable.Create(() => _on_disposed_impl?.Invoke(true)),
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(secs => _securities = secs));
PropertyAutoWrapper<LadderSettings, bool> bool_prop(
string name, Func<LadderSettings, bool> get_bool) =>
@@ -405,7 +407,9 @@ namespace mbts.spark {
event Action<bool>? _on_disposed_impl;
public IMDConsumerHandle md => _model.md;
public SecurityMD security_md => _model.security_md;
public IBus bus => _bus;
public IReadOnlyDictionary<uint, SecurityMD> securities => _securities;
// we return only entries who have piqs and whose piqs are not uint.Max
// uint.Max happens when the server is restarted but the client is still up
@@ -17,7 +17,6 @@ using Spark.Core.Disposables;
using Spark.Core.Math;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -38,9 +37,6 @@ namespace mbts.spark {
partial class LadderGridHost : Control {
static readonly int _initial_row_count;
static readonly bool _show_scrollbar;
static readonly bool _lighten_market_cells;
static readonly Color? _bid_market_cell_background;
static readonly Color? _ask_market_cell_background;
readonly VisualHost _visual_host = new();
readonly LadderOrdersHost _ordershost;
@@ -84,6 +80,7 @@ namespace mbts.spark {
LadderTactileAdorner _tactile_adorner;
ulong _prices_updated;
NativePriceSize[] _bids = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH], _asks = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
SecurityMDExtensions.Book _depth = SecurityMDExtensions.Book.Empty;
bool _price_dirty = false, _piq_dirty = false;
LadderPriceList _price_list = new() {
length = _initial_row_count,
@@ -96,6 +93,8 @@ namespace mbts.spark {
Func<NativePriceSize, string> _fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
Func<double, double, string> _fmt_mkt_quantity_impl = (v, p) => v.ToString();
Func<LadderPriceLevel, string> _fmt_price_level_impl = MDDisplayHelper.get_formatter_lpl(
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
int _old_tooltip_row = int.MaxValue;
NativePriceSize? _old_last_trade;
string _direction_arrow = string.Empty;
@@ -107,17 +106,7 @@ namespace mbts.spark {
static LadderGridHost() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
_show_scrollbar = knobs.ShowScrollbar;
_lighten_market_cells = knobs.LightenMarketCells;
_initial_row_count = (int)knobs.ScrollableTickWindowSize;
// For market cell backgrounds, 0 means null. Note that the full
// range of colors can still be expressed, as zero is merely one of
// 2^24 possible representations of 'transparent.'
if (knobs.BidMarketCellBackground != 0) {
_bid_market_cell_background = BTSColors.Ladder.bid_column_background;
}
if (knobs.AskMarketCellBackground != 0) {
_ask_market_cell_background = BTSColors.Ladder.ask_column_background;
}
}
public LadderGridHost() {
@@ -872,13 +861,13 @@ namespace mbts.spark {
switch (column.column_type) {
case ColumnType.Order: _ordershost.set_col_width(column.settings.width); break;
case ColumnType.Bid:
render_market_col(_bids, _bid_market_cell_background);
render_market_col(_depth.Bids);
break;
case ColumnType.Price:
_refresh_prices(column);
break;
case ColumnType.Ask:
render_market_col(_asks, _ask_market_cell_background);
render_market_col(_depth.Asks);
break;
case ColumnType.Volume:
_volume_container.mark_dirty();
@@ -891,9 +880,9 @@ namespace mbts.spark {
if (_mkt_break_lines.high_render_index.HasValue)
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
}
void render_market_col(NativePriceSize[] depth, Color? market_cell_background) {
void render_market_col(IEnumerable<(double Price, double Volume, double NotionalValue)> depth) {
var mid = LadderRender.index_of(_price_list, _last_origin_price, _price_comparer);
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_mkt_depth_item, _lighten_market_cells, market_cell_background);
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_price_level);
}
}
_apply_column_visual_offset();
@@ -939,6 +928,7 @@ namespace mbts.spark {
// **use local vm to protect against re-assignment,
// so the events are unhooked from the same instance to which they were added
// when the subs are disposed.
Disposable.dispose(ref _vm_sub);
var local_vm = _vm;
local_vm.on_tick += _tick;
local_vm.request_recenter += on_request_recenter;
@@ -1110,6 +1100,7 @@ namespace mbts.spark {
ulong unused = 0;
bool last_trade_changed = _vm.md.get_last_trade(ref unused, out _last_trade, out DateTime _, out ulong __);
bool depth_changed = _vm.md.get_depth(ref _prices_updated, ref _bids, ref _asks, NativePriceHelpers.COCONUT_MAX_DEPTH);
_depth = _vm.security_md.Depth(_bids, _asks, _vm.securities);
_mkt_break_lines.refresh();
if (_price_list is null) {
Log.assert(_price_dirty);
@@ -1322,8 +1313,8 @@ namespace mbts.spark {
void _refresh_sizes() {
Log.assert_d(_price_list != null);
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, _spec, _settings.bid_column, _fmt_mkt_depth_item, _lighten_market_cells, _bid_market_cell_background);
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, _spec, _settings.ask_column, _fmt_mkt_depth_item, _lighten_market_cells, _ask_market_cell_background);
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _depth.Bids, _settings.row_height, _spec, _settings.bid_column, _fmt_price_level);
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _depth.Asks, _settings.row_height, _spec, _settings.ask_column, _fmt_price_level);
}
/// <summary>
@@ -1506,11 +1497,13 @@ namespace mbts.spark {
static GlyphContext glyph_context(LadderColumnSettings cs) =>
GlyphContext.create(cs.font_family, cs.font_weight);
var price_display = _spec.tick_factors(0).to_api_tick_display_settingsi();
var price_display = security.regional_ticks().tick_factors(0).to_api_tick_display_settingsi();
_fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
_settings.size_display, _qty_display, price_display);
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(
_settings.size_display, _qty_display, price_display);
_fmt_price_level_impl = MDDisplayHelper.get_formatter_lpl(
_settings.size_display, _qty_display, price_display);
_set_columns();
_apply_column_visual_offset();
if (prev_row_height != _settings.row_height)
@@ -1520,18 +1513,16 @@ namespace mbts.spark {
_mkt_break_lines.fair_value_line_views.Clear();
// Clear and re-add the columns to the viewer to ensure offset/visibility is correct
_visual_host.Clear();
if (LadderSettings.mimic_tt) {
// The session_range_vis sits beneath the grid backgrounds (on the
// z-axis) to avoid obfuscating the font color (dj wants it to be
// pure white). Historically, it sat atop the price column, with a
// low opacity to slightly darken the range of prices inside of the
// session range (i.e., the price-band of the day's trades). In
// conjuction with this change, the price column's background
// opacity is made slightly less than 1 (in the default settings),
// while the font color remains fully opaque, so this layer will
// darken the background without affecting the font color.
_visual_host.AddVisualChild(_session_range_vis);
}
// The session_range_vis sits beneath the grid backgrounds (on the
// z-axis) to avoid obfuscating the font color (dj wants it to be
// pure white). Historically, it sat atop the price column, with a
// low opacity to slightly darken the range of prices inside of the
// session range (i.e., the price-band of the day's trades). In
// conjuction with this change, the price column's background
// opacity is made slightly less than 1 (in the default settings),
// while the font color remains fully opaque, so this layer will
// darken the background without affecting the font color.
_visual_host.AddVisualChild(_session_range_vis);
// Renders the grid lines and column backgrounds
_visual_host.AddVisualChild(_gridvis);
// Orders column
@@ -1571,8 +1562,6 @@ namespace mbts.spark {
with_vis => _visual_host.RemoveVisualChild(with_vis.vis));
_visual_host.AddVisualChild(_session_low_vis);
_visual_host.AddVisualChild(_session_high_vis);
if (!LadderSettings.mimic_tt)
_visual_host.AddVisualChild(_session_range_vis);
_render_grid_background();
_init_tactile_adorner();
// this effectively sets the background of the "Last" column //deleted to fix color
@@ -1594,6 +1583,16 @@ namespace mbts.spark {
return impl(value);
}
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
string _fmt_price_level(LadderPriceLevel value) {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
var impl = _fmt_price_level_impl;
impl ??= MDDisplayHelper.get_formatter_lpl(
MarketSizeDisplay.get_default(), _qty_display, security.regional_ticks().tick_factors(0).to_api_tick_display_settingsi());
return impl(value);
}
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
string _fmt_mkt_quantity(double value, double price) {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
@@ -0,0 +1,98 @@
namespace mbts.spark;
using bts;
using bts.utils;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
readonly struct LadderPriceLevel : IEquatable<LadderPriceLevel> {
public LadderPriceLevel(double price = double.NaN, double volume = 0, double notionalValue = double.NaN) {
Price = price;
Volume = volume;
NotionalValue = notionalValue.is_nan() ? volume * price : notionalValue;
}
public static LadderPriceLevel Empty => new();
public double Price { get; }
public double Volume { get; }
public double NotionalValue { get; }
public LadderPriceLevel Add(double price, double volume) {
if (!(volume > 0)) {
return this;
}
if (!(Volume > 0)) {
return new(price, volume);
}
Log.assert(Price.is_real());
Log.assert(price.is_real());
var newVolume = Volume + volume;
var newPrice = (Price * Volume + price * volume) / newVolume;
return new(newPrice, newVolume);
}
public LadderPriceLevel Add(IEnumerable<(double Price, double Volume)> items) {
var (totalPrice, totalVolume) = items.Sum();
return totalVolume > 0
? Add(totalPrice / totalVolume, totalVolume)
: this;
}
public Builder ToBuilder() =>
new() { Cost = Price, Volume = Volume };
public LadderPriceLevel With(double price = double.NaN, double volume = double.NaN, double notionalValue = double.NaN) =>
new(price.if_nan(Price), volume.if_nan(Volume), notionalValue.if_nan(NotionalValue));
public bool Equals(LadderPriceLevel other) =>
MathUtils.epsilon_eq_comparer.Equals(Price, other.Price)
&& MathUtils.epsilon_eq_comparer.Equals(Volume, other.Volume);
public override bool Equals([NotNullWhen(true)] object obj) =>
obj is LadderPriceLevel other && Equals(other);
public override int GetHashCode() =>
HashCode.Combine(
MathUtils.epsilon_eq_comparer.GetHashCode(Price),
MathUtils.epsilon_eq_comparer.GetHashCode(Volume));
public class Builder {
double notionalValue = double.NaN;
public double Cost { get; set; } = double.NaN;
public double Volume { get; set; } = 0;
public double NotionalValue => notionalValue.is_nan() ? Volume * Cost : notionalValue;
public void Add(double price, double volume) {
if (!(volume > 0)) {
return;
}
Log.assert(price.is_real());
Cost += price * volume;
Volume += volume;
}
public void Add(double price, double volume, double notionalValue) {
if (!(volume > 0)) {
return;
}
Log.assert(price.is_real());
Cost += price * volume;
Volume += volume;
if (this.notionalValue.is_nan()) {
this.notionalValue = notionalValue;
} else{
this.notionalValue += notionalValue;
}
}
public LadderPriceLevel ToImmutable() =>
Volume > 0
? new(Cost / Volume, Volume, NotionalValue)
: Empty;
}
}
@@ -57,8 +57,7 @@ namespace mbts.spark {
static class LadderRender {
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
// (see how it is incorporated in measure_row_height() below)
public static Thickness row_padding => LadderSettings.mimic_tt ?
new Thickness(4, 2, 4, 2) : new Thickness(6);
public static Thickness row_padding => new Thickness(4, 2, 4, 2);
const string tick_separator_period = ".";
const string tick_separator_dash = "-";
@@ -214,70 +213,78 @@ namespace mbts.spark {
}
}
public static void RenderSizes(
DrawingVisual dv,
GlyphContext glyphContext,
int originIndex,
IList<double> prices,
IEnumerable<LadderPriceLevel> levels,
double rowHeight,
RegionalTickFactors spec,
LadderColumnSettings settings,
Func<LadderPriceLevel, string> fmt) {
var bg = settings.background.rgb_color().blend((BTSColor)0x2AFFFFFF).to_brush();
using (var dc = dv.RenderOpen())
using (_draw_with_clip(dc, settings, rowHeight, prices.Count))
using (var levelsEn = (levels ?? Enumerable.Empty<LadderPriceLevel>()).GetEnumerator()) {
var fg = settings.foreground.rgb_brush();
var currIdx = originIndex;
var currPrice = prices[currIdx];
while (levelsEn.MoveNext()) {
var level = levelsEn.Current;
var ticks = spec.count_ticks(level.Price);
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : level.Price;
// prices are in descending order, so get diff from curr_price - price
var tickDiff = spec.tickdiff(currPrice, price).assert_value();
if (!MathUtils.try_increment(ref currIdx, tickDiff) || currIdx < 0 || currIdx >= prices.Count) {
continue;
}
currPrice = price;
if (bg is not null) {
var y = currIdx * rowHeight + GRID_LINE_HEIGHT;
var height = rowHeight - GRID_LINE_HEIGHT;
dc.DrawRectangle(bg, null, new Rect(0, y, settings.width, height));
}
var gr = format_text(fmt(level), currIdx, rowHeight, settings, glyphContext);
dc.DrawGlyphRun(fg, gr);
}
}
}
public static IEnumerable<LadderPriceLevel> GetLevels(IEnumerable<(double Price, double Volume, double NotionalValue)> depth, RegionalTickFactors spec) =>
depth.TakeWhile(nps => nps.Volume > 0 && nps.Price.is_real())
.AggregateConsecutiveGroups(
LadderPriceLevel.Empty.ToBuilder,
(bd, rung) => {
bd.Add(rung.Price, rung.Volume, rung.NotionalValue);
return bd;
},
rung => {
var ticks = spec.count_ticks(rung.Price);
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.Price;
})
.Select(bd => bd.ToImmutable());
public static void render_sizes(
DrawingVisual dv,
GlyphContext glyph_context,
int origin_index,
IList<double> prices,
NativePriceSize[] rungs,
IEnumerable<(double Price, double Volume, double NotionalValue)> rungs,
double row_height,
RegionalTickFactors spec,
LadderColumnSettings settings,
Func<NativePriceSize, string> fmt,
bool lighten_market_cells,
Color? market_cell_background) {
//var background = new SolidColorBrush(settings.background.rgb_color());
Brush market_cell_background_brush;
if (market_cell_background.HasValue) {
market_cell_background_brush = new SolidColorBrush(market_cell_background.Value);
} else if (lighten_market_cells) {
market_cell_background_brush = settings.background.rgb_color()
.blend((BTSColor)0x2AFFFFFF)
.to_brush();
} else {
market_cell_background_brush = null;
}
// market_cell_background_brush = settings.background.rgb_color().blend((BTSColor)0xFF000000).to_brush();
using (var dc = dv.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, prices.Count * row_height));
if (rungs.is_null_or_empty()) {
return;
}
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
NativePriceSize curr_size = NativePriceSize.NO_MARKET;
var row_tick_groups = rungs
.TakeWhile(nps => !nps.is_nan)
.AggregateConsecutiveGroups(() => NativePriceSize.NO_MARKET,
(aggr, rung) => rung.with(
direct_size: aggr.direct_size + rung.direct_size,
implied_size: aggr.implied_size + rung.implied_size),
rung => {
var ticks = spec.count_ticks(rung.price);
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
});
foreach (var rung in row_tick_groups) {
//double price = spec.round_to_tick(rung.price);
var ticks = spec.count_ticks(rung.price);
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
// prices are in descending order, so get diff from curr_price - price
var tick_diff = spec.tickdiff(curr_price, price).assert_value();
if (!MathUtils.try_increment(ref curr_index, tick_diff)) {
continue;
}
curr_price = price;
if (curr_index >= 0 && curr_index < prices.Count) {
if (market_cell_background_brush != null)
dc.DrawRectangle(market_cell_background_brush, null,
new Rect(0, curr_index * row_height + GRID_LINE_HEIGHT,
settings.width, row_height - GRID_LINE_HEIGHT));
var gr = format_text(fmt(rung), curr_index, row_height, settings, glyph_context);
dc.DrawGlyphRun(foreground, gr);
} else break;
}
}
Func<LadderPriceLevel, string> fmt) {
RenderSizes(
dv,
glyph_context,
origin_index,
prices,
GetLevels(rungs, spec),
row_height,
spec,
settings,
fmt);
}
public static void render_volumes(
@@ -182,7 +182,7 @@
</Style>
<Style x:Key="CancelAll" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Grid.Row" Value="2" />
<Setter Property="Grid.Row" Value="0"/>
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center">Cancel All</TextBlock>
@@ -220,10 +220,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TT_CancelAll" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="0"/>
</Style>
<Style x:Key="TT_CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Content">
<Setter.Value>
@@ -231,7 +228,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TT_CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="2" />
<Setter Property="Content">
<Setter.Value>
@@ -239,83 +236,6 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{Binding bid_foreground}">
Cancel Buys
</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{Binding bid_background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
<Setter
TargetName="Border"
Property="BorderBrush"
Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}" />
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{Binding ask_foreground}">
Cancel Sells
</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{Binding ask_background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True" >
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
<Setter
TargetName="Border"
Property="BorderBrush"
Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SettingsButton" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Content">
<Setter.Value>
@@ -1177,19 +1097,19 @@
<Button
Command="{Binding cancel_cmd}"
CommandParameter="Bids"
Style="{StaticResource {x:Static sp:LadderView.CancelBuysButtonStyleKey}}"
Style="{StaticResource CancelBuys}"
Visibility="{Binding
show_cancel_buys, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button
Command="{Binding cancel_cmd}"
CommandParameter="Asks"
Style="{StaticResource {x:Static sp:LadderView.CancelSellsButtonStyleKey}}"
Style="{StaticResource CancelSells}"
Visibility="{Binding
show_cancel_sells, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button
Command="{Binding cancel_cmd}"
CommandParameter="All"
Style="{StaticResource {x:Static sp:LadderView.CancelAllButtonStyleKey}}"
Style="{StaticResource CancelAll}"
Visibility="{Binding
show_cancel_all, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
@@ -27,22 +27,6 @@ namespace mbts.spark {
MinZoomFactor = 0.5,
MaxZoomFactor = 1.5;
static LadderView() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
MimicTT = knobs.MimicTt;
}
public static bool MimicTT { get; }
public static string CancelBuysButtonStyleKey =>
MimicTT ? "TT_CancelBuys" : "CancelBuys";
public static string CancelSellsButtonStyleKey =>
MimicTT ? "TT_CancelSells" : "CancelSells";
public static string CancelAllButtonStyleKey =>
MimicTT ? "TT_CancelAll" : "CancelAll";
public static IMultiValueConverter PositionToolTipConverter =
ValueConverter.create<AccountTuple?, bool, bool, bool, string>((account, show_day_pos, show_raw_day_pos, show_total_pos) => {
const string day_pos_text = "day", raw_day_pos_text = "raw day", total_pos_text = "total";
@@ -1,4 +1,4 @@
using bts;
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
@@ -223,6 +223,10 @@ namespace mbts.spark {
Add(qty_column = new SizeColumn(
new DoubleMinMax<OrderRecordTupleWithRoot>(
ort => ort.order.order.quantity(), nameof(OrderBookRowVM.quantity))));
// notional value
Add(notional_value_column = new NotionalValueColumn(
new DoubleMinMax<OrderRecordTupleWithRoot>(
ort => ort.notional_value, nameof(OrderBookRowVM.notional_value))));
// edit quantity column
// not usually part of the main order book, but it's useful to add it
// rather than debugging the popup window
@@ -232,6 +236,10 @@ namespace mbts.spark {
new DoubleMinMax<OrderRecordTupleWithRoot>(
ort => ort.order.order.filled_quantity(),
nameof(OrderBookRowVM.filled_quantity))));
// filled notional value
Add(filled_notional_value_column = new FilledNotionalValueColumn(
new DoubleMinMax<OrderRecordTupleWithRoot>(
ort => ort.filled_notional_value, nameof(OrderBookRowVM.filled_notional_value))));
// price
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.Price,
@@ -363,9 +371,11 @@ namespace mbts.spark {
public SimpleTextColumn<OrderSubType[]> sub_type_column { get; }
public SideColumn side_column { get; }
public SizeColumn qty_column { get; }
public NotionalValueColumn notional_value_column { get; }
public EditableQtyColumn edit_qty_column { get; }
public SimpleTextColumn<DoubleMinMaxState> price_column { get; }
public FilledColumn filled_qty_column { get; }
public FilledNotionalValueColumn filled_notional_value_column { get; }
public SimpleTextColumn<DoubleMinMaxState> last_price_column { get; }
public SimpleTextColumn<DoubleMinMaxState> avg_price_column { get; }
public SimpleTextColumn<OrderStatus[]> status_column { get; }
@@ -391,8 +401,10 @@ namespace mbts.spark {
order_sub_types = sub_type_column.filter.get_state(),
sides = side_column.filter.get_state(),
qty = qty_column.filter.get_state(),
notional_value = notional_value_column.filter.get_state(),
price = price_column.filter.get_state(),
qty_filled = filled_qty_column.filter.get_state(),
filled_notional_value = filled_notional_value_column.filter.get_state(),
avg_price = avg_price_column.filter.get_state(),
last_price = last_price_column.filter.get_state(),
statuses = status_column.filter.get_state(),
@@ -415,7 +427,9 @@ namespace mbts.spark {
account_column.filter.load_state(fs.accounts);
price_column.filter.load_state(fs.price);
qty_column.filter.load_state(fs.qty);
notional_value_column.filter.load_state(fs.notional_value);
filled_qty_column.filter.load_state(fs.qty_filled);
filled_notional_value_column.filter.load_state(fs.filled_notional_value);
side_column.filter.load_state(fs.sides);
order_type_column.filter.load_state(fs.order_types);
root_algo_type_column.filter.load_state(fs.root_algo_types);
@@ -645,6 +659,32 @@ namespace mbts.spark {
DefaultCellPadding);
}
public class NotionalValueColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
public NotionalValueColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
: base(
Columns.NotionalValue,
DefaultColumnHeaders.NotionalValue,
r => r.notional_value_text,
DefaultColumnWidths.NotionalValue,
filter,
DataGridFilterKnownTemplates.DoubleMinMax,
_wrap_comparer(r => r.notional_value)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(
dc,
_get_glyph_context(row),
font_size,
row.notional_value_text,
position,
row_height,
RenderUtils.get_side_brush(row),
background_brush,
horizontal_alignment,
VerticalAlignment.Center,
DefaultCellPadding);
}
public class FilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
: base(Columns.Filled, "Filled Qty", r => r.display_filled_quantity.ToString(), DefaultColumnWidths.Qty, filter, DataGridFilterKnownTemplates.DoubleMinMax, _wrap_comparer(r => r.filled_quantity)) { }
@@ -664,6 +704,32 @@ namespace mbts.spark {
DefaultCellPadding);
}
public class FilledNotionalValueColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledNotionalValueColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
: base(
Columns.FilledNotionalValue,
DefaultColumnHeaders.FilledNotionalValue,
r => r.filled_notional_value_text,
DefaultColumnWidths.FilledNotionalValue,
filter,
DataGridFilterKnownTemplates.DoubleMinMax,
_wrap_comparer(r => r.filled_notional_value)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(
dc,
_get_glyph_context(row),
font_size,
row.filled_notional_value_text,
position,
row_height,
RenderUtils.get_side_brush(row),
background_brush,
horizontal_alignment,
VerticalAlignment.Center,
DefaultCellPadding);
}
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
readonly BindingBase _foreground_binding;
ListSortDirection? _cached_sort_direction;
@@ -1,4 +1,4 @@
using bts;
using bts;
using bts.api;
using bts.api.orderman;
using bts.collections;
@@ -108,6 +108,8 @@ namespace mbts.spark {
public double display_quantity => quantity.safe_divide(_display_qty_divisor);
public string display_quantity_text =>
quantity_display_settings.to_tick_display_settings().format(display_quantity);
public double notional_value => order_tuple.notional_value;
public string notional_value_text => _fmt_price(notional_value);
public TickDisplaySettingsImmutable quantity_display_settings {
get => _quantity_display_settings;
set {
@@ -128,6 +130,8 @@ namespace mbts.spark {
filled_quantity.safe_divide(_display_qty_divisor);
public string display_filled_quantity_text =>
quantity_display_settings.to_tick_display_settings().format(display_filled_quantity);
public double filled_notional_value => order_tuple.filled_notional_value;
public string filled_notional_value_text => _fmt_price(filled_notional_value);
public string last_msg => order.last_msg();
// piq
public uint piq => order_tuple.order.piq ?? uint.MaxValue;
@@ -355,9 +359,15 @@ namespace mbts.spark {
if (_prospective_qty_translator_backing != null)
_prospective_qty_translator_backing.actual_value = quantity;
}
if ((Columns.NotionalValue & dirty) == Columns.NotionalValue) {
RaisePropertyChanged(nameof(notional_value), nameof(notional_value_text));
}
if ((Columns.Filled & dirty) != 0) {
RaisePropertyChanged(nameof(filled_quantity), nameof(display_filled_quantity));
}
if ((Columns.FilledNotionalValue & dirty) == Columns.FilledNotionalValue) {
RaisePropertyChanged(nameof(filled_notional_value), nameof(filled_notional_value_text));
}
// badge fill
if (old_order_tuple.order.meta?.badge_color != order_tuple.order.meta?.badge_color) {
// invalidate by setting to null (will be re-instantiated in the public getter)
@@ -521,6 +531,12 @@ namespace mbts.spark {
if (oldval.order.piq != newval.order.piq) {
dirty |= Columns.Piq;
}
if (oldval.notional_value != newval.notional_value) {
dirty |= Columns.NotionalValue;
}
if (oldval.filled_notional_value != newval.filled_notional_value) {
dirty |= Columns.FilledNotionalValue;
}
return dirty;
}
}
@@ -1,160 +1,170 @@
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.utility;
using ProtoBuf;
using Spark.Api.Orders;
using System;
namespace bts.shell.orderbook {
[ProtoContract]
class FilterStates : IEquatable<FilterStates> {
[ProtoMember(1)]
public SecurityCriteria security_criteria;
[ProtoMember(2)]
public string order_key_regex;
[ProtoMember(3)]
public TimeFilterTuple time_created;
[ProtoMember(4)]
public string[] users;
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(6)]
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
//[ProtoMember(8)]
//public CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(23)]
public AlgoID[] order_types;
[ProtoMember(24)]
public AlgoID[] root_algo_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
public DoubleMinMaxState qty;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
public DoubleMinMaxState qty_filled;
[ProtoMember(13)]
public DoubleMinMaxState avg_price;
[ProtoMember(14)]
public DoubleMinMaxState last_price;
[ProtoMember(15)]
public OrderStatus[] statuses;
[ProtoMember(16)]
public string exchange_message_regex;
[ProtoMember(17)]
public TimeFilterTuple last_change;
//[ProtoMember(18)]
//public string security_regex;
[ProtoMember(19)]
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
[ProtoMember(25)]
public UIntMinMaxState piq;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
price.safe_equals(other.price) &&
qty_filled.safe_equals(other.qty_filled) &&
avg_price.safe_equals(other.avg_price) &&
last_price.safe_equals(other.last_price) &&
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
exchange_message_regex == other.exchange_message_regex &&
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
piq.safe_equals(other.piq);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(root_algo_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols),
piq?.GetHashCode() ?? 0);
}
static class FilterStatesExtensions {
public static Func<OrderRecordTupleWithRoot, bool> build_predicate(this FilterStates fs) {
var pb = new ColumnPredicateBuilder<OrderRecordTupleWithRoot>();
// security
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
// regex
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
// time
pb.add_time_filter(fs.time_created, o => o.order.order.created());
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
// discrete values
pb.add_discrete_values(fs.users, o => o.order.order.user());
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root.algo?.key ?? default));
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
pb.add_discrete_values(fs.sides, o => o.order.order.side());
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.order.quantity());
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
pb.add_numeric(fs.qty_filled, o => o.order.order.filled_quantity());
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
return pb.to_predicate();
}
}
}
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.utility;
using ProtoBuf;
using Spark.Api.Orders;
using System;
namespace bts.shell.orderbook {
[ProtoContract]
class FilterStates : IEquatable<FilterStates> {
[ProtoMember(1)]
public SecurityCriteria security_criteria;
[ProtoMember(2)]
public string order_key_regex;
[ProtoMember(3)]
public TimeFilterTuple time_created;
[ProtoMember(4)]
public string[] users;
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(6)]
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
//[ProtoMember(8)]
//public CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(23)]
public AlgoID[] order_types;
[ProtoMember(24)]
public AlgoID[] root_algo_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
public DoubleMinMaxState qty;
[ProtoMember(26)]
public DoubleMinMaxState notional_value;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
public DoubleMinMaxState qty_filled;
[ProtoMember(27)]
public DoubleMinMaxState filled_notional_value;
[ProtoMember(13)]
public DoubleMinMaxState avg_price;
[ProtoMember(14)]
public DoubleMinMaxState last_price;
[ProtoMember(15)]
public OrderStatus[] statuses;
[ProtoMember(16)]
public string exchange_message_regex;
[ProtoMember(17)]
public TimeFilterTuple last_change;
//[ProtoMember(18)]
//public string security_regex;
[ProtoMember(19)]
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
[ProtoMember(25)]
public UIntMinMaxState piq;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
notional_value.safe_equals(other.notional_value) &&
price.safe_equals(other.price) &&
qty_filled.safe_equals(other.qty_filled) &&
filled_notional_value.safe_equals(other.filled_notional_value) &&
avg_price.safe_equals(other.avg_price) &&
last_price.safe_equals(other.last_price) &&
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
exchange_message_regex == other.exchange_message_regex &&
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
piq.safe_equals(other.piq);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(root_algo_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
notional_value?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
filled_notional_value?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols),
piq?.GetHashCode() ?? 0);
}
static class FilterStatesExtensions {
public static Func<OrderRecordTupleWithRoot, bool> build_predicate(this FilterStates fs) {
var pb = new ColumnPredicateBuilder<OrderRecordTupleWithRoot>();
// security
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
// regex
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
// time
pb.add_time_filter(fs.time_created, o => o.order.order.created());
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
// discrete values
pb.add_discrete_values(fs.users, o => o.order.order.user());
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root.algo?.key ?? default));
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
pb.add_discrete_values(fs.sides, o => o.order.order.side());
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.order.quantity());
pb.add_numeric(fs.notional_value, o => o.notional_value);
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
pb.add_numeric(fs.qty_filled, o => o.order.order.filled_quantity());
pb.add_numeric(fs.filled_notional_value, o => o.filled_notional_value);
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
return pb.to_predicate();
}
}
}
@@ -29,7 +29,7 @@ namespace bts.shell.orderbook {
static OrderBookGridSettingsManager() {
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
// TODO: Remove the immutable font-weight flags when cell animation is ready
var col_settings_flags = EnumHelper.get_values<Columns>().ToDictionary(
var col_settings_flags = Enum.GetValuesAsUnderlyingType<Columns>().Cast<Columns>().ToDictionary(
c => c.ToString(), c => GridColumnSettingsFeatureFlags.ImmutableFontWeight);
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
// TODO: Remove the immutable font-color flags when cell animation is ready
@@ -1,270 +1,277 @@
using bts.api.orderman;
using bts.shell.fills;
using mbts;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
using ColHeader = mbts.ui.controls.DIYGrid.DefaultColumnHeaders;
using ColWidth = mbts.ui.controls.DIYGrid.DefaultColumnWidths;
[ProtoContract]
class OrderBookStartupData {
const bool
_default_show_right_panel = true,
_default_show_order_params = true;
const double
_default_right_panel_width = 400,
_default_order_params_height = 250,
_default_fills_height = 300,
_default_ladder_height = 350;
[ProtoMember(16)]
OrderBookRightPanelSettings _right_panel;
// ALREADY USED TAGS
// [ProtoMember(5)]
// public readonly List<SortingCriteria> sort_criteria;
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
// right panel (host)
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
// order params
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
public double order_params_height { get; set; }
// ladder
public bool show_ladder { get; set; }
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
// fills
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
// filters
[ProtoMember(10)]
public FilterStates filters;
// grid
[ProtoMember(15)]
[Obsolete]
public GridSettings grid { get; set; }
// orderbook grid
[ProtoMember(17)]
public OrderBookGridSettings orderbook_grid { get; set; }
// fills settings
[ProtoMember(26)]
public OrderBookFillsGridSettings fills_settings { get; set; }
// predicate animations
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// use defaults
[ProtoMember(25)]
public bool use_defaults;
// menu display
[ProtoMember(27)]
public SparkMenuDisplaySettings menu_display { get; set; }
/// <summary>
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
/// </summary>
[Obsolete]
public OrderBookStartupData() {
show_right_panel = _default_show_right_panel;
show_order_params = _default_show_order_params;
right_panel_width = _default_right_panel_width;
order_params_height = _default_order_params_height;
fills_height = _default_fills_height;
ladder_height = _default_ladder_height;
}
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
this.show_right_panel = show_right_panel;
this.right_panel_width = right_panel_width;
this.fills_height = fills_height;
this.show_fills = show_fills;
this.orderbook_grid = orderbook_grid;
#pragma warning disable CS0612 // Type or member is obsolete
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
}
#pragma warning disable CS0612 // Type or member is obsolete
[Obsolete]
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
public OrderBookGridSettings grid_settings_wrapper() {
if (use_defaults)
return null;
if (orderbook_grid == null) {
if (grid == null)
grid = _default_general_grid;
orderbook_grid = new OrderBookGridSettings(grid.Clone(), _predicate_animations);
}
return orderbook_grid;
}
#pragma warning restore CS0612 // Type or member is obsolete
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates { statuses = new[] { OrderStatus.Working } },
DefaultGrid,
_default_show_right_panel,
_default_right_panel_width,
_default_fills_height,
use_defaults: true) {
show_order_params = _default_show_order_params,
order_params_height = _default_order_params_height,
fills_settings = DefaultFillsGrid
};
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
public static OrderBookFillsGridSettings DefaultFillsGrid {
get {
var default_fills = FillsSettings.Default();
var rv = new OrderBookFillsGridSettings {
sorting_criteria = default_fills.sorting_criteria.ToList(),
filters = default_fills.filters,
general = FillsSettings.MiniGridSettings
};
return rv;
}
}
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criteria);
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criteria)) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
static class DefaultColumns {
// standard
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings side => new(
Columns.Side.ToString(), ColHeader.Side,
width: ColWidth.Side,
horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new(
Columns.Filled.ToString(),
ColHeader.FilledQuantity,
width: ColWidth.Filled,
horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new(
Columns.AvgFilled.ToString(),
ColHeader.AvgFilled,
width: ColWidth.AveragePrice,
horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
// editable
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Qty, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
public static IEnumerable<SortingCriteria> sorting_criteria {
get {
yield return new SortingCriteria(time_created.id, false);
}
}
public static IEnumerable<GridColumnSettings> standard {
get {
yield return security;
yield return symbol;
yield return time_created;
yield return last_change;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return root_algo_type;
yield return sub_type;
yield return side;
yield return quantity;
yield return price;
yield return piq;
yield return filled_quantity;
yield return avg_price;
yield return last_price;
yield return status;
yield return last_message;
yield return security_key;
yield return order_key;
yield return parent_key;
yield return attached_target_order_key;
}
}
public static IEnumerable<GridColumnSettings> editable {
get {
yield return cancel;
yield return update;
yield return edit_quantity;
yield return edit_price;
yield return commit;
yield return attach_detach;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
};
public static IEnumerable<GridColumnSettings> mini {
get {
foreach (var col_setting in editable)
yield return col_setting;
foreach (var col_setting in standard) {
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
yield return col_setting.with(is_visible: false);
} else {
yield return col_setting;
}
}
}
}
}
}
}
using bts.api.orderman;
using bts.shell.fills;
using mbts;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
using ColHeader = mbts.ui.controls.DIYGrid.DefaultColumnHeaders;
using ColWidth = mbts.ui.controls.DIYGrid.DefaultColumnWidths;
[ProtoContract]
class OrderBookStartupData {
const bool
_default_show_right_panel = true,
_default_show_order_params = true;
const double
_default_right_panel_width = 400,
_default_order_params_height = 250,
_default_fills_height = 300,
_default_ladder_height = 350;
[ProtoMember(16)]
OrderBookRightPanelSettings _right_panel;
// ALREADY USED TAGS
// [ProtoMember(5)]
// public readonly List<SortingCriteria> sort_criteria;
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
// right panel (host)
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
// order params
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
public double order_params_height { get; set; }
// ladder
public bool show_ladder { get; set; }
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
// fills
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
// filters
[ProtoMember(10)]
public FilterStates filters;
// grid
[ProtoMember(15)]
[Obsolete]
public GridSettings grid { get; set; }
// orderbook grid
[ProtoMember(17)]
public OrderBookGridSettings orderbook_grid { get; set; }
// fills settings
[ProtoMember(26)]
public OrderBookFillsGridSettings fills_settings { get; set; }
// predicate animations
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// use defaults
[ProtoMember(25)]
public bool use_defaults;
// menu display
[ProtoMember(27)]
public SparkMenuDisplaySettings menu_display { get; set; }
/// <summary>
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
/// </summary>
[Obsolete]
public OrderBookStartupData() {
show_right_panel = _default_show_right_panel;
show_order_params = _default_show_order_params;
right_panel_width = _default_right_panel_width;
order_params_height = _default_order_params_height;
fills_height = _default_fills_height;
ladder_height = _default_ladder_height;
}
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
this.show_right_panel = show_right_panel;
this.right_panel_width = right_panel_width;
this.fills_height = fills_height;
this.show_fills = show_fills;
this.orderbook_grid = orderbook_grid;
#pragma warning disable CS0612 // Type or member is obsolete
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
}
#pragma warning disable CS0612 // Type or member is obsolete
[Obsolete]
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
public OrderBookGridSettings grid_settings_wrapper() {
if (use_defaults)
return null;
if (orderbook_grid == null) {
if (grid == null)
grid = _default_general_grid;
orderbook_grid = new OrderBookGridSettings(grid.Clone(), _predicate_animations);
}
return orderbook_grid;
}
#pragma warning restore CS0612 // Type or member is obsolete
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates { statuses = new[] { OrderStatus.Working } },
DefaultGrid,
_default_show_right_panel,
_default_right_panel_width,
_default_fills_height,
use_defaults: true) {
show_order_params = _default_show_order_params,
order_params_height = _default_order_params_height,
fills_settings = DefaultFillsGrid
};
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
public static OrderBookFillsGridSettings DefaultFillsGrid {
get {
var default_fills = FillsSettings.Default();
var rv = new OrderBookFillsGridSettings {
sorting_criteria = default_fills.sorting_criteria.ToList(),
filters = default_fills.filters,
general = FillsSettings.MiniGridSettings
};
return rv;
}
}
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criteria);
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criteria)) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
static class DefaultColumns {
// standard
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings side => new(
Columns.Side.ToString(), ColHeader.Side,
width: ColWidth.Side,
horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings notional_value => new GridColumnSettings(Columns.NotionalValue.ToString(), DIYGrid.DefaultColumnHeaders.NotionalValue, width: DIYGrid.DefaultColumnWidths.NotionalValue, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new(
Columns.Filled.ToString(),
ColHeader.FilledQuantity,
width: ColWidth.Filled,
horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_notional_value => new(
Columns.FilledNotionalValue.ToString(),
ColHeader.FilledNotionalValue,
width: ColWidth.FilledNotionalValue,
horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new(
Columns.AvgFilled.ToString(),
ColHeader.AvgFilled,
width: ColWidth.AveragePrice,
horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
// editable
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Qty, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
public static IEnumerable<SortingCriteria> sorting_criteria {
get {
yield return new SortingCriteria(time_created.id, false);
}
}
public static IEnumerable<GridColumnSettings> standard {
get {
yield return security;
yield return symbol;
yield return time_created;
yield return last_change;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return root_algo_type;
yield return sub_type;
yield return side;
yield return quantity;
yield return price;
yield return piq;
yield return filled_quantity;
yield return avg_price;
yield return last_price;
yield return status;
yield return last_message;
yield return security_key;
yield return order_key;
yield return parent_key;
yield return attached_target_order_key;
}
}
public static IEnumerable<GridColumnSettings> editable {
get {
yield return cancel;
yield return update;
yield return edit_quantity;
yield return edit_price;
yield return commit;
yield return attach_detach;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
};
public static IEnumerable<GridColumnSettings> mini {
get {
foreach (var col_setting in editable)
yield return col_setting;
foreach (var col_setting in standard) {
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
yield return col_setting.with(is_visible: false);
} else {
yield return col_setting;
}
}
}
}
}
}
}
@@ -1,107 +1,109 @@
using bts.api.orderman;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.converters;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.spark {
internal partial class OrderBookGridView : DIYGrid {
public const string
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
RowStyleKey = "RowStyle",
RowTemplateKey = "RowTemplateKey",
CVRowStyleKey = "CVRowStyle",
CVRowTemplateKey = "CVRowTemplateKey";
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
public enum ColumnKeys {
Security = 1,
Symbol = 1 << 1,
Time = 1 << 2,
LastChange = 1 << 3,
User = 1 << 4,
Account = 1 << 5,
Exchange = 1 << 6,
TimeInForce = 1 << 7,
OrderType = 1 << 8,
SubType = 1 << 9,
Side = 1 << 10,
Size = 1 << 11,
Price = 1 << 12,
Filled = 1 << 13,
AvgFilled = 1 << 14,
LastPrice = 1 << 15,
Status = 1 << 16,
LastMessage = 1 << 17,
SecurityKey = 1 << 18,
OrderKey = 1 << 19,
ParentKey = 1 << 20,
AttachedTargetOrderKey = 1 << 21,
Cancel = 1 << 22,
EditableQty = 1 << 23,
EditablePrice = 1 << 24,
Attach = 1 << 25,
Detach = 1 << 26,
Update = 1 << 27,
Commit = 1 << 28,
RootAlgoType = 1 << 29,
Piq = 1 << 30,
Check = 1 << 31
}
public OrderBookGridView() {
InitializeComponent();
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
}
static class RowBackgroundHelper {
public static readonly BindingBase row_background_binding;
static RowBackgroundHelper() {
var bg_binding = new MultiBinding();
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().StatusColorOpacityWhenSelected;
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
});
} else {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
if (is_selected)
return null;
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) : null;
});
}
row_background_binding = bg_binding;
}
static Color? _status_color(OrderStatus status) {
switch (status) {
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
case OrderStatus.Disconnected:
case OrderStatus.RejectedBySpark:
case OrderStatus.Error:
return BTSColors.Orders.error_order_background;
default: return null;
}
}
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
public OrderBookRowView() : base() { }
}
}
using bts.api.orderman;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.converters;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.spark {
internal partial class OrderBookGridView : DIYGrid {
public const string
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
RowStyleKey = "RowStyle",
RowTemplateKey = "RowTemplateKey",
CVRowStyleKey = "CVRowStyle",
CVRowTemplateKey = "CVRowTemplateKey";
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
public enum ColumnKeys : long {
Security = 1,
Symbol = 1 << 1,
Time = 1 << 2,
LastChange = 1 << 3,
User = 1 << 4,
Account = 1 << 5,
Exchange = 1 << 6,
TimeInForce = 1 << 7,
OrderType = 1 << 8,
SubType = 1 << 9,
Side = 1 << 10,
Size = 1 << 11,
Price = 1 << 12,
Filled = 1 << 13,
AvgFilled = 1 << 14,
LastPrice = 1 << 15,
Status = 1 << 16,
LastMessage = 1 << 17,
SecurityKey = 1 << 18,
OrderKey = 1 << 19,
ParentKey = 1 << 20,
AttachedTargetOrderKey = 1 << 21,
Cancel = 1 << 22,
EditableQty = 1 << 23,
EditablePrice = 1 << 24,
Attach = 1 << 25,
Detach = 1 << 26,
Update = 1 << 27,
Commit = 1 << 28,
RootAlgoType = 1 << 29,
Piq = 1 << 30,
Check = 1 << 31,
NotionalValue = 1L << 32,
FilledNotionalValue = 1L << 33,
}
public OrderBookGridView() {
InitializeComponent();
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
}
static class RowBackgroundHelper {
public static readonly BindingBase row_background_binding;
static RowBackgroundHelper() {
var bg_binding = new MultiBinding();
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().StatusColorOpacityWhenSelected;
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
});
} else {
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
if (is_selected)
return null;
var color = _status_color(status);
return color.HasValue ? new SolidColorBrush(color.Value) : null;
});
}
row_background_binding = bg_binding;
}
static Color? _status_color(OrderStatus status) {
switch (status) {
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
case OrderStatus.Disconnected:
case OrderStatus.RejectedBySpark:
case OrderStatus.Error:
return BTSColors.Orders.error_order_background;
default: return null;
}
}
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
public OrderBookRowView() : base() { }
}
}
@@ -65,9 +65,9 @@ namespace BCG {
public static NMSheetTheoBBOCross get_cross_state(double bid_price, double ask_price, double theo, double delta, bool is_call, double driver_tick_size, bool is_proximity_alert_enabled) {
double abs_delta = Math.Abs(delta);
abs_delta = abs_delta == 0 ? Double.Epsilon : abs_delta;
if (!Double.IsNaN(theo) && !Double.IsNaN(bid_price) && theo < bid_price)
if (!Double.IsNaN(theo) && !Double.IsNaN(bid_price) && (bid_price - theo) > _basic_crosss_eps)
return new NMSheetTheoBBOCross(CrossState.bid_cross, 0);
else if (!Double.IsNaN(theo) && !Double.IsNaN(ask_price) && theo > ask_price)
else if (!Double.IsNaN(theo) && !Double.IsNaN(ask_price) && (theo - ask_price) > _basic_crosss_eps)
return new NMSheetTheoBBOCross(CrossState.ask_cross, 0);
else if (is_proximity_alert_enabled) {
/// bool is passed to bypass doing the below
@@ -87,6 +87,7 @@ namespace BCG {
return NMSheetTheoBBOCross.get_nulled_obj();
}
private static double _basic_crosss_eps = 0.01;
private static double _n_max_ticks = 100.0;
private static NMSheetTheoBBOCross _nulled_obj = new NMSheetTheoBBOCross(CrossState.none, 0);
public static NMSheetTheoBBOCross get_nulled_obj() { return _nulled_obj; }
@@ -64,8 +64,7 @@ namespace mbts.analytics {
if (tl.tl_row.size > 0) {
start_long_pos += tl.tl_row.size;
start_long_total_cost += cost;
}
else {
} else {
start_short_pos += Math.Abs(tl.tl_row.size);
start_short_total_cost += cost;
}
@@ -78,8 +77,7 @@ namespace mbts.analytics {
if (tl.tl_row.size > 0) {
day_long_pos += tl.tl_row.size;
day_long_total_cost += cost;
}
else {
} else {
day_short_pos += Math.Abs(tl.tl_row.size);
day_short_total_cost += cost;
}
@@ -165,6 +163,13 @@ namespace mbts.analytics {
is_day_trade = _is_day_trade(tl)
};
pnl_trades.Add(pnl_tl);
} else if (tl.security_type == SecurityType.Spot) {
PNLTradeRow pnl_tl = new PNLTradeRow() {
tl_row = tl,
md = tl_md,
is_day_trade = _is_day_trade(tl)
};
pnl_trades.Add(pnl_tl);
}
}
return pnl_trades;
+17 -6
View File
@@ -32,6 +32,7 @@ namespace mbts.analytics {
return now_asset_value - init_asset_value;
}
public double driver {
get {
if (tl_row.security_type == SecurityType.Option) {
@@ -44,13 +45,18 @@ namespace mbts.analytics {
}
public double theo {
get {
if (tl_row.security_type == SecurityType.Option) {
return tl_row.call_put == PutOrCall.Call ?
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
} else if (tl_row.security_type == SecurityType.Future) {
return fut_theo.theo;
switch (tl_row.security_type) {
case SecurityType.Option:
return tl_row.call_put == PutOrCall.Call ?
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
case SecurityType.Future:
return fut_theo.theo;
case SecurityType.Spot:
var bbo = top_of_book;
return (bbo.Item1 + bbo.Item2) / 2;
default:
return Double.NaN;
}
return Double.NaN;
}
}
public double pnl {
@@ -61,6 +67,8 @@ namespace mbts.analytics {
rv = _future_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Option) {
rv = _option_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Spot) {
rv = tl_row.size * (theo - tl_row.price);
}
return rv;
}
@@ -69,7 +77,10 @@ namespace mbts.analytics {
rv = _future_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Option) {
rv = _option_pnl(tl_row.price, theo);
} else if (tl_row.security_type == SecurityType.Spot) {
rv = tl_row.size * tl_row.price - settle_price;
}
return rv;
}
}
@@ -58,9 +58,10 @@ namespace BCG {
}
// TODO: put it as a setting
bts.Log.assert(theos.Length == drivers.Length);
for (int i = 0; i < theos.Length; i++) {
theos[i] /= drivers[i];
}
// only do this when its deribit crypto option, TODO
// for (int i = 0; i < theos.Length; i++) {
// theos[i] /= drivers[i];
// }
return new SpreadValue(spread_spec, theos, deltas, gammas, vegas, adj_vegas, oevs, thetas, slope_vegas, drivers, qs_midpoint_theos, pos, additional_delta);
}
}
@@ -142,9 +143,21 @@ namespace BCG {
return tp ?? mbts.spark.TheoPoint.getNulledObject();
}
private static mbts.spark.TheoPoint _compute_single_option_leg_at_tied_price(DateTime option_expiration, double strike, bool is_call, OptionTheoInterpolationData sparse_theo_map, double driver_price) {
mbts.spark.TheoPoint tp = sparse_theo_map == null ? mbts.spark.TheoPoint.getNulledObject() : sparse_theo_map.interpolate_theos(option_expiration, strike, driver_price);
return tp ?? mbts.spark.TheoPoint.getNulledObject();
private static mbts.spark.TheoPoint _compute_single_option_leg_at_tied_price(DateTime option_expiration, double strike, bool is_call, OptionTheoInterpolationData sparse_theo_map,
ExpStrikeKeyedData<mbts.spark.TheoPoint> live_symbol_theo_map, double driver_price) {
// mbts.spark.TheoPoint tp = sparse_theo_map == null ? mbts.spark.TheoPoint.getNulledObject() : sparse_theo_map.interpolate_theos(option_expiration, strike, driver_price);
// return tp ?? mbts.spark.TheoPoint.getNulledObject();
// TOD: config here
return _compute_single_option_leg_at_tied_price_from_live(option_expiration, strike, is_call, live_symbol_theo_map, driver_price);
}
private static mbts.spark.TheoPoint _compute_single_option_leg_at_tied_price_from_live(DateTime option_expiration, double strike, bool is_call,
ExpStrikeKeyedData<mbts.spark.TheoPoint> live_symbol_theo_map, double driver_price) {
var tp = live_symbol_theo_map.get_data(option_expiration, strike);
if (tp == null)
return mbts.spark.TheoPoint.getNulledObject();
return TheoPoint.extrapolate(tp, driver_price, is_call);
}
/// this is the only relatively tricky thing here
@@ -174,7 +187,7 @@ namespace BCG {
if (leg_price_spec.primaryFrozen) /// tied
{
effective_driver = leg_price_spec.primary;
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, leg_price_spec.primary);
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, live_symbol_theo_map, leg_price_spec.primary);
} else {
mbts.spark.TheoPoint tp = _compute_single_option_leg_at_live_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, live_symbol_theo_map);
effective_driver = tp.driver;
@@ -188,7 +201,7 @@ namespace BCG {
future_theo_map.TryGetValue(driver_price_info.first, out symbol_future_theo_map);
double driver_price = StatelessFutureTheoCalculator.transform_price(symbol_future_theo_map, leg_price_spec.driverExpiration, leg_price_spec.primary, driver_price_info.second);
effective_driver = leg_price_spec.primary;
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, driver_price);
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, live_symbol_theo_map, driver_price);
} else {
bts.Log.Ui.error(String.Format("This spread is live but the driver expiration {0} is different from the system driver expiration {1} for {2}",
spread_option_leg.legPriceSpec.driverExpiration, driver_price_info.second, spread_option_leg.contract.product));
@@ -1,4 +1,4 @@
namespace mbts.ui.controls {
namespace mbts.ui.controls {
using bts;
using bts.utils;
@@ -43,7 +43,9 @@
Side = 47,
Qty = 48,
MergedQty = 68,
NotionalValue = 68,
Filled = 56,
FilledNotionalValue = 81,
Symbol = 60,
Time = 112,
TimeOfDay = 84,
@@ -122,6 +124,7 @@
SecurityType = "Security Type",
Side = "B/S",
Qty = "Qty",
NotionalValue = "Notional",
MergedQty = "Qty",
DirectQty = "Qty (D)",
ImpliedQty = "Qty (I)",
@@ -144,6 +147,7 @@
FillKind = "Fill Kind",
FilledPrice = "Fill Price",
FilledQuantity = "Fill Qty",
FilledNotionalValue = "Fill Notional",
FillStatus = "Fill Status",
Name = "Name",
Description = "Description",
+1 -1
View File
@@ -100,7 +100,7 @@
}
void PerformMEFComposition(string config_source) {
Log.Ui.info($"Generating MEF dependencies for the shell " +
Log.Ui.info($"Generating MEF dependencies for the shell" +
$"({typeof(IInitializable)}s, {typeof(IShellModule)}s, " +
$"{typeof(IShellDaemon)}s, etc.)");
try {
@@ -154,37 +154,48 @@ namespace mbts.ui.spark {
static class MDDisplayHelper {
/// This delegate should be created by calling <see cref="get_formatter(MarketSizeDisplay)"/>
public delegate string SizeFormatter(double merged, double implied, double price);
public delegate string SizeFormatter(double merged, double implied, double price, double notional);
/// The private delegates below use refs bc the <see cref="SizeFormatter"/> returned by <see cref="get_formatter(MarketSizeDisplay)"/>
/// internally uses several <see cref="_SizeFormatter"/>s to add different text components to the resultant string.
/// The refs, therefor, avoid making extra copies of the variables.
/// These are private members, so any code-readability cost is confined to a limited scope.
delegate void _SizeFormatter(ref double merged, ref double implied, ref double price, ref StringBuilder sb);
delegate void _SizeFormatter(ref double merged, ref double implied, ref double price, ref double notional, ref StringBuilder sb);
delegate void _SizeFormatterNPS(ref NativePriceSize nps, ref StringBuilder sb);
public static Func<double, double, string> get_value_formatter(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable price_display) {
var raw_fmt_size = get_formatter(size_display, display, price_display);
return (q, p) => raw_fmt_size(q, 0, p);
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
var raw_fmt_size = get_formatter(size_display, display, notional_value_display);
return (q, p) => raw_fmt_size(q, 0, p, double.NaN);
}
public static Func<LadderPriceLevel, string> get_formatter_lpl(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
var raw_fmt_size = get_formatter(size_display, display, notional_value_display);
return nps => raw_fmt_size(
nps.Volume,
0,
nps.Price,
nps.NotionalValue);
}
public static Func<NativePriceSize, string> get_formatter_nps(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable price_display) {
var raw_fmt_size = get_formatter(size_display, display, price_display);
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
var raw_fmt_size = get_formatter(size_display, display, notional_value_display);
return nps => raw_fmt_size(
nps.implied_size + nps.direct_size,
nps.implied_size,
nps.price);
nps.price,
nps.notional_size);
}
public static SizeFormatter get_formatter(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable price_display) {
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display, price_display);
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display, notional_value_display);
_SizeFormatter implied_present = _get_formatter(
size_display.implied_present ?? size_display.normal, display, price_display);
return (merged, implied, price) => {
size_display.implied_present ?? size_display.normal, display, notional_value_display);
return (merged, implied, price, notional) => {
StringBuilder sb = new StringBuilder();
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref price, ref sb);
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref price, ref notional, ref sb);
return sb.ToString();
};
}
@@ -205,13 +216,13 @@ namespace mbts.ui.spark {
static _SizeFormatter _get_formatter(
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
TickDisplaySettingsImmutable display,
TickDisplaySettingsImmutable price_display) {
TickDisplaySettingsImmutable notional_value_display) {
_SizeFormatter[] formatters = fmt_elements.is_null_or_empty() ?
Array.Empty<_SizeFormatter>() :
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter(sfe, display, price_display));
return new _SizeFormatter((ref double m, ref double i, ref double p, ref StringBuilder sb) => {
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter(sfe, display, notional_value_display));
return new _SizeFormatter((ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) => {
foreach (var f in formatters)
f(ref m, ref i, ref p, ref sb);
f(ref m, ref i, ref p, ref n, ref sb);
});
}
@@ -237,7 +248,7 @@ namespace mbts.ui.spark {
static _SizeFormatter _get_formatter(
MarketSizeDisplay.SizeFormattingElement fmt_element,
TickDisplaySettingsImmutable display,
TickDisplaySettingsImmutable price_display) =>
TickDisplaySettingsImmutable notional_value_display) =>
fmt_element.type switch {
MarketSizeDisplay.SizeFormattingElementType.Const =>
SizeTokenFormatters.char_formatter(fmt_element.data ?? string.Empty),
@@ -248,7 +259,7 @@ namespace mbts.ui.spark {
MarketSizeDisplay.SizeFormattingElementType.Merged =>
SizeTokenFormatters.merged_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.MergedNotional =>
SizeTokenFormatters.merged_notional_formatter(price_display),
SizeTokenFormatters.merged_notional_formatter(notional_value_display),
_ => throw ExceptionBuilder.Argument.unexpected_case(
fmt_element.type, nameof(fmt_element.type)),
};
@@ -256,24 +267,24 @@ namespace mbts.ui.spark {
static class SizeTokenFormatters {
public static _SizeFormatter char_formatter(string data) {
string local = data.ToString();
return (ref double m, ref double i, ref double p, ref StringBuilder sb) =>
return (ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
sb.Append(local);
}
public static _SizeFormatter merged_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
sb.Append(display.format(m));
public static _SizeFormatter direct_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
sb.Append(display.format(m - i));
public static _SizeFormatter implied_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
sb.Append(display.format(i));
public static _SizeFormatter merged_notional_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
sb.Append(display.format(m * p));
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
sb.Append(display.format(n));
public static _SizeFormatterNPS char_formatter_nps(string data) {
string local = data.ToString();
@@ -373,9 +384,10 @@ namespace mbts.ui.spark {
_display = backing(nameof(display), fmt);
string fmt() {
const double SAMPLE_PRICE = 25.0;
var impl = MDDisplayHelper.get_formatter(
owner.create(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
return impl(merged, implied, 1);
return impl(merged, implied, 1, merged * SAMPLE_PRICE);
}
}
@@ -11,6 +11,7 @@ namespace mbts {
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
public readonly struct SecurityMD : IEquatable<SecurityMD> {
public static readonly Func<SecurityMD, uint> key_fn = smd => smd.security.Id;
@@ -60,4 +61,128 @@ namespace mbts {
public static bool operator !=(SecurityMD left, SecurityMD right) =>
!(left == right);
}
static class SecurityMDExtensions {
const uint DEPTH_COUNT = 10;
[ThreadStatic]
static NativePriceSize[] leg1Bids;
[ThreadStatic]
static NativePriceSize[] leg1Asks;
static SecurityMDExtensions() {
leg1Bids = new NativePriceSize[DEPTH_COUNT];
leg1Asks = new NativePriceSize[DEPTH_COUNT];
}
public static Book Depth(this SecurityMD security, IReadOnlyDictionary<uint, SecurityMD> securities, ref ulong time) {
var bids = new NativePriceSize[DEPTH_COUNT];
var asks = new NativePriceSize[DEPTH_COUNT];
var bidsOut = new (double, double, double)[(int)DEPTH_COUNT];
var asksOut = new (double, double, double)[(int)DEPTH_COUNT];
if (!security.security.HasSynthetic) {
security.md.get_depth(ref time, ref bids, ref asks, DEPTH_COUNT);
for (int i = 0; i < DEPTH_COUNT; i++) {
bidsOut[i] = (bids[i].price, bids[i].size, bids[i].notional_size);
asksOut[i] = (asks[i].price, asks[i].size, asks[i].notional_size);
}
return new(bidsOut, asksOut);
}
Log.assert(security.security.Synthetic.Legs.TryGetFirst(out var leg1));
if (!securities.TryGetValue(leg1.Security.Id, out var sec1)) {
return Book.Empty;
}
ulong sec1Time = 0;
if (!sec1.md.get_depth(ref sec1Time, ref leg1Bids, ref leg1Asks, DEPTH_COUNT)) {
return Book.Empty;
}
security.md.get_depth(ref time, ref bids, ref asks, DEPTH_COUNT);
int leg1BidsLevel = 0;
int leg1AsksLevel = 0;
double leg1BidsConsumed = 0;
double leg1AsksConsumed = 0;
double takeQuantity;
double notional;
for (int i = 0; i < DEPTH_COUNT && !bids[i].is_nan; i++) {
takeQuantity = bids[i].size * leg1.Quantity;
notional = getNotionalValue(ref leg1Bids, ref leg1BidsLevel, ref leg1BidsConsumed, ref takeQuantity);
bidsOut[i] = (bids[i].price, bids[i].size, notional);
takeQuantity = asks[i].size * leg1.Quantity;
notional = getNotionalValue(ref leg1Asks, ref leg1AsksLevel, ref leg1AsksConsumed, ref takeQuantity);
asksOut[i] = (asks[i].price, asks[i].size, notional);
}
return new(bidsOut, asksOut);
}
public static Book Depth(this SecurityMD security, NativePriceSize[] bids, NativePriceSize[] asks, IReadOnlyDictionary<uint, SecurityMD> securities) {
bids ??= new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
asks ??= new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
var bidsOut = new (double, double, double)[bids.Length];
var asksOut = new (double, double, double)[asks.Length];
var depthCount = (uint)Math.Min(bids.Length, asks.Length);
if (!security.security.HasSynthetic) {
for (int i = 0; i < depthCount; i++) {
bidsOut[i] = (bids[i].price, bids[i].size, bids[i].notional_size);
asksOut[i] = (asks[i].price, asks[i].size, asks[i].notional_size);
}
return new(bidsOut, asksOut);
}
Log.assert(security.security.Synthetic.Legs.TryGetFirst(out var leg1));
if (!securities.TryGetValue(leg1.Security.Id, out var sec1)) {
return Book.Empty;
}
ulong sec1Time = 0;
if (!sec1.md.get_depth(ref sec1Time, ref leg1Bids, ref leg1Asks, depthCount)) {
return Book.Empty;
}
int leg1BidsLevel = 0;
int leg1AsksLevel = 0;
double leg1BidsConsumed = 0;
double leg1AsksConsumed = 0;
double takeQuantity;
double notional;
for (int i = 0; i < depthCount && !bids[i].is_nan; i++) {
takeQuantity = bids[i].size;
notional = getNotionalValue(ref leg1Bids, ref leg1BidsLevel, ref leg1BidsConsumed, ref takeQuantity);
bidsOut[i] = (bids[i].price, bids[i].size, notional);
takeQuantity = asks[i].size;
notional = getNotionalValue(ref leg1Asks, ref leg1AsksLevel, ref leg1AsksConsumed, ref takeQuantity);
asksOut[i] = (asks[i].price, asks[i].size, notional);
}
return new(bidsOut, asksOut);
}
static double getNotionalValue(ref NativePriceSize[] depth, ref int level, ref double levelConsumed, ref double takeQuantity) {
if (level >= depth.Length) {
return double.NaN;
}
double notionalValue = 0;
while (takeQuantity > MathUtils.epsilon<double>() && level < depth.Length && !depth[level].is_nan) {
var lvlDepth = (double)depth[level].size - levelConsumed;
if (lvlDepth <= 0) {
level++;
levelConsumed = 0;
continue;
}
var lvlTake = Math.Min(takeQuantity, lvlDepth);
notionalValue += lvlTake * depth[level].price;
levelConsumed += lvlTake;
takeQuantity -= lvlDepth;
}
return notionalValue;
}
public class Book {
public Book(IEnumerable<(double Price, double Volume, double NotionalValue)> bids, IEnumerable<(double Price, double Quantity, double NotionalValue)> asks) {
Bids = bids;
Asks = asks;
}
public static Book Empty =>
new(Array.Empty<(double, double, double)>(), Array.Empty<(double, double, double)>());
public IEnumerable<(double Price, double Volume, double NotionalValue)> Bids { get; }
public IEnumerable<(double Price, double Volume, double NotionalValue)> Asks { get; }
}
}
}
@@ -69,6 +69,18 @@ namespace Spark.Api.Orders {
public uint security_id => order.security_id;
public SecurityImmutable? security => order.security;
// TODO: remove assumption that synthetic quantities are already notional values
public double notional_value =>
(security?.HasSynthetic ?? false)
? order.order.quantity()
: order.order.notional_value();
// TODO: remove assumption that synthetic quantities are already notional values
public double filled_notional_value =>
(security?.HasSynthetic ?? false)
? order.order.filled_quantity()
: order.order.filled_notional_value();
public bool Equals(OrderRecordTupleWithRoot other) =>
order.Equals(other.order) &&
EqualityComparer.nullable_equals(_root, other._root);
@@ -48,6 +48,12 @@ namespace Spark.Api.Orders {
public static double quantity(this OrderRecordImmutable order) =>
order.HasState ? order.State.quantity() : 0;
public static double notional_value(this OrderRecordImmutable order) =>
order.quantity() * order.ui_price();
public static double filled_notional_value(this OrderRecordImmutable order) =>
order.avg_price() * order.filled_quantity();
public static double working_quantity(this OrderRecordImmutable order) =>
order.HasState ? order.State.working_quantity() : 0;
@@ -487,5 +493,8 @@ namespace Spark.Api.Orders {
CustomOrderDefinition.CustomOneofCase.Composite => "COMP",
_ => algo_type.ToString(),
};
public static double notional_value(this FillImmutable fill) =>
fill.LastShares * fill.LastPrice;
}
}
@@ -10,58 +10,64 @@ using System.IO;
using System.Reactive.Disposables;
using mbts;
using System.Threading;
using System.Runtime.InteropServices;
namespace bts.api.core {
internal class QuickbusClientChannel : ClientChannel {
IntPtr _client;
internal class QuickbusClientChannel : ClientChannel {
IntPtr _client;
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
}
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
Log.QBus.diag($"Created QuickbusClientChannel");
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
}
public override void Dispose() {
ApiNativeMethods.api_client_destroy(_client);
_client = IntPtr.Zero;
}
public override void Dispose() {
Log.QBus.diag($"Disposed QuickbusClientChannel");
ApiNativeMethods.api_client_destroy(_client);
_client = IntPtr.Zero;
}
int _req_id = 0;
Dictionary<int, PBCallbackWrapper> _keepalive_cache = new();
unsafe public override Task<Result<Response>> send<Request, Response>(int method_id, Request request) {
var id = Interlocked.Increment(ref _req_id);
var header = new RequestHeader() {
Method = method_id,
RequestId = id,
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
};
var buffer = header.serialize(request);
var tcs = new TaskCompletionSource<Result<Response>>();
//ApiNativeMethods.api_client_log_error($"Call {id}");
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
//ApiNativeMethods.api_client_log_error($"Reply {id}");
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
if (res.header.Code == ApiError.Success) {
tcs.TrySetResult(new Result<Response>(res.body));
}
else {
tcs.TrySetResult(new Result<Response>(res.header.Code));
}
var id = Interlocked.Increment(ref _req_id);
var header = new RequestHeader() {
Method = method_id,
RequestId = id,
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
};
var buffer = header.serialize(request);
var tcs = new TaskCompletionSource<Result<Response>>();
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
if (res.header.Code == ApiError.Success) {
tcs.TrySetResult(new Result<Response>(res.body));
}
else {
tcs.TrySetResult(new Result<Response>(res.header.Code));
}
lock (_keepalive_cache) {
_keepalive_cache.Remove(id);
}
};
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
var cb_wrapper = new PBCallbackWrapper(callback);
lock (_keepalive_cache) {
_keepalive_cache.Remove(id);
_keepalive_cache.Add(id, cb_wrapper);
}
};
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
var cb_wrapper = new PBCallbackWrapper(callback);
lock (_keepalive_cache) {
_keepalive_cache.Add(id, cb_wrapper);
}
ApiNativeMethods.api_client_send(
_client, buffer, buffer.Length, cb_wrapper.callback, IntPtr.Zero);
return tcs.Task;
}
ApiNativeMethods.api_client_send(
_client, buffer, buffer.Length, cb_wrapper.Callback, IntPtr.Zero);
// Return a task that will free the handle once the task is completed
return tcs.Task.ContinueWith(task => {
cb_wrapper.Free(); // Ensure the GCHandle is freed when the callback is done
return task.Result;
});
}
public override IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter) {
var result = Observable.Create<RawUpdate<TOut>>(async observer => {
@@ -206,11 +212,29 @@ namespace bts.api.core {
/// garbage-collector race may yield the infamous error, "A callback was made on a garbage
/// collected delegate of type...").
/// </remarks>
class PBCallbackWrapper {
public readonly ApiNativeMethods.raw_pb_callback callback;
class PBCallbackWrapper
{
public readonly ApiNativeMethods.raw_pb_callback Callback;
private GCHandle _handle;
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback) =>
this.callback = callback;
}
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback)
{
// Pin the callback to prevent garbage collection
Log.QBus.diag($"Created PBCallbackWrapper");
_handle = GCHandle.Alloc(callback);
Callback = callback;
}
public void Free()
{
// Free the GCHandle when the delegate is no longer needed
Log.QBus.diag($"Deleting PBCallbackWrapper");
if (_handle.IsAllocated)
{
_handle.Free();
}
}
}
}
}
+2 -1
View File
@@ -10,8 +10,9 @@ namespace bts
static int _needs_init_spark_lib = 0;
public static void init(string config_source = null) {
Console.WriteLine(
$"Configuring BTS with {(config_source is null ? "defaults" : $"'{config_source}'")}");
$"Configuring BTS v2.2.4 with {(config_source is null ? "defaults" : $"'{config_source}'")}");
var args = Environment.GetCommandLineArgs();
utils.BTSNativeMethods.provide_command_line(args, args.Length);
@@ -65,10 +65,10 @@ namespace bts.utils {
public static bool epsilon_equals<T>(this T x, T y, T epsilon) where T : IFloatingPoint<T> =>
MathUtils.epsilon_equals(x, y, epsilon);
public static bool marginal_equals<T>(T x, T y) where T : IFloatingPoint<T> =>
marginal_equals(x, y, MathUtils.epsilon<T>());
public static bool MarginalEquals<T>(this T x, T y) where T : IFloatingPoint<T> =>
MarginalEquals(x, y, MathUtils.epsilon<T>());
public static bool marginal_equals<T>(T x, T y, T margin) where T : IFloatingPoint<T> =>
public static bool MarginalEquals<T>(this T x, T y, T margin) where T : IFloatingPoint<T> =>
MathUtils.marginal_equals(x, y, margin);
public static bool is_within_range<T>(
@@ -13,6 +13,6 @@ message DatabentoMarketDataConfig {
double replay_sleep_timer_sec = 50;
string key = 60;
string dataset = 70;
bool filter_securities_one_year= 80;
uint32 filter_securities_upto_n_months= 80;
bool load_reference_data = 90;
}
+201 -174
View File
@@ -4,16 +4,11 @@
#include "lib/api/services/OrderService.server.pb.h"
#include "lib/config/config.h"
#include "lib/config/protos/toggles.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/utils/PBUtils.h"
#include <memory>
namespace bts {
namespace admin {
using namespace bts::api::admin;
using namespace bts::api::orderman;
class AdminServiceUtils {
public:
struct ExchangeSubAccountResolver {
@@ -33,7 +28,7 @@ class AdminServiceUtils {
return is_int_account ? entry.bts_account_name()
: entry.exchange_subaccount();
};
if(sec == nullptr)
if (sec == nullptr)
return nullptr;
for (const auto &u : exch_sub_accounts.accounts()) {
if (get_account(u) == account)
@@ -59,224 +54,247 @@ class AdminServiceUtils {
////////////////////////////////////////////
// finds the max id out of all the existing connections
// (todo: deleted connections)
static uint32_t find_max_connection_id(const AdminData &data);
static uint32_t find_max_connection_id(const api::admin::AdminData &data);
// finds a connection by name and returns a mutable pointer to it
static const ExchangeConnection *find_connection(
const AdminData &data, const std::string &connection_name);
static const api::admin::ExchangeConnection *find_connection(
const api::admin::AdminData &data, const std::string &connection_name);
// finds the presence of an exchange in a connection
static bool has_exchange(
const ExchangeConnection &conn, bts::api::Venue exchange);
const api::admin::ExchangeConnection &conn, api::Venue exchange);
// finds the default connections for an exchange
// in a given account
static const ExchangeConnection *find_default_account_connection(
const AdminData &data,
const BTSAccount &account,
bts::api::Venue exchange);
static const ExchangeConnection *resolve_cme_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *find_default_account_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
api::Venue exchange);
static const api::admin::ExchangeConnection *resolve_cme_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_ice_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *resolve_ice_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_sim_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *resolve_sim_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_connection(
const AdminData &data,
const BTSAccount &account,
const bts::api::Security &sec);
static const api::admin::ExchangeConnection *resolve_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
////////////////////////////////////////////
// connection mutators /////////////////////
////////////////////////////////////////////
// adds a new connection (does not check for name uniqueness)
static ExchangeConnection *add_connection(
AdminData &data, const ExchangeConnection &new_connection);
static api::admin::ExchangeConnection *add_connection(
api::admin::AdminData &data,
const api::admin::ExchangeConnection &new_connection);
// reads the connections from config and fills in the
// admin data (with names only)
static void fill_connections_from_config(AdminData &admin_data);
static void fill_connections_from_config(api::admin::AdminData &admin_data);
////////////////////////////////////////////
// account accessors ///////////////////////
////////////////////////////////////////////
// finds the account from the name and returns
// a mutable pointer to the account
static BTSAccount *find_account(
AdminData &data,
static api::admin::BTSAccount *find_account(
api::admin::AdminData &data,
const std::string &acct_name,
bool include_deleted = false);
static std::map<std::string, uint32_t> get_clearing_account_map(
const AdminData &);
const api::admin::AdminData &);
// finds the account from a clearing account (and its venue)
static BTSAccount const *find_account(
const AdminData &data,
static api::admin::BTSAccount const *find_account(
const api::admin::AdminData &data,
const std::string &clearing_account,
bts::api::Venue);
api::Venue);
// same as above for const pointer
static const BTSAccount *find_account(
const AdminData &data,
static const api::admin::BTSAccount *find_account(
const api::admin::AdminData &data,
const std::string &acct_name,
bool include_deleted = false);
// finds the account from the id and returns
// a mutable pointer to the account
static BTSAccount *find_account(
AdminData &data, uint32_t id, bool include_deleted = false);
static api::admin::BTSAccount *find_account(
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
// like above but returns a const pointer
static const BTSAccount *find_account(
const AdminData &data, uint32_t id, bool include_deleted = false);
static const api::admin::BTSAccount *find_account(
const api::admin::AdminData &data,
uint32_t id,
bool include_deleted = false);
// this function is an indirection layer for account access
// so deletions are dealt in a more deliberate manner
static std::vector<const BTSAccount *> accounts(
const AdminData &data, bool include_deleted);
static std::vector<BTSAccount *> accounts_mutable(
AdminData &data, bool include_deleted);
static std::vector<const api::admin::BTSAccount *> accounts(
const api::admin::AdminData &data, bool include_deleted);
static std::vector<api::admin::BTSAccount *> accounts_mutable(
api::admin::AdminData &data, bool include_deleted);
// finds the max id amongts all the existing accounts
static uint32_t find_max_acct_id(const AdminData &data);
static uint32_t find_max_acct_id(const api::admin::AdminData &data);
// gets the account data for cme (and goes up the hierarchy
// if needed)
static const CMEAccountData *get_account_cme_data(
const BTSAccount &account, const AdminData &data);
static const ICEAccountData *get_account_ice_data(
const BTSAccount &account, const AdminData &data);
static const SIMAccountData *get_account_sim_data(
const BTSAccount &account, const AdminData &data);
static const api::admin::CMEAccountData *get_account_cme_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
static const api::admin::ICEAccountData *get_account_ice_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
static const api::admin::SIMAccountData *get_account_sim_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
// returns true if an account has a connection
bool static account_has_connection(
const BTSAccount &account, const std::string &connection_name);
const api::admin::BTSAccount &account,
const std::string &connection_name);
// finds the external/clearing account associated
// with an account
static std::string find_account_exchange_account(
const AdminData &data,
const BTSAccount &account,
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
// returns a map of the accounts vs their 'is_active'
static std::map<uint32_t, bool> accounts_map(const AdminData &admin_data);
static std::map<uint32_t, bool> accounts_map(
const api::admin::AdminData &admin_data);
// validates that the update to the account is valid
static void validate_account_update(
const AdminData &admin_data,
const BTSAccount &updated_account,
AdminServiceOperationResult &result);
const api::admin::AdminData &admin_data,
const api::admin::BTSAccount &updated_account,
api::admin::AdminServiceOperationResult &result);
// validates that the account exchange changes are valid
// (ex > 12 characters for accounts at CME)
static void validate_account_exchange_data(
const BTSAccount &account, AdminServiceOperationResult &out_result);
static RoutingRecord get_routing_record(
const AdminData &data,
const api::admin::BTSAccount &account,
api::admin::AdminServiceOperationResult &out_result);
static api::orderman::RoutingRecord get_routing_record(
const api::admin::AdminData &data,
const std::string &account_name,
const std::string &user_name,
const bts::api::Security &sec,
AdminServiceOperationResult &out_result);
static RoutingRecord get_routing_record(
const AdminData &data,
const api::Security &sec,
api::admin::AdminServiceOperationResult &out_result);
static api::orderman::RoutingRecord get_routing_record(
const api::admin::AdminData &data,
uint32_t account_id,
uint32_t user_id,
const bts::api::Security &sec,
AdminServiceOperationResult &out_result);
const api::Security &sec,
api::admin::AdminServiceOperationResult &out_result);
////////////////////////////////////////////
// account mutators ////////////////////////
////////////////////////////////////////////
static BTSAccount *create_account(
AdminData &data,
static api::admin::BTSAccount *create_account(
api::admin::AdminData &data,
uint32_t parent_account_id,
const std::string &account_name,
const std::string &description,
bool is_active,
AdminServiceCreateAccountResult &result);
api::admin::AdminServiceCreateAccountResult &result);
// writes over the account data completely
static void update_account(
AdminData &data,
const BTSAccount &updated_account,
AdminServiceOperationResult &result);
api::admin::AdminData &data,
const api::admin::BTSAccount &updated_account,
api::admin::AdminServiceOperationResult &result);
static void delete_account(
AdminData &admin_data,
const IDAPIArgument &id,
AdminServiceOperationResult &result);
api::admin::AdminData &admin_data,
const api::admin::IDAPIArgument &id,
api::admin::AdminServiceOperationResult &result);
static void set_account_exchange_data(
BTSAccount *account, const ExchangeAccountData &exchange_data);
api::admin::BTSAccount *account,
const api::admin::ExchangeAccountData &exchange_data);
// this is not a generally useful function but since
// connection are in config, this cleans up the account's
// connections and syncs it with the list in AdminData
// connections and syncs it with the list in api::admin::AdminData
static void sync_account_connections(
const AdminData &data, BTSAccount &account);
const api::admin::AdminData &data, api::admin::BTSAccount &account);
////////////////////////////////////////////
// group accessors /////////////////////////
////////////////////////////////////////////
// given an id, finds the mutable pointer to a group
static BTSUserGroup *find_group(AdminData &data, uint32_t id);
static api::admin::BTSUserGroup *find_group(
api::admin::AdminData &data, uint32_t id);
// given an id, finds the const pointer to a group
static const BTSUserGroup *const find_group(
const AdminData &data, uint32_t id);
static const api::admin::BTSUserGroup *const find_group(
const api::admin::AdminData &data, uint32_t id);
// given a name, finds the mutable pointer to a group
static BTSUserGroup *find_group(
AdminData &data, const std::string &group_name);
static api::admin::BTSUserGroup *find_group(
api::admin::AdminData &data, const std::string &group_name);
// finds the max id out of all the groups
static uint32_t find_max_group_id(const AdminData &data);
static uint32_t find_max_group_id(const api::admin::AdminData &data);
// travels up the group tree until it finds a populated cme data
static const CMEUserData *get_group_cme_data(
const BTSUserGroup &group, const AdminData &data);
static const ICEUserData *get_group_ice_data(
const BTSUserGroup &group, const AdminData &data);
static const SIMUserData *get_group_sim_data(
const BTSUserGroup &group, const AdminData &data);
static const UserAccountPermissions *const get_group_user_permissions(
const bts::api::admin::AdminData &admin_data, uint32_t group_id);
static const api::admin::CMEUserData *get_group_cme_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::ICEUserData *get_group_ice_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::SIMUserData *get_group_sim_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::UserAccountPermissions *const
get_group_user_permissions(
const api::admin::AdminData &admin_data, uint32_t group_id);
////////////////////////////////////////////
// group mutators/ /////////////////////////
////////////////////////////////////////////
static BTSUserGroup *create_group(
AdminData &data,
static api::admin::BTSUserGroup *create_group(
api::admin::AdminData &data,
uint32_t parent_group_id,
const std::string &group_name,
AdminServiceOperationResult &result);
api::admin::AdminServiceOperationResult &result);
////////////////////////////////////////////
// user accessors //////////////////////////
////////////////////////////////////////////
static BTSUser *find_user(
AdminData &data,
static api::admin::BTSUser *find_user(
api::admin::AdminData &data,
const std::string &user_name,
bool include_deleted = false);
static const BTSUser *find_user(
const AdminData &data,
static const api::admin::BTSUser *find_user(
const api::admin::AdminData &data,
const std::string &user_name,
bool include_deleted = false);
static BTSUser *find_user(
AdminData &data, uint32_t id, bool include_deleted = false);
static const BTSUser *const find_user(
const AdminData &data, uint32_t id, bool include_deleted = false);
static std::vector<const BTSUser *> users(
const AdminData &data, bool include_deleted);
static std::vector<BTSUser *> users_mutable(
AdminData &data, bool include_deleted);
static uint32_t find_max_user_id(const AdminData &data);
static const CMEUserData *get_user_cme_data(
const BTSUser &user, const AdminData &data);
static const ICEUserData *get_user_ice_data(
const BTSUser &user, const AdminData &data);
static const SIMUserData *get_user_sim_data(
const BTSUser &user, const AdminData &data);
static api::admin::BTSUser *find_user(
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
static const api::admin::BTSUser *const find_user(
const api::admin::AdminData &data,
uint32_t id,
bool include_deleted = false);
static std::vector<const api::admin::BTSUser *> users(
const api::admin::AdminData &data, bool include_deleted);
static std::vector<api::admin::BTSUser *> users_mutable(
api::admin::AdminData &data, bool include_deleted);
static uint32_t find_max_user_id(const api::admin::AdminData &data);
static const api::admin::CMEUserData *get_user_cme_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
static const api::admin::ICEUserData *get_user_ice_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
static const api::admin::SIMUserData *get_user_sim_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
// finds the "exchange user" (ex: "operator_id" at CME or "authorized_trader"
// at ICE) for a user
static std::string find_user_exchange_id(
const AdminData &data,
const BTSUser &user,
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
// i. uses the exchange and exchange_user to reverse lookup the user(s)
// ii. uses the exchange, connection and exchange_account to reverse lookup
// the account(s) iii. finds the first account from ii that a user from i is
// authorized to trade
static std::string find_user_company_id(
const AdminData &data,
const BTSUser &user,
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
static std::pair<const BTSAccount *, const BTSUser *>
static std::pair<const api::admin::BTSAccount *, const api::admin::BTSUser *>
find_user_account_matches(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
const std::string &connection,
const std::string &exchange_account,
const std::string &exchange_user,
@@ -284,100 +302,108 @@ class AdminServiceUtils {
// for a given user finds the account permissions (if permissions are
// derived from the group, it traverses the group hierarcy to find the
// correct permissions.
static const UserAccountPermissions *get_user_permissions(
const AdminData &admin_data, uint32_t user_id);
static const api::admin::UserAccountPermissions *get_user_permissions(
const api::admin::AdminData &admin_data, uint32_t user_id);
// for a given user, fills in the associated_accounts ref arg
// with the accounts the user has permission to trade in
static void find_user_trading_accounts(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
uint32_t user_id,
std::vector<const BTSAccount *> &associated_accounts);
std::vector<const api::admin::BTSAccount *> &associated_accounts);
// for a given account, fills in the associated_users ref arg
// with the users that can trade into the account
static void find_users_for_trading_account(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
uint32_t account_id,
std::vector<const BTSUser *> &associated_users);
std::vector<const api::admin::BTSUser *> &associated_users);
// validates that changes in user data satisfy all the
// constraints
static void validate_user_update(
const AdminData &admin_data,
const BTSUser &updated_user,
AdminServiceOperationResult &result);
const api::admin::AdminData &admin_data,
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &result);
// vaidates the exchange part of user data updates
static void validate_user_exchange_data(
const BTSUser &updated_user, AdminServiceOperationResult &out_result);
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &out_result);
static void get_md_entitlement_report(
const AdminData &admin_data,
std::vector<MDEntitlementReportUnit> &entitlements);
const api::admin::AdminData &admin_data,
std::vector<api::admin::MDEntitlementReportUnit> &entitlements);
// Gets a reporting unit out of CMEMicData
static MDEntitlementReportUnit get_cme_md_entitlement_unit(
const BTSUser &user, const CMEMicData &cme_mic_data);
static MDEntitlementReportUnit get_ice_md_entitlement_unit(
const BTSUser &user, const ICEMicData &ice_mic_data);
static api::admin::MDEntitlementReportUnit get_cme_md_entitlement_unit(
const api::admin::BTSUser &user,
const api::admin::CMEMicData &cme_mic_data);
static api::admin::MDEntitlementReportUnit get_ice_md_entitlement_unit(
const api::admin::BTSUser &user,
const api::admin::ICEMicData &ice_mic_data);
static void get_user_symbol_permissions(
const AdminData &data,
const BTSUser &user,
std::vector<UserSymbolMDPermissionUnit> &res);
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
std::vector<api::admin::UserSymbolMDPermissionUnit> &res);
////////////////////////////////////////////
// user mutators ///////////////////////////
////////////////////////////////////////////
static BTSUser *create_user(
AdminData &data,
static api::admin::BTSUser *create_user(
api::admin::AdminData &data,
uint32_t parent_group_id,
const std::string &name,
const std::string &password,
const std::string &full_name,
const std::string &company,
bool is_active,
const LicenseData &license_data,
const LicenseEntitlements &license_entitlements,
AdminServiceCreateEntityResult &result);
static BTSUser *update_user(
AdminData &admin_data,
const BTSUser &updated_user,
AdminServiceOperationResult &result);
const api::admin::LicenseData &license_data,
const api::admin::LicenseEntitlements &license_entitlements,
api::admin::AdminServiceCreateEntityResult &result);
static api::admin::BTSUser *update_user(
api::admin::AdminData &admin_data,
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &result);
static void delete_user(
AdminData &admin_data,
const IDAPIArgument &id,
AdminServiceOperationResult &result);
api::admin::AdminData &admin_data,
const api::admin::IDAPIArgument &id,
api::admin::AdminServiceOperationResult &result);
static void set_user_exchange_data(
BTSUser *user, const ExchangeUserData &exchange_user_data);
static void clear_user_accounts(BTSUser *user);
api::admin::BTSUser *user,
const api::admin::ExchangeUserData &exchange_user_data);
static void clear_user_accounts(api::admin::BTSUser *user);
static void set_user_accounts(
BTSUser *user, const UserAccountPermissions &user_account_permissions);
api::admin::BTSUser *user,
const api::admin::UserAccountPermissions &user_account_permissions);
////////////////////////////////////////////
// risk limits //////////////////////////////
////////////////////////////////////////////
// sorting function used by sort_limits to
// sort SecurityScope's
static bool _sort_security_scopes(
const SecurityScope &a, const SecurityScope &b);
const api::admin::SecurityScope &a, const api::admin::SecurityScope &b);
// sorting function used by sort_limits to
// sort ProductScope's
static bool _sort_product_scopes(
const ProductScope &a, const ProductScope &);
const api::admin::ProductScope &a, const api::admin::ProductScope &);
// this sorts the rules such that the most
// specific ones are at the top. this makes it
// easier to match rules with securities/products
static void sort_limits(BTSAccount &account);
// creates a copy of AdminData with sorted rules
static AdminData sort_limits(const AdminData &admin_data);
static void sort_limits(api::admin::BTSAccount &account);
// creates a copy of api::admin::AdminData with sorted rules
static api::admin::AdminData sort_limits(
const api::admin::AdminData &admin_data);
// find the clip size for a security, account
static const uint32_t find_account_security_clip_size(
const BTSAccount &account, const api::Security &security);
const api::admin::BTSAccount &account, const api::Security &security);
// find the position limit for an account, security
static const AccountSecurityPositionLimitUnit *
static const api::admin::AccountSecurityPositionLimitUnit *
find_account_security_position_limit(
const BTSAccount &account, const api::Security &security);
const api::admin::BTSAccount &account, const api::Security &security);
// find the position limit for an account, product
static const AccountProductPositionLimitUnit *
static const api::admin::AccountProductPositionLimitUnit *
find_account_product_position_limit(
const BTSAccount &account, const api::Product &product);
const api::admin::BTSAccount &account, const api::Product &product);
static bool match_security_scope(
const SecurityScope &scope, const api::Security &security);
const api::admin::SecurityScope &scope, const api::Security &security);
static bool match_product_scope(
const ProductScope &scope, const api::Product &product);
const api::admin::ProductScope &scope, const api::Product &product);
static bool match_product(
const api::Security &security, const api::Product &product);
static bool _match_limit_rule_symbol(
@@ -388,13 +414,14 @@ class AdminServiceUtils {
////////////////////////////////////////////
static bool has_id(uint32_t id);
static bool is_symbol_set(const std::string &symbol);
static void bootstrap_data(bts::api::admin::AdminData &data);
static void bootstrap_data(api::admin::AdminData &data);
static void get_spread_prod_qty(
const api::Security &s,
std::map<std::string, double> &out_map,
double mult = 1.0);
// returns true if this spread effects the position of any outrights
static bool residual_pos_spread(const api::Security &s);
};
};
} // namespace admin
} // namespace bts
@@ -65,6 +65,7 @@ function(get_cpp_sources dest)
domain/DomainSecurityUtils.cpp
domain/SecurityCriteria.cpp
domain/SecurityCriteriaClause.cpp
domain/synthetic_book_side.cpp
domain_model/Security.cpp
domain_model/SecurityTests.cpp
domain_model/sod.cpp
@@ -64,6 +64,7 @@ function(get_cpp_sources dest)
domain/DomainSecurityUtils.cpp
domain/SecurityCriteria.cpp
domain/SecurityCriteriaClause.cpp
domain/synthetic_book_side.cpp
domain_model/Security.cpp
domain_model/SecurityTests.cpp
domain_model/sod.cpp
@@ -37,6 +37,7 @@ SymbolLists {
list {
key: "CBOE"
symbol:"SPX"
symbol:"SPXW"
}
list {
key: "BINANCE_US"
-2
View File
@@ -39,8 +39,6 @@ LadderKnobs {
security_timeout_secs: 60
use_algo_param_grid: true
max_mbo_orders: 200
lighten_market_cells: true
mimic_tt: true
scrollable_tick_window_size: 256
}
}
+4 -4
View File
@@ -82,7 +82,7 @@ message LadderKnobs {
bool show_scrollbar = 9;
// DJ wants bid/ask cells with markets (i.e., size > 0) to have a
// lighter background than those without markets.
bool lighten_market_cells = 10;
// bool lighten_market_cells = 10;
// For market cell backgrounds, 0 means 'ignore this color setting' (i.e.,
// employ the ladder's default behavior instead).
@@ -97,9 +97,9 @@ message LadderKnobs {
// Pure Red : 0xFFFF0000
// Pure Blue : 0xFF00FF00
// Pure Green : 0xFF0000FF
uint32 bid_market_cell_background = 11;
uint32 ask_market_cell_background = 12;
bool mimic_tt = 13;
// uint32 bid_market_cell_background = 11;
// uint32 ask_market_cell_background = 12;
// bool mimic_tt = 13;
// Defines the number of price rows rendered by the ladder. The smaller this
// window is, the cheaper it is to render the ladder grid (redraw prices,
// sizes, etc.). However, if the window is too small, scrolling may appear
+2 -2
View File
@@ -1,10 +1,10 @@
#pragma once
#include "BookLevel.h"
#include "BookLevelComp.h"
#include "BookSide.h"
#include "SecurityID.h"
#include "Side.h"
#include "lib/io/JsonReader.h"
#include <boost/container/small_vector.hpp>
#include <nlohmann/json.hpp>
+30
View File
@@ -33,6 +33,11 @@ class BookLevel {
void from_json(const nlohmann::json &j, BookLevel &bl);
void to_json(nlohmann::json &j, const BookLevel &bl);
constexpr bool operator==(const BookLevel &lhs, const BookLevel &rhs);
constexpr bool operator!=(const BookLevel &lhs, const BookLevel &rhs);
std::ostream &operator<<(std::ostream &out, const BookLevel &lvl) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
@@ -70,4 +75,29 @@ inline void to_json(nlohmann::json &j, const BookLevel &bl) {
};
}
constexpr bool operator==(const BookLevel &lhs, const BookLevel &rhs) {
return lhs.direct_quantity == rhs.direct_quantity &&
lhs.total_quantity == rhs.total_quantity &&
lhs.n_orders == rhs.n_orders &&
(lhs.price == rhs.price ||
(lhs.price.is_nan() && rhs.price.is_nan()));
}
constexpr bool operator!=(const BookLevel &lhs, const BookLevel &rhs) {
return !(lhs == rhs);
}
inline std::ostream &operator<<(
std::ostream &out, const BookLevel &lvl) noexcept {
out << lvl.price << " (" << lvl.direct_quantity;
if (lvl.direct_quantity != lvl.total_quantity) {
out << "/" << lvl.total_quantity;
}
if (lvl.n_orders != 0) {
out << ", " << lvl.n_orders;
}
out << ")";
return out;
}
} // namespace bts::domain
+31 -7
View File
@@ -1,18 +1,17 @@
#pragma once
#include "BookLevel.h"
#include "BookLevelComp.h"
#include "BookSide_PopulatedIterator.h"
#include "PriceComp.h"
#include "SecurityID.h"
#include "Side.h"
#include "lib/io/JsonReader.h"
#include <algorithm>
#include <boost/container/small_vector.hpp>
#include <initializer_list>
#include <iostream>
#include <iterator>
#include <limits>
#include <random>
#include <vector>
namespace bts::domain {
@@ -37,6 +36,8 @@ class BookSideT {
BookSideT &operator=(BookSideT &&) noexcept = default;
~BookSideT() = default;
constexpr BookLevel *data();
constexpr const BookLevel *data() const;
[[nodiscard]] constexpr BookLevel const &top() const;
constexpr BookLevel const &operator[](size_t i) const;
constexpr BookLevel &operator[](size_t i);
@@ -196,6 +197,16 @@ constexpr size_t BookSideT<SZ>::max_depth() noexcept {
return SZ;
}
template <std::size_t SZ>
constexpr BookLevel *BookSideT<SZ>::data() {
return _book.data();
}
template <std::size_t SZ>
constexpr const BookLevel *BookSideT<SZ>::data() const {
return _book.data();
}
template <std::size_t SZ>
constexpr const BookLevel &BookSideT<SZ>::top() const {
return _book[0];
@@ -234,12 +245,14 @@ BookSideT<SZ>::end() const noexcept {
}
template <std::size_t SZ>
constexpr typename BookSideT<SZ>::ReverseIterator BookSideT<SZ>::rbegin() noexcept {
constexpr typename BookSideT<SZ>::ReverseIterator
BookSideT<SZ>::rbegin() noexcept {
return _book.rbegin();
}
template <std::size_t SZ>
constexpr typename BookSideT<SZ>::ReverseIterator BookSideT<SZ>::rend() noexcept {
constexpr typename BookSideT<SZ>::ReverseIterator
BookSideT<SZ>::rend() noexcept {
return _book.rend();
}
@@ -250,12 +263,23 @@ constexpr size_t BookSideT<SZ>::size() const noexcept {
template <std::size_t SZ>
constexpr bool BookSideT<SZ>::operator==(BookSideT const &rhs) const {
return _book == rhs._book;
std::size_t i = 0;
for (; i < size(); i++) {
const BookLevel &l = (*this)[i];
if (i == rhs.size()) {
return l.is_empty();
}
const BookLevel &r = rhs[i];
if (l != r) {
return l.is_empty() && r.is_empty();
}
}
return i == rhs.size() || rhs[i].is_empty();
}
template <std::size_t SZ>
constexpr bool BookSideT<SZ>::operator!=(BookSideT const &rhs) const {
return _book != rhs._book;
return !(*this == rhs);
}
template <std::size_t SZ>
+77 -7
View File
@@ -1,16 +1,14 @@
#include "DomainSecurityUtils.h"
#include "FutureSecurity.h"
#include "OptionSecurity.h"
#include "SecurityCommon.h"
#include "SpotSecurity.h"
#include "SpreadSecurity.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/domain/TradeRecord.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/IntPrice.h"
#include "lib/md/PriceSize.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/utils/bts_assert.h"
// #include "lib/domain/SecurityID.h"
// #include "lib/utils/overloaded.h"
// #include <fmt/format.h>
// #include <variant>
namespace bts::domain {
@@ -62,7 +60,7 @@ bts::api::Venue DomainSecurityUtils::convert(api::domain::Venue venue) {
case DOMAIN_VENUE(CBOE):
return api::Venue::VENUE_CBOE;
case DOMAIN_VENUE(TAIFEX):
return api::Venue::VENUE_TAIFEX;
return api::Venue::VENUE_TAIFEX;
case DOMAIN_VENUE(FTX):
return api::Venue::VENUE_FTX;
case DOMAIN_VENUE(BINANCE):
@@ -219,4 +217,76 @@ bool DomainSecurityUtils::get_security_desc(
return true;
}
domain::Price DomainSecurityUtils::convert(md::IntPrice iprice) {
int64_t mantissa = iprice.mantissa();
int8_t exp_diff = iprice.exponent() - domain::Price::exponent();
if (exp_diff != 0) {
mantissa *= std::pow(10, exp_diff);
}
return domain::Price{mantissa};
}
BookLevel DomainSecurityUtils::convert(md::PriceSize ps) {
BookLevel rv;
convert(ps, rv);
return rv;
}
void DomainSecurityUtils::convert(md::PriceSize ps, BookLevel &res) {
res.price = convert(ps.int_price());
res.total_quantity.value() = ps.size();
res.direct_quantity.value() = ps.direct_size();
res.n_orders = 0;
}
void DomainSecurityUtils::convert(
const bts::domain::BookSide &book_side,
bts::md::PriceSize *res,
size_t res_size) {
convert(book_side.data(), book_side.size(), res, res_size);
}
void DomainSecurityUtils::convert(
const BookLevel *book_side,
size_t size,
md::PriceSize *res,
size_t res_size) {
size_t sz = std::min(size, res_size);
size_t i = 0;
for (; i < sz; i++) {
// WARNING: Implied size is discarded here. There would be data loss anyways, since
// bts::md::PriceSize's implied size is a uint, and domain::BookLevel's is a float.
res[i] = md::PriceSize{
md::IntPrice{
book_side[i].price.mantissa(), book_side[i].price.exponent()},
book_side[i].total_quantity.value()};
}
// Set remaining levels to empty.
for (; i < res_size; i++) {
res[i] = bts::md::PriceSize{};
}
}
void DomainSecurityUtils::convert(
const md::PriceSize *book_side, std::size_t len, BookSide &res) {
if (res.size() < len) {
len = res.size();
}
while (len-- != 0) {
convert(book_side[len], res[len]);
}
}
void DomainSecurityUtils::convert(
const co2::TradeMessage &tm, TradeRecord &tr) {
tr.sec_id.value() = tm.key();
tr.time_stamp = core::TimeStamp::from_nanos(tm.time());
tr.agg_side = tm.aggressor_side() == co2::AggressorSide::AGGRESSOR_SIDE_BUY
? AggressorSide::Buy
: AggressorSide::Sell;
tr.trade_quantity.value() = tm.size();
tr.trade_price =
convert(md::IntPrice{tm.mantissa(), static_cast<int8_t>(tm.exponent())});
}
} // namespace bts::domain
+29 -4
View File
@@ -1,18 +1,28 @@
#pragma once
#include "FutureSecurity.h"
#include "OptionSecurity.h"
#include "SpotSecurity.h"
#include "SpreadSecurity.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/domain/SecurityID.h"
#include "lib/domain/BookSide.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/utils/overloaded.h"
#include <fmt/format.h>
#include <variant>
namespace bts::co2 {
class TradeMessage;
}
namespace bts::md {
class IntPrice;
class PriceSize;
} // namespace bts::md
namespace bts::domain {
class TradeRecord;
class DomainSecurityUtils {
public:
static api::domain::Venue convert(bts::api::Venue venue);
@@ -29,6 +39,21 @@ class DomainSecurityUtils {
static std::string get_exchange_id(const bts::api::Security &s);
static bool get_security_desc(
const bts::api::Security &security, std::string &desc);
static Price convert(md::IntPrice iprice);
static BookLevel convert(md::PriceSize ps);
static void convert(md::PriceSize ps, BookLevel &res);
static void convert(
const BookSide &book_side, md::PriceSize *res, std::size_t res_size);
static void convert(
const BookLevel *book_side,
std::size_t size,
md::PriceSize *res,
std::size_t res_size);
static void convert(
const md::PriceSize *book_side, std::size_t len, BookSide &res);
static void convert(const co2::TradeMessage &tm, TradeRecord &tr);
};
} // namespace bts::domain
+2 -2
View File
@@ -1,13 +1,13 @@
#pragma once
#include "AccountID.h"
#include "Price.h"
#include "Quantity.h"
#include "SecurityID.h"
#include "Side.h"
#include "TimeInForce.h"
#include "UserID.h"
#include "lib/domain/cme/GlbxOrderDefinition.h"
#include "lib/domain/deribit/DeribitOrderDefinition.h"
#include <string>
#include <variant>
+20 -21
View File
@@ -5,7 +5,6 @@
#include "lib/logging/logging.h"
#include "lib/utils/ValueWrapper.h"
#include <cstdint>
#include <nlohmann/json.hpp>
namespace bts::domain {
@@ -77,7 +76,7 @@ inline void to_json(nlohmann::json &j, const Quantity &sec_id) {
return lhs; \
}
#define QTY_UINT32_BIN_OP(result_t, op) \
#define QTY_VALUE_BIN_OP(result_t, op) \
constexpr result_t operator op(Quantity lhs, Quantity::Value rhs) { \
return result_t(lhs.value() op rhs); /*NOLINT*/ \
} \
@@ -85,7 +84,7 @@ inline void to_json(nlohmann::json &j, const Quantity &sec_id) {
return result_t(lhs op rhs.value()); \
}
#define QTY_UINT32_BIN_REF_OP(op) \
#define QTY_VALUE_BIN_REF_OP(op) \
inline Quantity &operator op(Quantity &lhs, Quantity::Value rhs) { \
lhs.value() op rhs; \
return lhs; \
@@ -107,29 +106,29 @@ QTY_QTY_BIN_REF_OP(*=)
QTY_QTY_BIN_REF_OP(/=)
// QTY_QTY_BIN_REF_OP(%=)
QTY_UINT32_BIN_OP(bool, !=)
QTY_UINT32_BIN_OP(bool, ==)
QTY_UINT32_BIN_OP(bool, <)
QTY_UINT32_BIN_OP(bool, <=)
QTY_UINT32_BIN_OP(bool, >)
QTY_UINT32_BIN_OP(bool, >=)
QTY_VALUE_BIN_OP(bool, !=)
QTY_VALUE_BIN_OP(bool, ==)
QTY_VALUE_BIN_OP(bool, <)
QTY_VALUE_BIN_OP(bool, <=)
QTY_VALUE_BIN_OP(bool, >)
QTY_VALUE_BIN_OP(bool, >=)
QTY_UINT32_BIN_OP(Quantity, +)
QTY_UINT32_BIN_OP(Quantity, -)
QTY_UINT32_BIN_OP(Quantity, *)
QTY_UINT32_BIN_OP(Quantity, /)
// QTY_UINT32_BIN_OP(Quantity, %)
QTY_VALUE_BIN_OP(Quantity, +)
QTY_VALUE_BIN_OP(Quantity, -)
QTY_VALUE_BIN_OP(Quantity, *)
QTY_VALUE_BIN_OP(Quantity, /)
// QTY_VALUE_BIN_OP(Quantity, %)
QTY_UINT32_BIN_REF_OP(+=)
QTY_UINT32_BIN_REF_OP(-=)
QTY_UINT32_BIN_REF_OP(*=)
QTY_UINT32_BIN_REF_OP(/=)
// QTY_UINT32_BIN_REF_OP(%=)
QTY_VALUE_BIN_REF_OP(+=)
QTY_VALUE_BIN_REF_OP(-=)
QTY_VALUE_BIN_REF_OP(*=)
QTY_VALUE_BIN_REF_OP(/=)
// QTY_VALUE_BIN_REF_OP(%=)
#undef QTY_QTY_BIN_OP
#undef QTY_QTY_BIN_REF_OP
#undef QTY_UINT32_BIN_OP
#undef QTY_UINT32_BIN_REF_OP
#undef QTY_VALUE_BIN_OP
#undef QTY_VALUE_BIN_REF_OP
} // namespace bts::domain
-1
View File
@@ -1,6 +1,5 @@
#include "Security.h"
#include "DomainSecurityUtils.h"
#include "lib/core/TableFormatter.h"
#include "lib/domain/FutureSecurity.h"
#include "lib/domain/OptionSecurity.h"
#include "lib/domain/SpotSecurity.h"
+3 -13
View File
@@ -1,22 +1,12 @@
#pragma once
#include "DomainSecurityUtils.h"
#include "Price.h"
#include "SecurityID.h"
#include "lib/api/domain/CallPut.pb.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/core/TimeStamp.h"
#include "lib/domain/FutureSecurity.h"
#include "lib/domain/LegDefinition.h"
#include "lib/domain/OptionSecurity.h"
#include "lib/domain/Quantity.h"
#include "lib/domain/Side.h"
#include "lib/domain/SpreadSecurity.h"
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/bts_assert.h"
#include <limits>
#include <nlohmann/json.hpp>
#include <string>
@@ -40,7 +30,7 @@ class SecurityCommon {
double min_quantity_increment = 0;
double min_quantity = 0;
double max_quantity = std::numeric_limits<double>::max();
double cash_per_point =1;
double cash_per_point = 1;
SecurityCommon() {}
SecurityCommon(const bts::api::Security &s)
@@ -21,9 +21,6 @@ using std::regex_search;
using std::smatch;
using std::string;
#define CONTAINS(container, value) \
(std::find(container.begin(), container.end(), value) != container.end())
bool SecurityCriteriaClause::is_defined() const {
return !venues.empty() || !types.empty() || !assets.empty() ||
!groups.empty() || !ids.empty() || !names.empty() ||
@@ -1,11 +1,13 @@
#pragma once
#include "Price.h"
#include "Quantity.h"
#include "Security.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain/SecurityID.h"
#include "lib/io/JsonReader.h"
#include "lib/md/IntPrice.h"
#include "lib/md/protos/co2.pb.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <ostream>
+5
View File
@@ -1,4 +1,7 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include <ostream>
namespace bts::domain {
@@ -153,3 +156,5 @@ constexpr Side operator*(Side lhs, Side::Value rhs) noexcept {
}
} // namespace bts::domain
FMT_IMPL(bts::domain::Side, s, "{}", s.name());
+2 -14
View File
@@ -1,12 +1,12 @@
#pragma once
#include "AggressorSide.h"
#include "Price.h"
#include "Quantity.h"
#include "SecurityID.h"
#include "lib/core/TimeStamp.h"
#include "lib/logging/logging.h"
#include "lib/md/IntPrice.h"
#include "lib/md/protos/co2.pb.h"
#include <nlohmann/json.hpp>
namespace bts::domain {
@@ -18,8 +18,6 @@ class TradeRecord {
AggressorSide agg_side = {};
Quantity trade_quantity;
Price trade_price;
TradeRecord() {}
inline TradeRecord(const bts::co2::TradeMessage &tm);
};
void from_json(const nlohmann::json &j, TradeRecord &tr);
@@ -29,16 +27,6 @@ void to_json(nlohmann::json &j, const TradeRecord &tr);
// Definition
//////////////////////////////////////////////////
inline TradeRecord::TradeRecord(const bts::co2::TradeMessage &tm)
: sec_id(tm.key()), time_stamp(tm.time()),
agg_side(
tm.aggressor_side() == co2::AggressorSide::AGGRESSOR_SIDE_BUY
? AggressorSide{AggressorSide::Buy}
: AggressorSide{AggressorSide::Sell}),
trade_quantity(tm.size()),
trade_price(Price::from_double(
bts::md::IntPrice(tm.mantissa(), tm.exponent()).price())) {}
inline void from_json(const nlohmann::json &j, TradeRecord &tr) {
io::JsonReader jr{&j};
// clang-format off
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include "lib/domain/BookSide.h"
#include "lib/domain/Quantity.h"
namespace bts::domain {
class BookSideConsumer {
// Use pointer so copying is cheap
const BookLevel *levels_;
std::size_t size_;
std::size_t curr_{0};
Quantity curr_consumed_{0};
public:
BookSideConsumer() = default;
BookSideConsumer(const BookLevel *levels, std::size_t size)
: levels_(levels), size_(size) {}
template <std::size_t SZ>
BookSideConsumer(
const BookSideT<SZ> *levels,
std::size_t curr = 0,
Quantity curr_consumed = Quantity{0});
BookSideConsumer(const BookSideConsumer &other) = default;
BookSideConsumer &operator=(BookSideConsumer &&other) = default;
BookSideConsumer &operator=(const BookSideConsumer &other) = default;
const BookLevel &operator[](std::size_t idx) const;
const BookLevel *levels() const { return levels_; }
size_t size() const;
bool is_done() const;
void reset();
Quantity available(std::size_t level_limit = 1) const;
std::pair<Quantity, Price> consume(Quantity quantity);
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
template <std::size_t SZ>
inline BookSideConsumer::BookSideConsumer(
const BookSideT<SZ> *levels, std::size_t curr, Quantity curr_consumed)
: levels_(levels), curr_(curr), curr_consumed_(curr_consumed) {}
inline const BookLevel &BookSideConsumer::operator[](std::size_t idx) const {
return levels_[idx];
}
inline size_t BookSideConsumer::size() const { return size_; }
inline bool BookSideConsumer::is_done() const {
if (curr_ >= size_) {
return true;
}
if (curr_ == size_ - 1) {
return curr_consumed_ >= levels_[curr_].total_quantity;
}
return curr_consumed_ >= levels_[curr_].total_quantity &&
levels_[curr_ + 1].total_quantity == 0;
}
inline void BookSideConsumer::reset() {
curr_ = 0;
curr_consumed_.value() = 0;
}
inline Quantity BookSideConsumer::available(std::size_t level_limit) const {
std::size_t end = size_ > curr_ + level_limit ? curr_ + level_limit : size_;
Quantity rv{0};
for (std::size_t i = curr_; i < end; i++) {
Quantity level_qty = levels_[i].total_quantity;
if (i == curr_) {
level_qty -= curr_consumed_;
}
rv += level_qty;
}
return rv;
}
inline std::pair<Quantity, Price> BookSideConsumer::consume(Quantity quantity) {
Quantity consumed{0};
Price price = Price::zero();
for (; curr_ < size_; curr_++) {
Quantity level_qty = levels_[curr_].total_quantity - curr_consumed_;
if (level_qty == 0) {
break;
}
Quantity rem = quantity - consumed;
if (level_qty > rem) {
curr_consumed_ += rem;
consumed = quantity;
price += levels_[curr_].price * rem.value();
break;
}
curr_consumed_.value() = 0;
consumed += level_qty;
price += levels_[curr_].price * level_qty.value();
}
return {consumed, price};
}
} // namespace bts::domain
+116
View File
@@ -0,0 +1,116 @@
#include "synthetic_book_side.h"
#include "lib/domain/book_side_consumer.h"
namespace bts::domain {
void SyntheticBookSide::copy_(const SyntheticBookSide &other) noexcept {
if (this == &other) {
return;
}
leg_qty_.value() = other.leg_qty_.value();
// Copy 'data_'
if (other.data_ != nullptr) {
// Clear 'own_data_'
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = other.size_;
data_ = other.data_;
return;
}
data_ = nullptr;
if (other.size_ == 0) {
// Clear 'own_data_'
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = 0;
return;
}
// Copy 'own_data_'
if (own_data_ == nullptr) {
own_data_ = new BookLevel[other.size_];
} else if (size_ != other.size_) {
delete[] own_data_;
own_data_ = new BookLevel[other.size_];
}
size_ = other.size_;
std::memcpy(own_data_, other.own_data_, size_ * sizeof(BookLevel));
}
void SyntheticBookSide::move_(SyntheticBookSide &&other) noexcept {
if (own_data_ != nullptr) {
delete[] own_data_;
}
leg_qty_.value() = other.leg_qty_.value();
data_ = other.data_;
size_ = other.size_;
own_data_ = other.own_data_;
other.leg_qty_.value() = 0;
other.data_ = nullptr;
other.own_data_ = nullptr;
other.size_ = 0;
}
SyntheticBookSide SyntheticBookSide::add_leg(
SyntheticBookSide other_leg,
BookLevel *synth_bk,
size_t synth_bk_size) const noexcept {
if (leg_qty_.value() == 0) {
return other_leg;
}
if (other_leg.leg_qty_.value() == 0) {
return *this;
}
SyntheticBookSide rv;
rv.leg_qty_.value() = 1;
rv.size_ = synth_bk_size;
if (synth_bk == nullptr) {
rv.own_data_ = synth_bk = new BookLevel[synth_bk_size];
} else {
rv.data_ = synth_bk;
}
BookSideConsumer l1{data(), size_};
BookSideConsumer l2{other_leg.data(), other_leg.size_};
Quantity l1_qty = leg_qty_;
Quantity l2_qty = other_leg.leg_qty_;
std::size_t i = 0;
for (; !l1.is_done() && !l2.is_done() && i < synth_bk_size; i++) {
Quantity leg2_per_leg1 = (l2_qty / l1_qty);
if (leg2_per_leg1 < 0) {
leg2_per_leg1.value() = -leg2_per_leg1.value();
}
Quantity leg1_per_leg2 = (l1_qty / l2_qty);
if (leg1_per_leg2 < 0) {
leg1_per_leg2.value() = -leg1_per_leg2.value();
}
Quantity avail1 = l1.available();
Quantity avail2 = l2.available();
Quantity take1;
Quantity take2;
if ((avail1 / leg1_per_leg2) < (avail2 / leg2_per_leg1)) {
take1 = avail1;
take2 = avail1 * leg2_per_leg1;
} else {
take1 = avail2 * leg1_per_leg2;
take2 = avail2;
}
const auto [qty1, cost1] = l1.consume(take1);
const auto [qty2, cost2] = l2.consume(take2);
Price price1 = (cost1 / qty1.value()) * l1_qty.value();
Price price2 = (cost2 / qty2.value()) * l2_qty.value();
auto &synth_lvl = synth_bk[i];
synth_lvl.price = price1 + price2;
synth_lvl.total_quantity = qty1;
synth_lvl.direct_quantity = qty1;
synth_lvl.n_orders = 0;
}
for (; i < synth_bk_size; i++) {
synth_bk[i] = BookLevel::empty();
}
return rv;
}
} // namespace bts::domain
+112
View File
@@ -0,0 +1,112 @@
#pragma once
#include "lib/domain/BookLevel.h"
#include "lib/domain/Quantity.h"
#include <cstddef>
#include <cstring>
namespace bts::domain {
class SyntheticBookSide {
Quantity leg_qty_{0};
// Either 'data_' or 'own_data_' can be non-null (not both). Only the latter
// is deleted in the destructor.
const BookLevel *data_{nullptr};
BookLevel *own_data_{nullptr};
std::size_t size_{0};
void copy_(const SyntheticBookSide &other) noexcept;
void move_(SyntheticBookSide &&other) noexcept;
public:
constexpr SyntheticBookSide() = default;
constexpr SyntheticBookSide(
Quantity leg_qty, const BookLevel *book_side, std::size_t size);
constexpr ~SyntheticBookSide();
SyntheticBookSide(const SyntheticBookSide &other);
SyntheticBookSide(SyntheticBookSide &&other);
SyntheticBookSide &operator=(const SyntheticBookSide &other);
SyntheticBookSide &operator=(SyntheticBookSide &&other);
constexpr std::size_t size() const;
constexpr const BookLevel *data() const;
/**
* Adds a leg to this one (calls can be chained to get the depth for N legs).
*
* @return SyntheticBookSide whose levels constitute the synthetic depth of
* this and the specified @p leg. Its quantity is always 1.
*/
SyntheticBookSide add_leg(SyntheticBookSide leg) const noexcept;
SyntheticBookSide add_leg(
SyntheticBookSide other_leg,
BookLevel *synth_bk,
size_t synth_bk_size) const noexcept;
/**
* Syntactic sugar for `add_leg`
*/
SyntheticBookSide operator+(SyntheticBookSide other) const noexcept;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
constexpr SyntheticBookSide::SyntheticBookSide(
Quantity leg_qty, const BookLevel *data, std::size_t size)
: leg_qty_(leg_qty), data_(data), own_data_(nullptr), size_(size) {}
constexpr SyntheticBookSide::~SyntheticBookSide() {
leg_qty_.value() = 0;
data_ = nullptr;
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = 0;
}
inline SyntheticBookSide::SyntheticBookSide(const SyntheticBookSide &other) {
copy_(other);
}
inline SyntheticBookSide::SyntheticBookSide(SyntheticBookSide &&other) {
move_(std::move(other));
}
inline SyntheticBookSide &SyntheticBookSide::operator=(
const SyntheticBookSide &other) {
if (this != &other) {
copy_(other);
}
return *this;
}
inline SyntheticBookSide &SyntheticBookSide::operator=(
SyntheticBookSide &&other) {
if (this != &other) {
move_(std::move(other));
}
return *this;
}
constexpr std::size_t SyntheticBookSide::size() const { return size_; }
constexpr const BookLevel *SyntheticBookSide::data() const {
return data_ == nullptr ? own_data_ : data_;
}
inline SyntheticBookSide SyntheticBookSide::add_leg(
SyntheticBookSide other_leg) const noexcept {
return add_leg(other_leg, nullptr, 10);
}
inline SyntheticBookSide SyntheticBookSide ::operator+(
SyntheticBookSide other) const noexcept {
return add_leg(other);
}
} // namespace bts::domain
+1 -4
View File
@@ -1,10 +1,8 @@
#include "Security.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/config/config.h"
#include "lib/config/protos/orders_fills_cleanup.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/common.h"
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <google/protobuf/text_format.h>
@@ -13,7 +11,6 @@ namespace bts {
namespace securities {
using bts::api::Security;
using bts::api::Venue;
using std::optional;
api::SecurityType security_type(api::Security const &security) {
-2
View File
@@ -7,10 +7,8 @@
#include "lib/domain_model/Venue.h"
#include "lib/domain_model/common.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/datetime.h"
#include <cmath>
#include <memory>
namespace boost {
namespace gregorian {
+12
View File
@@ -205,10 +205,20 @@ message SyntheticContract {
enum PriceCalc {
PRICECALC_STANDARD = 0;
PRICECALC_NET_CHANGE = 1;
PRICECALC_NOTIONAL_LEG = 2;
// RATIO = 2;
}
}
message ContractQuantityInfo {
double min_value = 1;
double max_value = 2;
double tick_unit = 3;
double tick_base = 4;
double multiplier = 5;
double max_price = 6;
}
message ContractSpecification {
double low_tick = 10;
double price_break = 20;
@@ -219,6 +229,8 @@ message ContractSpecification {
uint32 main_fraction = 60;
uint32 sub_fraction = 61;
uint32 price_display_format = 62;
repeated ContractQuantityInfo quantity_infos = 63;
repeated ContractQuantityInfo notional_quantity_infos = 64;
}
message Security {
+239 -132
View File
@@ -1,6 +1,4 @@
//#ifdef SPARK_ENABLE_CCAPI
#include "GeneralMarketDataListener.h"
#include "lib/api/domain/Currency.pb.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/core/TimeStamp.h"
@@ -21,7 +19,6 @@
#include "lib/domain/SpreadSecurity.h"
#include "lib/domain/TradeRecord.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/common.h"
#include "lib/logging/logging.h"
#include "lib/md/GeneralSecurityIndex.h"
#include "lib/md/IMarketDataListener.h"
@@ -29,28 +26,19 @@
#include "lib/md/MDProducer.h"
#include "lib/md/MDProducerHandle.h"
#include "lib/md/PriceSize.h"
#include "lib/md/SecurityIndex.h"
#include "lib/md/Shm/FixedSecurity.h"
#include "lib/md/Shm/MDStructure.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/orderman/actors/SODPosition.h"
#include "lib/orderman/gateways/sim/SimExecutionGateway.h"
#include "lib/orderman/gateways/sim/SimExecutionGatewayFactory.h"
#include "lib/orderman/orderman_utils/Book.h"
#include "lib/os/os.h"
#include "lib/persistence/IDGenerator.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/bts_fatal.h"
#include <cstddef>
#include <cstdio>
#include <memory>
#include <bits/stdint-uintn.h>
#include <cstdio>
#include <google/protobuf/timestamp.pb.h>
#include <google/protobuf/util/time_util.h>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
@@ -80,74 +68,88 @@ struct CCSecSearchResult {
struct IncrementalBookData {
std::map<domain::SecurityID, domain::Book> book_map;
bool update_incremental(const IMarketDataListener::BookOp &op) {
//DONT NEED THIS?
// Validate inputs first
if (op.price <= 0.0 || op.quantity < 0) {
LOG(md, Error, "Invalid input - Price: {}, Quantity: {}", op.price, op.quantity);
return false;
}
bool update_incremental(const IMarketDataListener::BookOp &op) {
//DONT NEED THIS?
// Validate inputs first
if (op.price <= 0.0 || op.quantity < 0) {
LOG(md, Error, "Invalid input - Price: {}, Quantity: {}", op.price,
op.quantity);
return false;
}
// Logging setup
//DO WE NEED TO PUT THESE IN A STRING
std::string side_str = (op.side == domain::Side::Buy) ? "Buy" : "Sell";
std::string op_type_str;
switch (op.op_type) {
case IMarketDataListener::BookOpType::Add: op_type_str = "Add"; break;
case IMarketDataListener::BookOpType::MODIFY: op_type_str = "Modify"; break;
case IMarketDataListener::BookOpType::REMOVE: op_type_str = "Delete"; break;
default: op_type_str = "Unknown"; break;
}
//CAN WE DO THIS WITHOUT ASSIGNING TO STRINGS
std::stringstream op_details;
op_details << "Operation: Price: " << op.price << ", Quantity: " << op.quantity
<< ", Side: " << side_str << ", Type: " << op_type_str << "\n";
// Logging setup
//DO WE NEED TO PUT THESE IN A STRING
std::string side_str = (op.side == domain::Side::Buy) ? "Buy" : "Sell";
std::string op_type_str;
switch (op.op_type) {
case IMarketDataListener::BookOpType::Add:
op_type_str = "Add";
break;
case IMarketDataListener::BookOpType::MODIFY:
op_type_str = "Modify";
break;
case IMarketDataListener::BookOpType::REMOVE:
op_type_str = "Delete";
break;
default:
op_type_str = "Unknown";
break;
}
//CAN WE DO THIS WITHOUT ASSIGNING TO STRINGS
std::stringstream op_details;
op_details << "Operation: Price: " << op.price
<< ", Quantity: " << op.quantity << ", Side: " << side_str
<< ", Type: " << op_type_str << "\n";
LOG(md, Info, "Book Increment: {}", op_details.str());
LOG(md, Info, "Book Increment: {}", op_details.str());
// Get book and validate
auto book = get_book(op.id);
if (book == nullptr) {
LOG(md, Error, "Book not found for id: {}", op.id);
return false;
}
// Get book and validate
auto book = get_book(op.id);
if (book == nullptr) {
LOG(md, Error, "Book not found for id: {}", op.id);
return false;
}
// Get correct side of the book
auto& book_side = (op.side == domain::Side::Buy) ? book->bids() : book->asks();
// Get correct side of the book
auto &book_side =
(op.side == domain::Side::Buy) ? book->bids() : book->asks();
// Log initial state
std::stringstream book_before;
book_before << "Book before update (" << side_str << " side):\n";
for (const auto& level : book_side) {
book_before << "Price: " << level.price << ", Quantity: " << level.total_quantity << "\n";
}
// Log initial state
std::stringstream book_before;
book_before << "Book before update (" << side_str << " side):\n";
for (const auto &level : book_side) {
book_before << "Price: " << level.price
<< ", Quantity: " << level.total_quantity << "\n";
}
/*-----------*/
if(op.side == domain::Side::Buy){
switch(op.op_type){
case IMarketDataListener::BookOpType::Add:
{
/*-----------*/
if (op.side == domain::Side::Buy) {
switch (op.op_type) {
case IMarketDataListener::BookOpType::Add: {
int insert_position = 0;
for (size_t i = 0; i < book_side.size(); i++){//not sure how to access book
if(book_side[i].price != 0 && book_side[i].price > op.price ){
for (size_t i = 0; i < book_side.size();
i++) { //not sure how to access book
if (book_side[i].price != 0 && book_side[i].price > op.price) {
//book_side is the book we have ?
//op.price is what we received ?
insert_position++;
continue;
}else {//need j to be last level
for (size_t j =fmin(book_side.size()-1,5-1); j > i; j--){
book_side[j] = book_side[j-1];
} else { //need j to be last level
for (size_t j = fmin(book_side.size() - 1, 5 - 1); j > i;
j--) {
book_side[j] = book_side[j - 1];
}
break;
}
}
book_side[insert_position].price = op.price;
book_side[insert_position].total_quantity = op.quantity;//dont know how to add quantity
book_side[insert_position].total_quantity =
op.quantity; //dont know how to add quantity
break;
}
case IMarketDataListener::BookOpType::MODIFY:{
for(size_t i = 0; i < fmin(book_side.size(),5); i++){
if(book_side[i].price != 0 && book_side[i].price == op.price){
case IMarketDataListener::BookOpType::MODIFY: {
for (size_t i = 0; i < fmin(book_side.size(), 5); i++) {
if (book_side[i].price != 0 && book_side[i].price == op.price) {
//want to update quantity if price is a match
book_side[i].total_quantity = op.quantity;
break;
@@ -155,103 +157,113 @@ bool update_incremental(const IMarketDataListener::BookOp &op) {
}
break;
}
case IMarketDataListener::BookOpType::REMOVE:{
case IMarketDataListener::BookOpType::REMOVE: {
int found_position = -1;
for (size_t i = 0; i < fmin(book_side.size(),5); i++){
if(book_side[i].price == op.price){//trying to find based on price only
for (size_t i = 0; i < fmin(book_side.size(), 5); i++) {
if (book_side[i].price ==
op.price) { //trying to find based on price only
found_position = i;
break;
}
}
size_t level = found_position;
//would've had 4 here; want to be at last level
for(size_t i = found_position; i <fmin(book_side.size(),5); i++){
book_side[i] = book_side[i+1];
//would've had 4 here; want to be at last level
for (size_t i = found_position; i < fmin(book_side.size(), 5);
i++) {
book_side[i] = book_side[i + 1];
level++;
}
if(found_position != -1){
if (found_position != -1) {
//trying to set last level to 0
book_side[level].total_quantity =domain::Quantity(0);
book_side[level].total_quantity = domain::Quantity(0);
}
break;
}
default:
LOG(md,Error, "In the Default Case for IMarketDataListener::BookOp {}");
}
}else if (op.side == domain::Side::Sell){
switch(op.op_type){
case IMarketDataListener::BookOpType::Add:{
LOG(md, Error,
"In the Default Case for IMarketDataListener::BookOp {}");
}
} else if (op.side == domain::Side::Sell) {
switch (op.op_type) {
case IMarketDataListener::BookOpType::Add: {
int insert_position = 0;
for (int i = 0; i < fmin(book_side.size(),5); i++) {
if (book_side[i].price != 0 && book_side[i].price < op.price) {
insert_position++;
continue;
}
}//need i to be at the last level
for (int i = fmin(book_side.size()-1,5-1); i > insert_position; i--) {
book_side[i].price = book_side[i - 1].price;
book_side[i].total_quantity = book_side[i - 1].total_quantity;
for (int i = 0; i < fmin(book_side.size(), 5); i++) {
if (book_side[i].price != 0 && book_side[i].price < op.price) {
insert_position++;
continue;
}
} //need i to be at the last level
for (int i = fmin(book_side.size() - 1, 5 - 1); i > insert_position;
i--) {
book_side[i].price = book_side[i - 1].price;
book_side[i].total_quantity = book_side[i - 1].total_quantity;
}
book_side[insert_position].price = op.price;
book_side[insert_position].total_quantity = op.quantity;
book_side[insert_position].total_quantity = op.quantity;
break;
}
case IMarketDataListener::BookOpType::MODIFY:{
for (int i = 0; i < fmin(book_side.size(),5); i++) {
if (book_side[i].price != 0 && book_side[i].price == op.price) {
// For modify, we might want to update or keep the same value
book_side[i].price = op.price;
book_side[i].total_quantity = op.quantity;
break;
}
}break;
case IMarketDataListener::BookOpType::MODIFY: {
for (int i = 0; i < fmin(book_side.size(), 5); i++) {
if (book_side[i].price != 0 && book_side[i].price == op.price) {
// For modify, we might want to update or keep the same value
book_side[i].price = op.price;
book_side[i].total_quantity = op.quantity;
break;
}
}
break;
}
case IMarketDataListener::BookOpType::REMOVE: {
int found_position = -1;
for (size_t i = 0; i < fmin(book_side.size(),5); i++){
if(book_side[i].price == op.price){
for (size_t i = 0; i < fmin(book_side.size(), 5); i++) {
if (book_side[i].price == op.price) {
found_position = i;
break;
}
}
size_t level =found_position;
for (int i = found_position; i < fmin(book_side.size(),5); i++){
book_side[i].price = book_side[i+1].price;
book_side[i].total_quantity = book_side[i+1].total_quantity;
size_t level = found_position;
for (int i = found_position; i < fmin(book_side.size(), 5); i++) {
book_side[i].price = book_side[i + 1].price;
book_side[i].total_quantity = book_side[i + 1].total_quantity;
level++;
}
if(found_position != -1){
book_side[fmin(book_side.size()-1,level)].total_quantity = domain::Quantity(0);
if (found_position != -1) {
book_side[fmin(book_side.size() - 1, level)].total_quantity =
domain::Quantity(0);
}
break;
}
default:
LOG(md, Error, "In the Default Case for IMarketDataListener::BookOp {}");
LOG(md, Error,
"In the Default Case for IMarketDataListener::BookOp {}");
break;
}
}
/*---------------------*/
int counter = 0;
for (auto &level : book_side) {
if (counter > 4) {
level.total_quantity = domain::Quantity(0);
}
counter++;
}
/*---------------------*/
int counter =0;
for(auto &level:book_side){
if(counter >4){
level.total_quantity =domain::Quantity(0);
// Log final state
std::stringstream book_after;
book_after << "Book after update (" << side_str << " side):\n";
for (const auto &level : book_side) {
book_after << "Price: " << level.price
<< ", Quantity: " << level.total_quantity << "\n";
}
counter++;
std::string book_before_after_op =
book_before.str() + "\n" + book_after.str() + "\n" + op_details.str();
LOG(md, Diag, "Book Increment desc: {}", book_before_after_op);
bts_assert_msgf(
book->is_sorted(), "book not sorted {}", book_before_after_op);
return true;
}
// Log final state
std::stringstream book_after;
book_after << "Book after update (" << side_str << " side):\n";
for (const auto& level : book_side) {
book_after << "Price: " << level.price << ", Quantity: " << level.total_quantity << "\n";
}
std::string book_before_after_op = book_before.str() + "\n" + book_after.str() + "\n" + op_details.str();
LOG(md, Diag, "Book Increment desc: {}", book_before_after_op);
bts_assert_msgf(book->is_sorted(), "book not sorted {}", book_before_after_op);
return true;
}
domain::Book const *get_const_book(domain::SecurityID id) const {
auto it = book_map.find(id);
if (it == book_map.end()) {
@@ -301,8 +313,7 @@ struct GeneralMarketDataListener::PImpl {
GeneralSecurityIndex &index,
MDProducer &md_producer,
std::shared_ptr<bts::orderman::SimExecutionGateway> sim_ex_gwy)
: venue(venue), index(index),
md_producer(md_producer),
: venue(venue), index(index), md_producer(md_producer),
sim_ex_gwy(std::move(sim_ex_gwy)) {
if (this->sim_ex_gwy != nullptr) {
this->sim_ex_gwy->set_producers(&this->index);
@@ -464,7 +475,7 @@ void GeneralMarketDataListener::on_book(
void GeneralMarketDataListener::on_book_increment(
const IMarketDataListener::BookOp &book_op) {
if(!me->inc_book_data.update_incremental(book_op)){
if (!me->inc_book_data.update_incremental(book_op)) {
return;
}
domain::Book const *book_ptr = me->inc_book_data.get_const_book(book_op.id);
@@ -635,6 +646,81 @@ void GeneralMarketDataListener::_commit_security(const FixedSecurity &sec) {
handle.security().numeric_id, me->index.n_secs());
}
static bool get_qty_info(
double qty_incr, float &qty_tick_unit, float &qty_tick_base) {
float qty_tick_denom = 1.0 / qty_incr;
int qty_tick_denomi = static_cast<int>(qty_tick_denom);
if (qty_tick_denom != qty_tick_denomi) {
return false;
}
switch (static_cast<int>(qty_tick_denom)) {
case 1:
qty_tick_unit = 1;
qty_tick_base = 1;
break;
case 10:
qty_tick_unit = 10;
qty_tick_base = 100;
break;
case 100:
qty_tick_unit = 1;
qty_tick_base = 100;
break;
case 1000:
qty_tick_unit = 1;
qty_tick_base = 1000;
break;
case 10000:
qty_tick_unit = 1;
qty_tick_base = 10000;
break;
case 100000:
qty_tick_unit = 1;
qty_tick_base = 100000;
break;
case 1000000:
qty_tick_unit = 1;
qty_tick_base = 1000000;
break;
case 4:
qty_tick_unit = 25;
qty_tick_base = 100;
break;
case 40:
qty_tick_unit = 25;
qty_tick_base = 1000;
break;
case 400:
qty_tick_unit = 25;
qty_tick_base = 10000;
break;
case 4000:
qty_tick_unit = 25;
qty_tick_base = 100000;
break;
case 20:
qty_tick_unit = 5;
qty_tick_base = 100;
break;
case 200:
qty_tick_unit = 5;
qty_tick_base = 1000;
break;
case 2000:
qty_tick_unit = 5;
qty_tick_base = 10000;
break;
case 20000:
qty_tick_unit = 5;
qty_tick_base = 100000;
break;
default:
bts_assert_msg(false, "Invalid qty tick");
return false;
}
return true;
}
void GeneralMarketDataListener::_copy_security_common(
const bts::domain::Security &from, uint64_t key, FixedSecurity &to) const {
to.key = key;
@@ -648,6 +734,24 @@ void GeneralMarketDataListener::_copy_security_common(
to.display_factor = 0.0;
to.price_break = 0.0;
to.low_tick = to.high_tick = from.security_common.min_price_increment;
auto &sc = from.security_common;
double min_qty = sc.min_quantity;
double qty_incr = sc.min_quantity_increment;
if (!(min_qty > 0)) {
min_qty = qty_incr;
} else if (!(qty_incr > 0)) {
qty_incr = min_qty;
}
double max_qty = sc.max_quantity;
if (qty_incr > 0 && min_qty > 0 && max_qty > min_qty) {
float qty_tick_unit;
float qty_tick_base;
if (get_qty_info(qty_incr, qty_tick_unit, qty_tick_base)) {
to.add_quantity_info(
min_qty, max_qty, qty_tick_unit, qty_tick_base,
std::numeric_limits<float>::max(), false);
}
}
}
uint64_t GeneralMarketDataListener::_generate_security_key(
@@ -685,13 +789,16 @@ void GeneralMarketDataListener::_on_book(
_possibly_match_with_sim_orders(
bts::md::SecurityKey(book.security_id().value()));
LOG(md, Diag,
"GeneralMarketDataListener::_on_book post id: {} bid: {}/{}/{} ask: "
"GeneralMarketDataListener::_on_book sec: {} sec_id: {} handle_id: {} "
"bid: "
"{}/{}/{} ask: "
"{}/{}/{}",
handle->id, me->bids.at(0).price(), me->bids.at(0).direct_size(),
me->bids.at(0).size(), me->asks.at(0).price(),
me->asks.at(0).direct_size(), me->asks.at(0).size());
std::string(handle->handle.security().security_desc()),
book.security_id(), handle->id, me->bids.at(0).price(),
me->bids.at(0).direct_size(), me->bids.at(0).size(),
me->asks.at(0).price(), me->asks.at(0).direct_size(),
me->asks.at(0).size());
handle->handle.update_direct(time.value(), me->bids, me->asks);
}
} // namespace bts::md
//#endif
+2 -1
View File
@@ -1,7 +1,8 @@
#pragma once
#include "lib/md/SecurityIndex.h"
#include <cstdint>
#include <string>
namespace bts::md {
+3 -2
View File
@@ -1,8 +1,9 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/domain/Book.h"
#include "lib/domain/SecurityID.h"
#include "lib/md/PriceSize.h"
#include "lib/utils/TimeStamp.h"
#include <vector>
namespace bts::domain {
-3
View File
@@ -4,9 +4,6 @@
#include "lib/utils/ConcurrentPool.h"
#include "lib/utils/thread_manager.h"
#include <atomic>
#include <functional>
#include <map>
#include <sstream>
#include <thread>
#include <vector>
-4
View File
@@ -3,13 +3,9 @@
#include "IntPrice.h"
#include "MDProducerHandle.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/utils/TimeStamp.h"
#include <btree/map.h>
#include <map>
#include <memory>
#include <tuple>
#include <vector>
-4
View File
@@ -1,6 +1,5 @@
#ifndef _WIN32
#include "MBOServiceServer.h"
#include "ExchangeOrderKey.h"
#include "MBOProcessor.h"
#include "MDClient.h"
@@ -8,11 +7,8 @@
#include "lib/domain_model/Security.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/LockBox.h"
#include <tuple>
#include <unordered_map>
#include <vector>
+1 -1
View File
@@ -2,8 +2,8 @@
#ifndef _WIN32
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/MBOService.server.pb.h"
#include <memory>
#include <optional>
namespace bts {
namespace utils {
+1 -5
View File
@@ -3,16 +3,12 @@
#include "MBOProcessor.h"
#include "MemorySegmentUtils.h"
#include "SecurityCache.h"
#include "lib/config/config.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/md/ShmSegmentName.h"
#include "lib/md/SymbolLoader.h"
#include "lib/utils/SharedList.h"
#include "lib/utils/WaitFreeQueue.h"
#include "lib/utils/bts_chrono.h"
#include "lib/utils/sane_algorithm.h"
#include <algorithm>
#include <btree/map.h>
+1 -3
View File
@@ -1,13 +1,11 @@
#pragma once
#include "lib/utils/bts_export.h"
#include "lib/utils/bts_no_copy.h"
#include "ISyntheticSecurityService.h"
#include "MDConsumerHandle.h"
#include "SecurityPtr.h"
#include "UserSymbol.h"
#include "common.h"
#include "lib/utils/bts_export.h"
#include <functional>
#include <google/protobuf/repeated_field.h>
+31 -155
View File
@@ -1,7 +1,11 @@
#ifndef NO_CPP0X
#include "MDConsumerHandle.h"
#include "MBOProcessor.h"
#include "lib/domain/BookSide.h"
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain/synthetic_book_side.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/os/os.h"
#include <boost/container/small_vector.hpp>
@@ -13,6 +17,10 @@ using small_vector = boost::container::small_vector<T, 4>;
namespace bts {
namespace md {
using bts::domain::DomainSecurityUtils;
using std::make_tuple;
using std::tie;
MDConsumerHandle::MDConsumerHandle() {
os::memset(this, 0, sizeof(MDConsumerHandle));
_type = HANDLETYPE_VOID;
@@ -102,125 +110,6 @@ TimeStamp copy_depth(
/////////////////////////////
// SYNTHETIC DEPTH
/////////////////////////////
struct Level {
double price;
double size;
};
struct DepthTraversal {
std::array<Level, COCONUT_MAX_DEPTH> levels;
size_t head;
api::Side side;
double qty, price_mult;
DepthTraversal(
api::Side side,
double qty,
double price_mult,
const PriceSize *depth,
size_t depth_count)
: levels(), head(0), side(side), qty(qty), price_mult(price_mult) {
// Copy prices into mutable storage
for (size_t x = 0; x < depth_count; ++x) {
levels[x].price = depth[x].price();
levels[x].size = depth[x].size();
}
}
double available_size() const {
double available = 0.0;
auto cur = head;
bool partial = false;
while (cur < levels.size() && available < 1) {
available += levels[cur].size / qty;
if (available < 1.0)
partial = true;
++cur;
}
if (partial && available >= 1.0)
return 1.0;
return available;
}
double consume(double size) {
bts_assert_d(size <= available_size());
double take = size * qty;
double price = 0.0;
while (take > 0) {
if (levels[head].size >= take) {
price += levels[head].price * price_mult * (take / qty);
levels[head].size -= take;
if (levels[head].size <= 0)
++head;
break;
} else {
double available = levels[head].size;
price += levels[head].price * price_mult * (available / qty);
levels[head].size = 0;
++head;
take -= available;
}
}
return price / size;
}
};
void copy_synthetic_side(
size_t count,
api::Side side,
const api::ContractSpecification &spec,
small_vector<DepthTraversal> &leg_travs,
Level *out_price) {
size_t out_index = 0;
bool first = true;
while (out_index < count) {
double price = 0.0;
// Find the leg with the minimum available size
double size = std::numeric_limits<double>().max();
for (const auto &dt : leg_travs) {
auto sz = dt.available_size();
// If we can't get 1.0 from any leg we're done
if (sz <= size)
size = sz;
if (size < 1.0)
break;
}
size = (int64_t)size;
if (size < 1.0) {
// We've run out of book so we nan out any remaining levels and finish
size_t start = first ? out_index : out_index + 1;
for (size_t x = start; x < count; ++x) {
out_price[x].price = os::nan();
out_price[x].size = 0;
}
return;
}
// Take that size from each leg
for (auto &dt : leg_travs) {
double temp = dt.consume(size);
price += temp;
}
bts_assert_d(!os::is_nan(price));
// Round to tick size
price = securities::checked_round_to_quote_price(spec, price, side);
if (first) {
first = false;
out_price[out_index].price = price;
}
if (price == out_price[out_index].price) {
out_price[out_index].size += size;
} else {
out_index += 1;
if (out_index < count) {
out_price[out_index].price = price;
out_price[out_index].size = size;
}
}
}
}
const spark::LegSpreaderParams &get_spreader_params(
const SyntheticStrategy &synth, security_key key) {
@@ -239,10 +128,11 @@ TimeStamp copy_synthetic_depth(
size_t count,
PriceSize *bid,
PriceSize *ask) {
small_vector<DepthTraversal> bid_leg_travs;
small_vector<DepthTraversal> ask_leg_travs;
bid_leg_travs.reserve(synth.legs.size());
ask_leg_travs.reserve(synth.legs.size());
domain::SyntheticBookSide synth_bids;
domain::SyntheticBookSide synth_asks;
// Store bid/ask for each leg (2 BookSides per leg).
std::vector<domain::BookSide> depth_cache;
depth_cache.resize(2 * synth.legs.size());
TimeStamp rv = std::numeric_limits<TimeStamp>::min();
for (size_t x = 0; x < synth.legs.size(); ++x) {
std::array<PriceSize, COCONUT_MAX_DEPTH> bids, asks;
@@ -258,41 +148,27 @@ TimeStamp copy_synthetic_depth(
}
if (r == HandleReadResult::BUSY || t == TimeStamp::invalid())
return TimeStamp::invalid();
// NET Change
if (synth.security.synthetic().price_calc() ==
api::SyntheticContract::PRICECALC_NET_CHANGE) {
auto settle = synth.legs[x].settlement_price();
for (size_t d = 0; d < COCONUT_MAX_DEPTH; ++d) {
bids[d] = {bids[d].int_price() - settle, bids[d].size()};
asks[d] = {asks[d].int_price() - settle, asks[d].size()};
}
}
rv = std::max(rv, t);
const auto &leg = synth.security.synthetic().legs(x);
const auto &eff_bid = leg.side() == api::SIDE_BUY ? bids : asks;
const auto &eff_ask = leg.side() == api::SIDE_BUY ? asks : bids;
bid_leg_travs.push_back(DepthTraversal(
leg.side(), leg.quantity(), leg.price_multiplier(), &eff_bid[0],
eff_bid.size()));
ask_leg_travs.push_back(DepthTraversal(
leg.side(), leg.quantity(), leg.price_multiplier(), &eff_ask[0],
eff_ask.size()));
}
std::array<Level, COCONUT_MAX_DEPTH> scratch{};
const auto &spec = synth.security.contract_spec();
copy_synthetic_side(
count, api::SIDE_BUY, spec, bid_leg_travs, scratch.data());
for (size_t x = 0; x < count; ++x) {
bid[x] = PriceSize_from_double<11>(scratch[x].price, scratch[x].size);
}
memset(scratch.data(), 0, sizeof(scratch));
copy_synthetic_side(
count, api::SIDE_SELL, spec, ask_leg_travs, scratch.data());
for (size_t x = 0; x < count; ++x) {
ask[x] = PriceSize_from_double<11>(scratch[x].price, scratch[x].size);
PriceSize *eff_bid, *eff_ask;
domain::Quantity rel_qty;
tie(eff_bid, eff_ask, rel_qty.value()) =
leg.side() == api::SIDE_BUY
? make_tuple(bids.data(), asks.data(), leg.quantity())
: make_tuple(asks.data(), bids.data(), -leg.quantity());
// Bid
domain::BookSide &cbids = depth_cache.at(x);
DomainSecurityUtils::convert(eff_bid, COCONUT_MAX_DEPTH, cbids);
synth_bids = synth_bids.add_leg({rel_qty, cbids.data(), cbids.size()});
// Ask
domain::BookSide &casks = depth_cache.at(x + synth.legs.size());
DomainSecurityUtils::convert(eff_ask, COCONUT_MAX_DEPTH, casks);
synth_asks = synth_asks.add_leg({rel_qty, casks.data(), casks.size()});
}
DomainSecurityUtils::convert(
synth_bids.data(), synth_bids.size(), bid, count);
DomainSecurityUtils::convert(
synth_asks.data(), synth_asks.size(), ask, count);
return rv;
}
+1 -1
View File
@@ -4,8 +4,8 @@
#include "lib/md/Shm/MDStructure.h"
#include "lib/sparkproto/SparkProtos.pb.h"
#include "lib/utils/bts_export.h"
#include "lib/utils/bts_no_copy.h"
#include "lib/utils/sane_algorithm.h"
#include <atomic>
#include <vector>
-1
View File
@@ -10,7 +10,6 @@
#include "lib/os/os.h"
#include "lib/utils/SharedList.h"
#include "lib/utils/WaitFreeQueue.h"
#include <iomanip>
using namespace std;
-2
View File
@@ -3,11 +3,9 @@
#include "MDProducerChannel.h"
#include "Shm/MDStructure.h"
#include "lib/utils/bts_export.h"
#include "lib/utils/bts_no_copy.h"
#include "lib/utils/datetime.h"
#include <tbb/concurrent_queue.h>
#include <vector>
namespace bts {
namespace co2 {
-2
View File
@@ -1,7 +1,5 @@
#ifndef NO_CPP0X
#include "MemorySegmentUtils.h"
#include "lib/config/config.h"
#include "lib/config/protos/market_data.pb.h"
using namespace std;
-8
View File
@@ -3,17 +3,9 @@
#include "IntPrice.h"
#include "SizeTypes.h"
#include "common.h"
#include "lib/os/os.h"
#include "lib/utils/Optimistic.h"
#include "lib/utils/bts_array_size.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/bts_no_copy.h"
#include "lib/utils/warn_ctrl.h"
#include <atomic>
#include <cmath>
#include <limits>
#include <nlohmann/json.hpp>
namespace bts {
-2
View File
@@ -5,11 +5,9 @@
#include "lib/logging/logging.h"
#include "lib/md/security_type_index.h"
#include "lib/md/venue_index.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/LockBox.h"
#include <btree/map.h>
#include <mutex>
#include <tbb/concurrent_queue.h>
#include <unordered_map>
+1 -1
View File
@@ -3,8 +3,8 @@
#include "MDConsumerHandle.h"
#include "SecurityPtr.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/md/ExchangeSecurityKey.h"
#include <memory>
namespace bts {
-5
View File
@@ -2,14 +2,9 @@
#include "SecurityPtr.h"
#include "UserSymbol.h"
#include "common.h"
#include "lib/api/services/SyntheticSecurityService.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/domain_model/common.h"
#include "lib/md/MDProducerHandle.h"
#include "lib/utils/ResultEnum.h"
#include "lib/utils/datetime.h"
#include <functional>
#include <set>
+3 -11
View File
@@ -1,20 +1,13 @@
#ifndef _WIN32
#include "SecurityIDRecorder.h"
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/persistence/IStore.h"
#include "lib/persistence/SqliteStore.h"
#include <boost/pool/object_pool.hpp>
#include "lib/config/config.h"
#include "lib/logging/logging.h"
#include "lib/utils/Sqlite_utils.h"
#include "lib/utils/checked_arithmetic.h"
#include <boost/filesystem.hpp>
#include <tbb/concurrent_queue.h>
#include "lib/config/protos/market_data.pb.h"
#include "lib/config/protos/system_config.pb.h"
#include <boost/pool/object_pool.hpp>
#include <memory>
#include <sqlite/sqlite3.h>
#include <stdexcept>
@@ -31,7 +24,6 @@ namespace md {
using bts::api::Security;
using bts::api::SecurityType;
using bts::api::Venue;
using bts::config::SystemConfig;
using bts::persistence::IStore;
using bts::persistence::SqliteStore;
+1 -2
View File
@@ -1,10 +1,9 @@
#pragma once
#ifndef _WIN32
#include "MDProducerHandle.h"
#include "common.h"
#include "lib/persistence/IStore.h"
#include "lib/utils/Sqlite_utils.h"
#include <atomic>
#include <functional>
#include <memory>
#include <optional>
+1 -2
View File
@@ -3,14 +3,13 @@
#include "MDProducerHandle.h"
#include "common.h"
#include "lib/logging/logging.h"
#include "lib/md/Shm/MDStructure.h"
#include "lib/utils/bts_no_copy.h"
#include <memory>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
#include <tbb/queuing_rw_mutex.h>
#include <unordered_map>
#include <vector>
namespace bts {
namespace md {
-1
View File
@@ -5,7 +5,6 @@
#include "lib/config/config.h"
#include "lib/config/protos/cme_reference_data.pb.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/utils/parse_enum.h"
#ifndef _WIN32
+1 -2
View File
@@ -1,6 +1,5 @@
#pragma once
#include "lib/domain_model/SecurityKey.h"
#include <memory>
namespace bts {
@@ -12,4 +11,4 @@ namespace md {
typedef std::shared_ptr<const api::Security> SecurityPtr;
}
} // namespace bts
} // namespace bts
-3
View File
@@ -1,13 +1,10 @@
#include "SecurityStore.h"
#include "lib/domain/FutureSecurity.h"
#include "lib/domain/Security.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/LockBox.h"
#include <btree/map.h>
#include <mutex>
#include <tbb/concurrent_queue.h>
#include <unordered_map>
namespace bts::md {
+1 -1
View File
@@ -1,9 +1,9 @@
#pragma once
#include "MDConsumerHandle.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/domain/SecurityID.h"
#include <memory>
namespace bts::domain {
+5 -2
View File
@@ -13,10 +13,13 @@
namespace bts {
namespace co2 {
using bts::md::INT_NAN;
using bts::md::IntPrice;
using bts::md::invalid_security_key;
using bts::md::MDProducerHandle;
using bts::md::security_key;
using datetime::Date;
using std::string;
using std::unique_ptr;
using namespace utils;
enum WorkType { SNAPSHOT, UPDATE };
+4 -4
View File
@@ -1,12 +1,12 @@
#pragma once
#include "MDProducerHandle.h"
#include "lib/utils/bts_export.h"
#include "lib/utils/datetime.h"
#include <memory>
namespace bts {
namespace co2 {
using namespace md;
struct SettlementRecorderImpl;
class SettlementRecorder {
@@ -16,9 +16,9 @@ class SettlementRecorder {
// Put settlement update in a queue
// in do_work() drain queue and update settlement 'depth'
void restore_settlement(MDProducerHandle handle);
void restore_settlement(md::MDProducerHandle handle);
void add_settlement(
MDProducerHandle handle, IntPrice price, datetime::Date date);
md::MDProducerHandle handle, md::IntPrice price, datetime::Date date);
void do_work();
private:
+82 -1
View File
@@ -1,4 +1,5 @@
#include "lib/domain_model/Security.h"
#include "lib/utils/bts_array_size.h"
#include "lib/utils/bts_assert.h"
#ifndef NO_CPP0X
@@ -25,7 +26,7 @@ FixedSecurity::FixedSecurity()
cme_main_fraction(0), cme_sub_fraction(0), cme_price_display_format(0),
ice_price_denominator(0), ice_deal_denominator(0),
ice_settle_denominator(0), is_ice_strip(false), _symbol(), _cfi_code(),
_security_desc(), _alt_symbol() {
_security_desc(), _alt_symbol(), _qty_info_size(0) {
memset(_symbol, 0, BTS_ARRAYSIZE(_symbol));
memset(_alt_symbol, 0, BTS_ARRAYSIZE(_alt_symbol));
memset(_cfi_code, 0, BTS_ARRAYSIZE(_cfi_code));
@@ -39,10 +40,57 @@ FixedSecurity::FixedSecurity()
memset(_ice_hub_alias, 0, BTS_ARRAYSIZE(_ice_hub_alias));
memset(_ice_strip_name, 0, BTS_ARRAYSIZE(_ice_strip_name));
memset(_mic_code, 0, BTS_ARRAYSIZE(_mic_code));
memset(_qty_min_value, 0, BTS_ARRAYSIZE_T(_qty_min_value));
memset(_qty_max_value, 0, BTS_ARRAYSIZE_T(_qty_max_value));
memset(_qty_tick_unit, 0, BTS_ARRAYSIZE_T(_qty_tick_unit));
memset(_qty_tick_base, 0, BTS_ARRAYSIZE_T(_qty_tick_base));
memset(_qty_multiplier, 0, BTS_ARRAYSIZE_T(_qty_multiplier));
memset(_qty_max_price, 0, BTS_ARRAYSIZE_T(_qty_max_price));
memset(_qty_is_notional, 0, BTS_ARRAYSIZE_T(_qty_is_notional));
}
FixedSecurity::~FixedSecurity() {}
bool FixedSecurity::add_quantity_info(
float min_value,
float max_value,
float tick_unit,
float tick_base,
float max_price,
bool is_notional) {
if (_qty_info_size >= QTY_INFO_CAPACITY) {
return false;
}
_qty_min_value[_qty_info_size] = min_value;
_qty_max_value[_qty_info_size] = max_value;
_qty_tick_unit[_qty_info_size] = tick_unit;
_qty_tick_base[_qty_info_size] = tick_base;
_qty_max_price[_qty_info_size] = max_price;
_qty_is_notional[_qty_info_size] = is_notional;
_qty_info_size++;
return true;
}
bool FixedSecurity::get_quantity_info(
uint8_t index,
float &min_value,
float &max_value,
float &tick_unit,
float &tick_base,
float &max_price,
bool &is_notional) const {
if (index >= _qty_info_size) {
return false;
}
min_value = _qty_min_value[index];
max_value = _qty_max_value[index];
tick_unit = _qty_tick_unit[index];
tick_base = _qty_tick_base[index];
max_price = _qty_max_price[index];
is_notional = _qty_is_notional[index];
return true;
}
FixedFutureSecurity::FixedFutureSecurity()
: expiration_year(0), expiration_month(0), expiration_day(0) {
type = api::SECURITYTYPE_FUTURE;
@@ -75,6 +123,29 @@ void get_contract_spec(
spec.set_main_fraction(source.cme_main_fraction);
spec.set_sub_fraction(source.cme_sub_fraction);
spec.set_price_display_format(source.cme_price_display_format);
// Qty info
spec.clear_quantity_infos();
spec.clear_notional_quantity_infos();
float min_value;
float max_value;
float tick_unit;
float tick_base;
float max_price;
bool is_notional;
for (uint8_t i = 0; source.get_quantity_info(
i, min_value, max_value, tick_unit, tick_base, max_price,
is_notional);
i++) {
ContractQuantityInfo *qty_info = is_notional
? spec.add_notional_quantity_infos()
: spec.add_quantity_infos();
qty_info->set_min_value(min_value);
qty_info->set_max_value(max_value);
qty_info->set_tick_unit(tick_unit);
qty_info->set_tick_base(tick_base);
qty_info->set_max_price(max_price);
}
}
Security convert_security(const FixedSecurity &security) {
@@ -365,6 +436,16 @@ void convert_security(
dest.cme_sub_fraction = src.contract_spec().sub_fraction();
dest.cme_price_display_format = src.contract_spec().price_display_format();
for (const auto &q : src.contract_spec().quantity_infos()) {
bts_assert(dest.add_quantity_info(
q.min_value(), q.max_value(), q.tick_unit(), q.tick_base(),
q.max_price(), false));
}
for (const auto &q : src.contract_spec().notional_quantity_infos()) {
bts_assert(dest.add_quantity_info(
q.min_value(), q.max_value(), q.tick_unit(), q.tick_base(),
q.max_price(), true));
}
switch (securitytype_of(src)) {
case SECURITYTYPE_FUTURE: {
FixedFutureSecurity &future = static_cast<FixedFutureSecurity &>(dest);
+33
View File
@@ -85,6 +85,25 @@ class BTS_EXPORT FixedSecurity {
STRING_PROPERTY(ice_strip_name);
STRING_PROPERTY(mic_code);
static constexpr const uint8_t QTY_INFO_CAPACITY = 2;
bool add_quantity_info(
float min_value,
float max_value,
float tick_unit,
float tick_base,
float max_price,
bool is_notional);
bool get_quantity_info(
uint8_t index,
float &min_value,
float &max_value,
float &tick_unit,
float &tick_base,
float &max_price,
bool &is_notional) const;
private:
char _symbol[16]; // CME defines this as a char[6] but I'm padding
char _cfi_code[8]; // CME defines this as char[6]
@@ -100,6 +119,20 @@ class BTS_EXPORT FixedSecurity {
char _ice_hub_alias[80];
char _ice_strip_name[39];
char _mic_code[4];
// The '_qty' arrays collectively represent a list of "ContractQuantityInfo"
// in Security.proto, with a caveat for '_qty_is_notional'.
float _qty_min_value[QTY_INFO_CAPACITY];
float _qty_max_value[QTY_INFO_CAPACITY];
float _qty_tick_unit[QTY_INFO_CAPACITY];
float _qty_tick_base[QTY_INFO_CAPACITY];
float _qty_multiplier[QTY_INFO_CAPACITY];
float _qty_max_price[QTY_INFO_CAPACITY];
// In the proto, each security has 2 "ContractQuantityInfo" lists: quantity_infos and
// notional_quantity_infos. This indicates the list to which each element belongs.
bool _qty_is_notional[QTY_INFO_CAPACITY];
// The number of qty info elements defined in the above arrays.
uint8_t _qty_info_size;
};
class FixedSpotSecurity : public FixedSecurity {
+2 -1
View File
@@ -1,8 +1,9 @@
#pragma once
#include "lib/config/config.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/protos/co2.pb.h"
#include <fmt/format.h>
#include <string>
+6 -2
View File
@@ -1,7 +1,11 @@
#pragma once
#include "lib/utils/ValueWrapper.h"
#include <cstdint>
namespace bts {
namespace md {
using ImpliedSize = utils::ValueWrapper<struct ImpliedSizeTag, uint32_t>;
using ImpliedSize = utils::ValueWrapper<struct ImpliedSizeTag, std::uint32_t>;
}
} // namespace bts
} // namespace bts
+1 -5
View File
@@ -1,13 +1,9 @@
#pragma once
#include "UserSymbol.h"
#include "common.h"
#include "lib/api/md/MarketDataConfig.pb.h"
#include "lib/collections/Contains.h"
#include "lib/config/config.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/config/protos/symbols.pb.h"
#include "lib/core/FMT.h"
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain/SecurityCriteria.h"
#include "lib/domain_model/Security.pb.h"
-7
View File
@@ -1,19 +1,12 @@
#ifndef _WIN32
#include "SyntheticSecurityService.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/PersistenceService.pb.h"
#include "lib/config/config.h"
#include "lib/domain/SecurityCriteria.h"
#include "lib/md/SecurityDB.h"
#include "lib/md/SecurityIDRecorder.h"
#include "lib/md/SymbolLoader.h"
#include "lib/md/common.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/Event.h"
#include <set>
namespace bts {
namespace api {
+2 -1
View File
@@ -1,9 +1,10 @@
#pragma once
#include "ISyntheticSecurityService.h"
#include "lib/api/services/SyntheticSecurityService.pb.h"
#include "lib/api/services/SyntheticSecurityService.server.pb.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/md/common.h"
#include <memory>
#include <unordered_map>
+2 -1
View File
@@ -1,7 +1,8 @@
#pragma once
#include "common.h"
#include "lib/core/FMT_IMPL.h"
#include <sstream>
#include <string>
namespace bts {
+1 -1
View File
@@ -1,9 +1,9 @@
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/utils/bts_assert.h"
#ifndef _WIN32
#include "QueryServer.h"
#include "lib/config/config.h"
#include "lib/domain_model/Security.h"
#include "lib/md/SymbolLoader.h"
#include "lib/utils/AuthToken.h"
#include "lib/utils/Dispatcher.h"
+4 -8
View File
@@ -2,22 +2,18 @@
#include "RelayRecorder.h"
#include "lib/config/config.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/config/protos/system_config.pb.h"
#include "lib/logging/logging.h"
#include "lib/md/SecurityIDRecorder.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/utils/HTTPStream.h"
#include "lib/utils/bts_chrono.h"
#include "lib/utils/datetime.h"
#include "lib/config/protos/market_data.pb.h"
#include "lib/config/protos/system_config.pb.h"
#include "lib/md/SecurityIDRecorder.h"
#include "lib/md/common.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/utils/bts_chrono.h"
#include <boost/filesystem.hpp>
#include <google/protobuf/text_format.h>
#include <memory>
#include <string.h>
#include <thread>
using namespace std;
+305
View File
@@ -0,0 +1,305 @@
#include "MDSource.h"
#include "lib/domain_model/Security.h"
#include "lib/md/cme/MDP3Utils.h"
namespace bts::orderman {
MDSource::MDSource(
md::MDConsumer &consumer,
metrics::MetricsContainer &stats,
trace::PerfLog &log,
md::TradeSummarySignalDispatcher *trade_signal_dispatcher)
: _handles(), _trade_signal_dispatcher(trade_signal_dispatcher),
_pending_handles(), _status_subs(), _md_consumer(consumer),
_book_update(), _ticker_update(), _status_update(), _perf_log(log),
_stats(stats), _loop_timer(), _counter(0) {
_trades_buffer.Reserve(1000);
auto cme_channels = md::get_configured_cme_channels();
for (auto const &c : cme_channels)
for (auto const &s : c.symbols)
_symbol_to_channel[s] = c.id;
}
void MDSource::init() {
LOG(OrderMan, Warn, "Starting MDSource actor");
// TODO: why is this hitting?
bts_assert_msg(
_md_consumer.all_heartbeats_ok(),
"Marketdata heartbeats are bad. MDConsumer will not recover from "
"this condition. Please run 'sparkd md cleanup' and restart sparkd");
std::array<double, 30> buckets;
auto i = 1;
for (auto &b : buckets)
b = (i *= 2);
_loop_timer = std::make_shared<metrics::HistogramMetric<30>>(
"spark_metrics_md_loop_us", buckets);
_stats.publish(_loop_timer);
auto security_count_metric = std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_security_count");
auto synthetic_count_metric = std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_synthetic_count");
auto ticker_channel_cout_metric =
std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_ticker_channel_count");
auto status_count_metric = std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_status_count");
_stats.publish(security_count_metric);
_stats.publish(synthetic_count_metric);
_stats.publish(ticker_channel_cout_metric);
_stats.publish(status_count_metric);
_book_update = make_stream<BookUpdateEvent>();
_trade_signal_update = make_stream<TradeSignalEvent>();
_ticker_update = make_stream<TickerUpdateEvent>();
_status_update = make_stream<StatusUpdateEvent>();
subscribe<MDRequestEvent>(
[this, security_count_metric, synthetic_count_metric,
ticker_channel_cout_metric, status_count_metric](auto const &update) {
if (update.events) {
for (auto const &ev : *update.events) {
auto k = ev.security_key;
switch (ev.md_type) {
case MDRequestType::BOOK:
on_book_sub(k, *synthetic_count_metric);
security_count_metric->set(_handles.size());
break;
case MDRequestType::TICKER:
on_ticker_sub(k, *ticker_channel_cout_metric);
break;
case MDRequestType::STATUS:
on_status_sub(k);
status_count_metric->set(_status_subs.size());
break;
}
}
}
});
security_count_metric->set(_handles.size());
}
void MDSource::on_poll(trace::PerfLog &log) {
auto scoped_do_work = trace::log_scope(
log, {}, "MDSource::on_poll",
utils::TimeStamp::Interval::from_microseconds(2));
// Book Updates
Book book;
auto start = utils::TimeStamp::now();
for (auto &h : _handles) {
auto &handle = h.handle;
if (handle.get_book(
h.last, std::tuple_size<BookSide>(), &book.bid()[0],
&book.ask()[0]) == md::HandleReadResult::SUCCESS) {
book._security_key = SecurityKey(handle.key());
trace::CorID trace{
h.external_event_channel, handle.sequence(), h.last};
_perf_log.log(trace, h.last, "md", handle.key());
_perf_log.log(trace, "md_r", h.last, h.symbol->c_str(), handle.key());
publish(_book_update, book, trace);
}
if (_trade_signal_dispatcher) {
auto tsignal = _trade_signal_dispatcher->poll(handle.key());
if (tsignal.ts > h.last_trade_signal && tsignal.ts > h.last) {
h.last_trade_signal = tsignal.ts;
trace::CorID trace{
h.external_event_channel, tsignal.packet_sn, tsignal.ts};
publish(_trade_signal_update, tsignal, trace);
}
}
}
// Ticker Updates
for (auto &c : _channels) {
auto &channel = c.channel;
_trades_buffer.Clear();
c.cursor = channel.read_trades(_trades_buffer, c.cursor);
for (auto const &trade : _trades_buffer) {
size_t trade_count = 0;
for (auto &per_sec_ev : c.securities) {
if (trade.key() == per_sec_ev.key().value()) {
trade_count++;
per_sec_ev.trades.emplace_back(Trade{
datetime::utc_ll_to_timepoint(trade.time()),
Price(
md::IntPrice(trade.mantissa(), (int8_t)trade.exponent())
.price()),
Quantity(trade.size()), Quantity(trade.total_volume()),
trade.aggressor_side()});
}
}
if (trade_count)
for (auto &per_sec_ev : c.securities)
if (!per_sec_ev.trades.empty()) {
publish(_ticker_update, per_sec_ev);
per_sec_ev.trades.clear();
}
}
}
// Status updates
for (auto &s : _status_subs) {
auto status = s.handle.trading_status();
if (status != s.status) {
s.status = status;
publish(_status_update, SecurityKey{s.handle.key()}, status);
}
}
// Publish stats
auto stop = utils::TimeStamp::now();
_loop_timer->observe((start - stop).nanoseconds());
_counter += 1;
if (_counter % (2 << 20) == 0) {
// Invoke periodic work every 2M calls on on_poll
// This is approximately 20-30 seconds
do_periodic_work();
}
}
void MDSource::do_periodic_work() {
if (!_pending_handles.empty()) {
for (auto iter = _pending_handles.begin(); iter != _pending_handles.end();
++iter) {
if (iter->handle->is_initialized()) {
auto handle = *(iter->handle);
LOG(OrderMan, Info, "Found md for {}. Pending handle removed.",
handle.key());
switch (iter->type) {
case MDRequestType::BOOK:
_handles.push_back(
{utils::TimeStamp(), utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(securities::descriptor(
handle.get_normalized_security()))});
break;
case MDRequestType::STATUS:
for (auto &h : _status_subs) {
// no need to reset status
if (h.handle.key() == handle.key())
break;
}
_status_subs.push_back(
StatusRecord{handle, co2::TRADINGSTATUS_UNKNOWN});
case MDRequestType::TICKER:
// We aren't currently monitoring for pending ticker requests
break;
}
}
}
_pending_handles.erase(
std::remove_if(
_pending_handles.begin(), _pending_handles.end(),
[](const auto &h) { return h.handle->is_initialized(); }),
_pending_handles.end());
}
}
void MDSource::on_book_sub(
SecurityKey sk, metrics::CounterMetric<int> &synthetic_count_metric) {
auto k = sk.value();
for (auto &h : _handles)
if (h.handle.key() == k) {
// we want to resend this MD so that late joiners can
// catch up
h.last = utils::TimeStamp();
return;
}
LOG(OrderMan, Warn, "Starting to publish book updates for security key: {}",
k);
auto mhandle = _md_consumer.get_managed_handle(k);
if (mhandle->is_initialized()) {
auto handle = *mhandle;
_handles.push_back(
{utils::TimeStamp(), utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(
securities::descriptor(handle.get_normalized_security()))});
if (handle.is_synthetic())
synthetic_count_metric.increment();
} else {
// A pending handle is a handle for security we are reasonbly sure
// exists but we haven't yet found live market data for.
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
_pending_handles.push_back({mhandle, MDRequestType::BOOK});
}
}
void MDSource::on_ticker_sub(
SecurityKey sk, metrics::CounterMetric<int> &channel_count_metric) {
auto handle = _md_consumer.get_managed_handle(sk.value());
if (!handle->is_initialized()) {
LOG(OrderMan, Warn,
"Received ticker subscription for security {} which does not have "
"valid market data. Ignoring",
sk.value());
return;
}
auto user_symbol = handle->get_usersymbol();
for (auto &ch : _channels) {
if (ch.user_symbol == user_symbol) {
auto it = std::find_if(
ch.securities.begin(), ch.securities.end(),
[sk](auto const &ev) { return ev.key() == sk; });
if (it == ch.securities.end()) {
// first time we are asked for this security, we will
// publish the trades for it next time around
LOG(OrderMan, Warn,
"Starting to publish ticker updates on user symbol {} for "
"security key: {} (sec count for the symbol: {} )",
user_symbol.to_string(), sk.value(), ch.securities.size() + 1);
ch.securities.push_back(sk);
}
return;
}
}
// this is a channel we are not subscribed to
channel_count_metric.increment();
LOG(OrderMan, Warn,
"Starting to publish ticker updates on user symbol {} for security "
"key: {}",
user_symbol.to_string(), sk.value());
// We do not want to get old trades so we drain the trades buffer at
// this point. This makes the first subscription to a channel
// consistent with subsquent ones in terms of late join semantics.
ChannelRecord c{0, _md_consumer.get_channel(user_symbol), {}, user_symbol};
_trades_buffer.Clear();
c.cursor = c.channel.read_trades(_trades_buffer, c.cursor);
c.securities.push_back(sk);
_channels.push_back(c);
}
void MDSource::on_status_sub(SecurityKey sk) {
auto k = sk.value();
for (auto &h : _status_subs) {
if (h.handle.key() == k) {
// reset status so it get resent next on_poll
h.status = co2::TRADINGSTATUS_UNKNOWN;
return;
}
}
auto handle = _md_consumer.get_managed_handle(k);
if (handle->is_initialized()) {
_status_subs.push_back(StatusRecord{*handle, co2::TRADINGSTATUS_UNKNOWN});
} else {
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
_pending_handles.push_back({handle, MDRequestType::STATUS});
}
}
} // namespace bts::orderman
+8 -299
View File
@@ -1,14 +1,9 @@
#pragma once
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/md/IntPrice.h"
#include "lib/md/MDConsumer.h"
#include "lib/md/MDConsumerHandle.h"
#include "lib/md/cme/MDP3Utils.h"
#include "lib/mercury/actors/Actor.h"
#include "lib/mercury/actors/IPollableActor.h"
#include "lib/mercury/events/TimerEvent.h"
#include "lib/metrics/metrics.h"
#include "lib/orderman/events/BookUpdateEvent.h"
#include "lib/orderman/events/MDRequestEvent.h"
@@ -34,239 +29,17 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
md::MDConsumer &consumer,
metrics::MetricsContainer &stats,
trace::PerfLog &log,
md::TradeSummarySignalDispatcher *trade_signal_dispatcher)
: _handles(), _trade_signal_dispatcher(trade_signal_dispatcher),
_pending_handles(), _status_subs(), _md_consumer(consumer),
_book_update(), _ticker_update(), _status_update(), _perf_log(log),
_stats(stats), _loop_timer(), _counter(0) {
md::TradeSummarySignalDispatcher *trade_signal_dispatcher);
_trades_buffer.Reserve(1000);
auto cme_channels = md::get_configured_cme_channels();
for (auto const &c : cme_channels)
for (auto const &s : c.symbols)
_symbol_to_channel[s] = c.id;
}
void init() override {
LOG(OrderMan, Warn, "Starting MDSource actor");
// TODO: why is this hitting?
bts_assert_msg(
_md_consumer.all_heartbeats_ok(),
"Marketdata heartbeats are bad. MDConsumer will not recover from "
"this condition. Please run 'sparkd md cleanup' and restart sparkd");
std::array<double, 30> buckets;
auto i = 1;
for (auto &b : buckets)
b = (i *= 2);
_loop_timer = std::make_shared<metrics::HistogramMetric<30>>(
"spark_metrics_md_loop_us", buckets);
_stats.publish(_loop_timer);
auto security_count_metric =
std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_security_count");
auto synthetic_count_metric =
std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_synthetic_count");
auto ticker_channel_cout_metric =
std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_ticker_channel_count");
auto status_count_metric = std::make_shared<metrics::CounterMetric<int>>(
"spark_mdsource_status_count");
_stats.publish(security_count_metric);
_stats.publish(synthetic_count_metric);
_stats.publish(ticker_channel_cout_metric);
_stats.publish(status_count_metric);
_book_update = make_stream<BookUpdateEvent>();
_trade_signal_update = make_stream<TradeSignalEvent>();
_ticker_update = make_stream<TickerUpdateEvent>();
_status_update = make_stream<StatusUpdateEvent>();
subscribe<MDRequestEvent>([this, security_count_metric,
synthetic_count_metric,
ticker_channel_cout_metric,
status_count_metric](auto const &update) {
if (update.events) {
for (auto const &ev : *update.events) {
auto k = ev.security_key;
switch (ev.md_type) {
case MDRequestType::BOOK:
on_book_sub(k, *synthetic_count_metric);
security_count_metric->set(_handles.size());
break;
case MDRequestType::TICKER:
on_ticker_sub(k, *ticker_channel_cout_metric);
break;
case MDRequestType::STATUS:
on_status_sub(k);
status_count_metric->set(_status_subs.size());
break;
}
}
}
});
security_count_metric->set(_handles.size());
}
void init() override;
private:
void on_poll(trace::PerfLog &log) override {
void on_poll(trace::PerfLog &log) override;
auto scoped_do_work = trace::log_scope(
log, {}, "MDSource::on_poll",
utils::TimeStamp::Interval::from_microseconds(2));
// Book Updates
Book book;
auto start = utils::TimeStamp::now();
for (auto &h : _handles) {
auto &handle = h.handle;
if (handle.get_book(
h.last, std::tuple_size<BookSide>(), &book.bid()[0],
&book.ask()[0]) == md::HandleReadResult::SUCCESS) {
book._security_key = SecurityKey(handle.key());
trace::CorID trace{
h.external_event_channel, handle.sequence(), h.last};
_perf_log.log(trace, h.last, "md", handle.key());
_perf_log.log(
trace, "md_r", h.last, h.symbol->c_str(), handle.key());
publish(_book_update, book, trace);
}
if (_trade_signal_dispatcher) {
auto tsignal = _trade_signal_dispatcher->poll(handle.key());
if (tsignal.ts > h.last_trade_signal && tsignal.ts > h.last) {
h.last_trade_signal = tsignal.ts;
trace::CorID trace{
h.external_event_channel, tsignal.packet_sn, tsignal.ts};
publish(_trade_signal_update, tsignal, trace);
}
}
}
// Ticker Updates
for (auto &c : _channels) {
auto &channel = c.channel;
_trades_buffer.Clear();
c.cursor = channel.read_trades(_trades_buffer, c.cursor);
for (auto const &trade : _trades_buffer) {
size_t trade_count = 0;
for (auto &per_sec_ev : c.securities) {
if (trade.key() == per_sec_ev.key().value()) {
trade_count++;
per_sec_ev.trades.emplace_back(Trade{
datetime::utc_ll_to_timepoint(trade.time()),
Price(md::IntPrice(
trade.mantissa(), (int8_t)trade.exponent())
.price()),
Quantity(trade.size()), Quantity(trade.total_volume()),
trade.aggressor_side()});
}
}
if (trade_count)
for (auto &per_sec_ev : c.securities)
if (!per_sec_ev.trades.empty()) {
publish(_ticker_update, per_sec_ev);
per_sec_ev.trades.clear();
}
}
}
// Status updates
for (auto &s : _status_subs) {
auto status = s.handle.trading_status();
if (status != s.status) {
s.status = status;
publish(_status_update, SecurityKey{s.handle.key()}, status);
}
}
// Publish stats
auto stop = utils::TimeStamp::now();
_loop_timer->observe((start - stop).nanoseconds());
_counter += 1;
if (_counter % (2 << 20) == 0) {
// Invoke periodic work every 2M calls on on_poll
// This is approximately 20-30 seconds
do_periodic_work();
}
}
void do_periodic_work() {
if (!_pending_handles.empty()) {
for (auto iter = _pending_handles.begin();
iter != _pending_handles.end(); ++iter) {
if (iter->handle->is_initialized()) {
auto handle = *(iter->handle);
LOG(OrderMan, Info, "Found md for {}. Pending handle removed.",
handle.key());
switch (iter->type) {
case MDRequestType::BOOK:
_handles.push_back(
{utils::TimeStamp(), utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(securities::descriptor(
handle.get_normalized_security()))});
break;
case MDRequestType::STATUS:
for (auto &h : _status_subs) {
// no need to reset status
if (h.handle.key() == handle.key())
break;
}
_status_subs.push_back(
StatusRecord{handle, co2::TRADINGSTATUS_UNKNOWN});
case MDRequestType::TICKER:
// We aren't currently monitoring for pending ticker requests
break;
}
}
}
_pending_handles.erase(
std::remove_if(
_pending_handles.begin(), _pending_handles.end(),
[](const auto &h) { return h.handle->is_initialized(); }),
_pending_handles.end());
}
}
void do_periodic_work();
void on_book_sub(
SecurityKey sk, metrics::CounterMetric<int> &synthetic_count_metric) {
auto k = sk.value();
for (auto &h : _handles)
if (h.handle.key() == k) {
// we want to resend this MD so that late joiners can
// catch up
h.last = utils::TimeStamp();
return;
}
LOG(OrderMan, Warn,
"Starting to publish book updates for security key: {}", k);
auto mhandle = _md_consumer.get_managed_handle(k);
if (mhandle->is_initialized()) {
auto handle = *mhandle;
_handles.push_back(
{utils::TimeStamp(), utils::TimeStamp(), handle,
get_external_event_channel(handle.get_usersymbol()),
std::make_unique<std::string>(
securities::descriptor(handle.get_normalized_security()))});
if (handle.is_synthetic())
synthetic_count_metric.increment();
} else {
// A pending handle is a handle for security we are reasonbly sure
// exists but we haven't yet found live market data for.
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
_pending_handles.push_back({mhandle, MDRequestType::BOOK});
}
}
SecurityKey sk, metrics::CounterMetric<int> &synthetic_count_metric);
const char *get_external_event_channel(const md::UserSymbol &user_symbol) {
auto &channel = _symbol_to_channel[user_symbol];
@@ -276,73 +49,9 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
}
void on_ticker_sub(
SecurityKey sk, metrics::CounterMetric<int> &channel_count_metric) {
auto handle = _md_consumer.get_managed_handle(sk.value());
SecurityKey sk, metrics::CounterMetric<int> &channel_count_metric);
if (!handle->is_initialized()) {
LOG(OrderMan, Warn,
"Received ticker subscription for security {} which does not have "
"valid market data. Ignoring",
sk.value());
return;
}
auto user_symbol = handle->get_usersymbol();
for (auto &ch : _channels) {
if (ch.user_symbol == user_symbol) {
auto it = std::find_if(
ch.securities.begin(), ch.securities.end(),
[sk](auto const &ev) { return ev.key() == sk; });
if (it == ch.securities.end()) {
// first time we are asked for this security, we will
// publish the trades for it next time around
LOG(OrderMan, Warn,
"Starting to publish ticker updates on user symbol {} for "
"security key: {} (sec count for the symbol: {} )",
user_symbol.to_string(), sk.value(),
ch.securities.size() + 1);
ch.securities.push_back(sk);
}
return;
}
}
// this is a channel we are not subscribed to
channel_count_metric.increment();
LOG(OrderMan, Warn,
"Starting to publish ticker updates on user symbol {} for security "
"key: {}",
user_symbol.to_string(), sk.value());
// We do not want to get old trades so we drain the trades buffer at
// this point. This makes the first subscription to a channel
// consistent with subsquent ones in terms of late join semantics.
ChannelRecord c{
0, _md_consumer.get_channel(user_symbol), {}, user_symbol};
_trades_buffer.Clear();
c.cursor = c.channel.read_trades(_trades_buffer, c.cursor);
c.securities.push_back(sk);
_channels.push_back(c);
}
void on_status_sub(SecurityKey sk) {
auto k = sk.value();
for (auto &h : _status_subs) {
if (h.handle.key() == k) {
// reset status so it get resent next on_poll
h.status = co2::TRADINGSTATUS_UNKNOWN;
return;
}
}
auto handle = _md_consumer.get_managed_handle(k);
if (handle->is_initialized()) {
_status_subs.push_back(
StatusRecord{*handle, co2::TRADINGSTATUS_UNKNOWN});
} else {
LOG(OrderMan, Warn, "No market data for {}. Pending handle added.", k);
_pending_handles.push_back({handle, MDRequestType::STATUS});
}
}
void on_status_sub(SecurityKey sk);
struct HandleRecord {
utils::TimeStamp last;
@@ -361,7 +70,7 @@ class MDSource : public mercury::Actor, public mercury::IPollableActor {
struct ChannelRecord {
int64_t cursor;
co2::MDConsumerChannel channel;
md::MDConsumerChannel channel;
boost::container::small_vector<TickerUpdateEvent, 5> securities;
md::UserSymbol user_symbol;
};
@@ -4,7 +4,8 @@
namespace bts::orderman {
const std::string &getm(const NVMap &map, char const *key_name) {
const std::string &getm(
const std::map<std::string, std::string> &map, char const *key_name) {
auto it = map.find(key_name);
bts_assert_msg(
it != map.end(), "Could not find key_name" + std::string(key_name));
@@ -59,4 +60,4 @@ std::ostream &operator<<(
return out;
}
} // namespace bts::orderman
} // namespace bts::orderman
@@ -1,14 +1,8 @@
#pragma once
#include "ccapi_cpp/ccapi_message.h"
#include "ccapi_cpp/ccapi_session.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/bts_assert.h"
#include <ccapi_cpp/ccapi_element.h>
#include <ccapi_cpp/ccapi_event.h>
#include <ccapi_cpp/ccapi_message.h>
@@ -17,14 +11,11 @@
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <variant>
using namespace std;
namespace bts::orderman {
using NVMap = std::map<std::string, std::string>;
const std::string &getm(const NVMap &map, char const *key_name);
const std::string &getm(
const std::map<std::string, std::string> &map, char const *key_name);
bts::api::orderman::OrderStatus convert_ccapi_status(const std::string &status);
// 1.
@@ -66,7 +57,9 @@ struct CCAPICreateOrderMsg {
double cumm_filled_qty;
bts::api::orderman::OrderStatus status;
std::string corr_id;
CCAPICreateOrderMsg(const NVMap &map, const std::string &corr_id)
CCAPICreateOrderMsg(
const std::map<std::string, std::string> &map,
const std::string &corr_id)
: order_id(getm(map, CCAPI_EM_ORDER_ID)),
instrument(getm(map, CCAPI_INSTRUMENT)),
side(
@@ -159,7 +152,7 @@ struct CCAPIOrderUpdateMsg {
double cumm_filled_price;
double cumm_filled_qty;
api::orderman::OrderStatus status;
CCAPIOrderUpdateMsg(const NVMap &map)
CCAPIOrderUpdateMsg(const std::map<std::string, std::string> &map)
: order_id(getm(map, CCAPI_EM_ORDER_ID)),
side(
getm(map, CCAPI_EM_ORDER_SIDE) == CCAPI_EM_ORDER_SIDE_BUY
@@ -211,7 +204,7 @@ struct CCAPITradeMsg {
double last_exec_price;
double last_exec_qty;
std::string trade_id;
CCAPITradeMsg(const NVMap &map)
CCAPITradeMsg(const std::map<std::string, std::string> &map)
: order_id(getm(map, CCAPI_EM_ORDER_ID)),
side(
getm(map, CCAPI_EM_ORDER_SIDE) == CCAPI_EM_ORDER_SIDE_BUY
@@ -261,4 +254,4 @@ void to_json(nlohmann::json &j, const DeribitOpenPosition &dop);
std::ostream &operator<<(
std::ostream &out, const DeribitOpenPosition &dob) noexcept;
} // namespace bts::orderman
} // namespace bts::orderman
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff. Show more