Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e942dd2d4 | ||
|
|
142310a17a | ||
|
|
7f99a89a95 | ||
|
|
bfce663b4b | ||
|
|
33c743643d | ||
|
|
858e0a389b | ||
|
|
9c3bb85674 | ||
|
|
f4930e8cd4 | ||
|
|
f21e7f3602 | ||
|
|
b56c35aca4 | ||
|
|
e6ac83196e | ||
|
|
7b6f74fc4a | ||
|
|
f754953fac | ||
|
|
2dd1143d9f | ||
|
|
e1ee3275d8 | ||
|
|
2549e406f3 | ||
|
|
d5024f1858 | ||
|
|
3623ac96db | ||
|
|
19834625f9 | ||
|
|
c8fc649b6b | ||
|
|
6b1fc1c413 | ||
|
|
6f086532ac | ||
|
|
dc66282225 | ||
|
|
e2c74c0bb3 | ||
|
|
e89d50eec1 | ||
|
|
8bebbc278d | ||
|
|
2e6d12c9c1 | ||
|
|
102a5e23ea | ||
|
|
582dc791db | ||
|
|
afb35d8c25 | ||
|
|
3a32ed5fe6 | ||
|
|
4e01697327 | ||
|
|
22032c1742 | ||
|
|
0a9bce317c | ||
|
|
40e44db597 | ||
|
|
4a447e44e1 |
No files matched your search
Vendored
+62
@@ -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",
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -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.order.order.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
|
||||
@@ -363,6 +367,7 @@ 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; }
|
||||
@@ -391,6 +396,7 @@ 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(),
|
||||
avg_price = avg_price_column.filter.get_state(),
|
||||
@@ -415,6 +421,7 @@ 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);
|
||||
side_column.filter.load_state(fs.sides);
|
||||
order_type_column.filter.load_state(fs.order_types);
|
||||
@@ -645,6 +652,25 @@ 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.Qty, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(
|
||||
dc,
|
||||
_get_glyph_context(row),
|
||||
font_size,
|
||||
row.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)) { }
|
||||
|
||||
@@ -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.notional_value();
|
||||
public string notional_value_text => _fmt_price(notional_value);
|
||||
public TickDisplaySettingsImmutable quantity_display_settings {
|
||||
get => _quantity_display_settings;
|
||||
set {
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace bts.shell.orderbook {
|
||||
public Side[] sides;
|
||||
[ProtoMember(10)]
|
||||
public DoubleMinMaxState qty;
|
||||
[ProtoMember(26)]
|
||||
public DoubleMinMaxState notional_value;
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState price;
|
||||
[ProtoMember(12)]
|
||||
@@ -83,6 +85,7 @@ namespace bts.shell.orderbook {
|
||||
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) &&
|
||||
avg_price.safe_equals(other.avg_price) &&
|
||||
@@ -111,6 +114,7 @@ namespace bts.shell.orderbook {
|
||||
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,
|
||||
avg_price?.GetHashCode() ?? 0,
|
||||
@@ -149,6 +153,7 @@ namespace bts.shell.orderbook {
|
||||
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.order.order.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.avg_price, o => o.order.order.avg_price());
|
||||
|
||||
@@ -172,6 +172,7 @@ namespace bts.shell.orderbook {
|
||||
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.Size.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(
|
||||
|
||||
@@ -53,7 +53,8 @@ namespace mbts.spark {
|
||||
Commit = 1 << 28,
|
||||
RootAlgoType = 1 << 29,
|
||||
Piq = 1 << 30,
|
||||
Check = 1 << 31
|
||||
Check = 1 << 31,
|
||||
NotionalValue = 1 & (1 << 1),
|
||||
}
|
||||
|
||||
public OrderBookGridView() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace mbts.ui.controls {
|
||||
namespace mbts.ui.controls {
|
||||
using bts;
|
||||
using bts.utils;
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
Side = 47,
|
||||
Qty = 48,
|
||||
MergedQty = 68,
|
||||
NotionalValue = 68,
|
||||
Filled = 56,
|
||||
Symbol = 60,
|
||||
Time = 112,
|
||||
@@ -122,6 +123,7 @@
|
||||
SecurityType = "Security Type",
|
||||
Side = "B/S",
|
||||
Qty = "Qty",
|
||||
NotionalValue = "Notional",
|
||||
MergedQty = "Qty",
|
||||
DirectQty = "Qty (D)",
|
||||
ImpliedQty = "Qty (I)",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
}
|
||||
|
||||
void PerformMEFComposition(string config_source) {
|
||||
Log.Ui.info($"Generating MEF dependencies for the shell " +
|
||||
Log.Ui.info($"this is v2.2.3 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,9 @@ 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 working_quantity(this OrderRecordImmutable order) =>
|
||||
order.HasState ? order.State.working_quantity() : 0;
|
||||
|
||||
@@ -487,5 +490,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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,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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/md/SecurityIndex.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace bts::md {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/utils/SharedList.h"
|
||||
#include "lib/utils/WaitFreeQueue.h"
|
||||
#include <iomanip>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include <sstream>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace bts {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -88,7 +88,7 @@ struct SingleOrderMux {
|
||||
|
||||
struct InitState {
|
||||
enum class StateType { Start = 0, Securities = 1, Admin = 2 };
|
||||
map<StateType, bool> state_map = {
|
||||
std::map<StateType, bool> state_map = {
|
||||
{StateType::Start, false},
|
||||
{StateType::Securities, false},
|
||||
{StateType::Admin, false}};
|
||||
@@ -492,4 +492,4 @@ SingleOrderMux *CCAPIExecutionGateway::_find_mux_from_orderkey(OrderKey key) {
|
||||
return _find_mux(it->second.routing_record);
|
||||
}
|
||||
|
||||
} // namespace bts::orderman
|
||||
} // namespace bts::orderman
|
||||
@@ -2,10 +2,9 @@
|
||||
#include "SimExecutionGateway.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/common.h"
|
||||
#include "lib/utils/LockBox.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include <chrono>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
@@ -71,4 +70,4 @@ bool SimExecutionGatewayFactory::is_sim_execution_on(api::Venue venue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace bts::orderman
|
||||
} // namespace bts::orderman
|
||||
@@ -152,6 +152,12 @@ void SpreaderOrder::_on_time(const bts::mercury::TimerEvent &t_event) {
|
||||
}
|
||||
}
|
||||
void SpreaderOrder::_on_book_update(const BookUpdateEvent &book_event) {
|
||||
// TODO: rmeove
|
||||
LOG(OrderMan, Diag,
|
||||
"SpreaderOrder::_on_book_update skey: {} bid: {}/{} a: {}/{}",
|
||||
book_event.key(), book_event.book.bid().at(0).price(),
|
||||
book_event.book.bid().at(0).size(), book_event.book.ask().at(0).price(),
|
||||
book_event.book.ask().at(0).size());
|
||||
if (!_active)
|
||||
return;
|
||||
auto scoped = _order_services->log_scope(
|
||||
@@ -429,13 +435,18 @@ void SpreaderOrder::_compute_hedge_price_quantity(
|
||||
_bookkeeping_data().get_leg_data(filled_leg_index);
|
||||
SpreaderLegData &hedged_leg_data =
|
||||
_bookkeeping_data().get_leg_data(hedge_leg_index);
|
||||
if (_bookkeeping_data().n_legs() == 2)
|
||||
if (_bookkeeping_data().n_legs() == 2) {
|
||||
SpreaderUtils::compute_hedge_price_size_2_legs(
|
||||
_bookkeeping_data().effective_price(),
|
||||
filled_leg_data.leg_property_data(), fill_price, fill_quantity,
|
||||
hedged_leg_data.leg_property_data(), computed_hedge_price,
|
||||
hedge_quantity);
|
||||
else {
|
||||
LOG(OrderMan, Info,
|
||||
"SpreaderOrder::_compute_hedge_price_quantity effective_price: {} "
|
||||
"fill_price: {} fill_qty: hedge_price: {} hedge_qty: {}",
|
||||
_bookkeeping_data().effective_price(), fill_price, fill_quantity,
|
||||
computed_hedge_price, hedge_quantity);
|
||||
} else {
|
||||
NLegsHedgeRecompData &recomp_data =
|
||||
_bookkeeping_data().hedge_recomp_data();
|
||||
if (recomp_data.activated && recomp_data.last_call_succ()) {
|
||||
|
||||
@@ -180,7 +180,7 @@ class SpreaderOrder : public Order, public ISpreaderManager {
|
||||
void _on_book_update(const BookUpdateEvent &);
|
||||
void _on_settlement_update(const SettlementUpdateEvent &);
|
||||
void _on_child_order_update(const OrderStateUpdateEvent &);
|
||||
// order handling
|
||||
// order handling for quoter order update events
|
||||
inline void _on_quoter_order(
|
||||
const OrderStateUpdateEvent &ord_ev,
|
||||
SpreaderLegData &leg_data,
|
||||
|
||||
@@ -54,6 +54,9 @@ class LegPropertyData {
|
||||
bool is_net_change() const {
|
||||
return _price_calc == bts::api::SyntheticContract::PRICECALC_NET_CHANGE;
|
||||
}
|
||||
bool is_notional() const {
|
||||
return _price_calc == bts::api::SyntheticContract::PRICECALC_NOTIONAL_LEG;
|
||||
}
|
||||
bts::spark::LegSpreaderParams::MarketDataType md_type() const {
|
||||
return _leg_params.market_data_type();
|
||||
}
|
||||
@@ -67,9 +70,7 @@ class LegPropertyData {
|
||||
uint32_t n_queue_holder_orders() const {
|
||||
return _leg_params.quote_params().n_queue_holder_orders();
|
||||
}
|
||||
const bts::api::ContractLeg& leg () const{
|
||||
return _leg_contract;
|
||||
}
|
||||
const bts::api::ContractLeg &leg() const { return _leg_contract; }
|
||||
};
|
||||
|
||||
} // namespace orderman
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/domain_model/common.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderChildOrderState.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
@@ -65,7 +68,25 @@ class QuoteLegPriceQuantityState {
|
||||
rv = _in_target_leg_quantity.value() - accounted_for;
|
||||
return Quantity(rv);
|
||||
}
|
||||
Quantity remaining_to_fill_raw() const {
|
||||
Quantity rem = remaining_to_fill();
|
||||
SpreaderFractionalQtyHelper const *qty_helper =
|
||||
_order.spreader_qty_helper();
|
||||
bts_assert(qty_helper != nullptr);
|
||||
Quantity raw_qty = qty_helper->out_transform_leg_order_qty(
|
||||
_order.sec_key(), _order.total_filled_price(), rem);
|
||||
return raw_qty;
|
||||
}
|
||||
// TODO here, if the rmeainder is > min qty size, do the same
|
||||
bool target_reached() const { return remaining_to_fill().value() == 0; }
|
||||
|
||||
bool target_reached_raw() const {
|
||||
Quantity rem_raw = remaining_to_fill_raw();
|
||||
LOG(OrderMan, Info,
|
||||
"QuoteLegPriceQuantityState::target_reached_raw() rem_raw: {}",
|
||||
rem_raw.full_value());
|
||||
return rem_raw.is_zero();
|
||||
}
|
||||
// mutators ////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////
|
||||
// the 2 mutators, called by SingleLegQuoter (and the various QuoterStateWorkers)
|
||||
|
||||
@@ -290,6 +290,11 @@ void SingleLegQuoter::_transform_to_WAITING_FOR_CANCEL_ACK() {
|
||||
}
|
||||
void SingleLegQuoter::_transform_to_END() {
|
||||
bts_assert(_worker->state() != QuoterState::END);
|
||||
std::string curr_state = _worker == nullptr ? "NULL" : _worker->state_text();
|
||||
LOG(OrderMan, Info,
|
||||
"SingleLegQuoter::_transform_to_END "
|
||||
"spread_key:{} leg_idx:{}",
|
||||
_spreader.spreader_order_key(), _this_leg_data.leg_index());
|
||||
_switch_state(&_end_state_worker);
|
||||
}
|
||||
void SingleLegQuoter::_switch_state(IQuoterStateWorker *new_state) {
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
#include "SpreaderUtils.h"
|
||||
#include "external_interfacing/IAlternateOrderGateway.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/domain_model/SecurityKey.h"
|
||||
#include "lib/domain_model/common.h"
|
||||
#include "lib/orderman/events/OrderStateUpdateEvent.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/ISpreaderManager.h"
|
||||
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderFractionalQtyHelper.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
@@ -80,6 +82,7 @@ class SpreaderChildOrderState {
|
||||
else
|
||||
return _test_only_order_key;
|
||||
}
|
||||
SecurityKey sec_key() const { return _sec_key; }
|
||||
api::orderman::OrderStatus status() const { return _status; }
|
||||
// if the last fill was a status change
|
||||
// this will be zero
|
||||
@@ -112,11 +115,20 @@ class SpreaderChildOrderState {
|
||||
Quantity total_fills() const {
|
||||
return Quantity(_base_quantity.value() + _filled_quantity.value());
|
||||
}
|
||||
Price total_filled_price() const {
|
||||
Quantity filled_qty;
|
||||
Price rv;
|
||||
total_fills(filled_qty, rv);
|
||||
return rv;
|
||||
}
|
||||
void total_fills(Quantity &out_filled, Price &out_price) const {
|
||||
SpreaderUtils::compute_average_price(
|
||||
_base_price, _base_quantity, _filled_price, _filled_quantity,
|
||||
out_price, out_filled);
|
||||
}
|
||||
SpreaderFractionalQtyHelper const *spreader_qty_helper() {
|
||||
return _spreader_manager->spreader_qty_helper();
|
||||
}
|
||||
// this is the fills from the current active order
|
||||
Quantity current_fills() const { return _filled_quantity; }
|
||||
Price out_price() const { return _out_price; }
|
||||
@@ -161,7 +173,7 @@ class SpreaderChildOrderState {
|
||||
_n_place_orders++;
|
||||
auto const *qty_helper = _spreader_manager->spreader_qty_helper();
|
||||
auto adj_qty =
|
||||
qty_helper->out_transform_leg_order_qty(_sec_key, quantity);
|
||||
qty_helper->out_transform_leg_order_qty(_sec_key, price, quantity);
|
||||
if (_is_live) {
|
||||
if (_spreader_manager != nullptr)
|
||||
_spreader_manager->place_order(
|
||||
@@ -187,7 +199,7 @@ class SpreaderChildOrderState {
|
||||
_n_modify_orders++;
|
||||
auto const *qty_helper = _spreader_manager->spreader_qty_helper();
|
||||
auto adj_qty =
|
||||
qty_helper->out_transform_leg_order_qty(_sec_key, quantity);
|
||||
qty_helper->out_transform_leg_order_qty(_sec_key, price, quantity);
|
||||
if (_is_live) {
|
||||
if (_spreader_manager != nullptr) {
|
||||
_spreader_manager->update_order(
|
||||
|
||||
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user